open() instead of file()
[sfa.git] / sfa / client / sfi.py
index dcad9da..95acf18 100644 (file)
@@ -3,6 +3,8 @@
 # this module is also used in sfascan
 #
 
 # this module is also used in sfascan
 #
 
+from __future__ import print_function
+
 import sys
 sys.path.append('.')
 
 import sys
 sys.path.append('.')
 
@@ -15,7 +17,6 @@ import pickle
 import json
 import shutil
 from lxml import etree
 import json
 import shutil
 from lxml import etree
-from StringIO import StringIO
 from optparse import OptionParser
 from pprint import PrettyPrinter
 from tempfile import mkstemp
 from optparse import OptionParser
 from pprint import PrettyPrinter
 from tempfile import mkstemp
@@ -32,6 +33,7 @@ from sfa.util.config import Config
 from sfa.util.version import version_core
 from sfa.util.cache import Cache
 from sfa.util.printable import printable
 from sfa.util.version import version_core
 from sfa.util.cache import Cache
 from sfa.util.printable import printable
+from sfa.util.py23 import StringIO
 
 from sfa.storage.record import Record
 
 
 from sfa.storage.record import Record
 
@@ -46,7 +48,8 @@ from sfa.client.return_value import ReturnValue
 from sfa.client.candidates import Candidates
 from sfa.client.manifolduploader import ManifoldUploader
 
 from sfa.client.candidates import Candidates
 from sfa.client.manifolduploader import ManifoldUploader
 
-CM_PORT=12346
+CM_PORT = 12346
+DEFAULT_RSPEC_VERSION = "GENI 3"
 
 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
     terminal_render, filter_records 
 
 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
     terminal_render, filter_records 
@@ -66,12 +69,12 @@ def display_rspec(rspec, format='rspec'):
     else:
         result = rspec
 
     else:
         result = rspec
 
-    print result
+    print(result)
     return
 
 def display_list(results):
     for result in results:
     return
 
 def display_list(results):
     for result in results:
-        print result
+        print(result)
 
 def display_records(recordList, dump=False):
     ''' Print all fields in the record'''
 
 def display_records(recordList, dump=False):
     ''' Print all fields in the record'''
@@ -83,7 +86,7 @@ def display_record(record, dump=False):
         record.dump(sort=True)
     else:
         info = record.getdict()
         record.dump(sort=True)
     else:
         info = record.getdict()
-        print "%s (%s)" % (info['hrn'], info['type'])
+        print("{} ({})".format(info['hrn'], info['type']))
     return
 
 
     return
 
 
@@ -95,95 +98,108 @@ def filter_records(type, records):
     return filtered_records
 
 
     return filtered_records
 
 
-def credential_printable (cred):
-    credential=Credential(cred=cred)
-    result=""
-    result += credential.get_summary_tostring()
+def credential_printable(cred):
+    credential = Credential(cred=cred)
+    result = ""
+    result += credential.pretty_cred()
     result += "\n"
     rights = credential.get_privileges()
     result += "\n"
     rights = credential.get_privileges()
-    result += "type=%s\n" % credential.type    
-    result += "version=%s\n" % credential.version    
-    result += "rights=%s\n"%rights
+    result += "type={}\n".format(credential.type)
+    result += "version={}\n".format(credential.version)
+    result += "rights={}\n".format(rights)
     return result
 
     return result
 
-def show_credentials (cred_s):
-    if not isinstance (cred_s,list): cred_s = [cred_s]
+def show_credentials(cred_s):
+    if not isinstance(cred_s, list): cred_s = [cred_s]
     for cred in cred_s:
     for cred in cred_s:
-        print "Using Credential %s"%credential_printable(cred)
+        print("Using Credential {}".format(credential_printable(cred)))
+
+########## save methods
 
 
-# save methods
-def save_raw_to_file(var, filename, format="text", banner=None):
-    if filename == "-":
-        # if filename is "-", send it to stdout
-        f = sys.stdout
+### raw
+def save_raw_to_file(var, filename, format='text', banner=None):
+    if filename == '-':
+        _save_raw_to_file(var, sys.stdout, format, banner)
     else:
     else:
-        f = open(filename, "w")
-    if banner:
-        f.write(banner+"\n")
+        with open(filename, w) as fileobj:
+            _save_raw_to_file(var, fileobj, format, banner)
+        print("(Over)wrote {}".format(filename))
+
+def _save_raw_to_file(var, f, format, banner):
     if format == "text":
     if format == "text":
-        f.write(str(var))
+        if banner: f.write(banner+"\n")
+        f.write("{}".format(var))
+        if banner: f.write('\n'+banner+"\n")
     elif format == "pickled":
         f.write(pickle.dumps(var))
     elif format == "json":
     elif format == "pickled":
         f.write(pickle.dumps(var))
     elif format == "json":
-        if hasattr(json, "dumps"):
-            f.write(json.dumps(var))   # python 2.6
-        else:
-            f.write(json.write(var))   # python 2.5
+        f.write(json.dumps(var))   # python 2.6
     else:
         # this should never happen
     else:
         # this should never happen
-        print "unknown output format", format
-    if banner:
-        f.write('\n'+banner+"\n")
+        print("unknown output format", format)
 
 
+### 
 def save_rspec_to_file(rspec, filename):
     if not filename.endswith(".rspec"):
         filename = filename + ".rspec"
 def save_rspec_to_file(rspec, filename):
     if not filename.endswith(".rspec"):
         filename = filename + ".rspec"
-    f = open(filename, 'w')
-    f.write("%s"%rspec)
-    f.close()
-    return
+    with open(filename, 'w') as f:
+        f.write("{}".format(rspec))
+    print("(Over)wrote {}".format(filename))
+
+def save_record_to_file(filename, record_dict):
+    record = Record(dict=record_dict)
+    xml = record.save_as_xml()
+    with codecs.open(filename, encoding='utf-8', mode="w") as f:
+        f.write(xml)
+    print("(Over)wrote {}".format(filename))
 
 def save_records_to_file(filename, record_dicts, format="xml"):
     if format == "xml":
 
 def save_records_to_file(filename, record_dicts, format="xml"):
     if format == "xml":
-        index = 0
-        for record_dict in record_dicts:
-            if index > 0:
-                save_record_to_file(filename + "." + str(index), record_dict)
-            else:
-                save_record_to_file(filename, record_dict)
-            index = index + 1
+        for index, record_dict in enumerate(record_dicts):
+            save_record_to_file(filename + "." + str(index), record_dict)
     elif format == "xmllist":
     elif format == "xmllist":
-        f = open(filename, "w")
-        f.write("<recordlist>\n")
-        for record_dict in record_dicts:
-            record_obj=Record(dict=record_dict)
-            f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
-        f.write("</recordlist>\n")
-        f.close()
+        with open(filename, "w") as f:
+            f.write("<recordlist>\n")
+            for record_dict in record_dicts:
+                record_obj = Record(dict=record_dict)
+                f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
+            f.write("</recordlist>\n")
+            print("(Over)wrote {}".format(filename))
+
     elif format == "hrnlist":
     elif format == "hrnlist":
-        f = open(filename, "w")
-        for record_dict in record_dicts:
-            record_obj=Record(dict=record_dict)
-            f.write(record_obj.hrn + "\n")
-        f.close()
+        with open(filename, "w") as f:
+            for record_dict in record_dicts:
+                record_obj = Record(dict=record_dict)
+                f.write(record_obj.hrn + "\n")
+            print("(Over)wrote {}".format(filename))
+
     else:
         # this should never happen
     else:
         # this should never happen
-        print "unknown output format", format
-
-def save_record_to_file(filename, record_dict):
-    record = Record(dict=record_dict)
-    xml = record.save_as_xml()
-    f=codecs.open(filename, encoding='utf-8',mode="w")
-    f.write(xml)
-    f.close()
-    return
+        print("unknown output format", format)
 
 # minimally check a key argument
 
 # minimally check a key argument
-def check_ssh_key (key):
+def check_ssh_key(key):
     good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
     return re.match(good_ssh_key, key, re.IGNORECASE)
 
 # load methods
     good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
     return re.match(good_ssh_key, key, re.IGNORECASE)
 
 # load methods
+def normalize_type(type):
+    if type.startswith('au'):
+        return 'authority'
+    elif type.startswith('us'):
+        return 'user'
+    elif type.startswith('sl'):
+        return 'slice'
+    elif type.startswith('no'):
+        return 'node'
+    elif type.startswith('ag'):
+        return 'aggregate'
+    elif type.startswith('al'):
+        return 'all'
+    else:
+        print('unknown type {} - should start with one of au|us|sl|no|ag|al'.format(type))
+        return None
+
 def load_record_from_opts(options):
     record_dict = {}
     if hasattr(options, 'xrn') and options.xrn:
 def load_record_from_opts(options):
     record_dict = {}
     if hasattr(options, 'xrn') and options.xrn:
@@ -199,8 +215,8 @@ def load_record_from_opts(options):
             pubkey = open(options.key, 'r').read()
         except IOError:
             pubkey = options.key
             pubkey = open(options.key, 'r').read()
         except IOError:
             pubkey = options.key
-        if not check_ssh_key (pubkey):
-            raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
+        if not check_ssh_key(pubkey):
+            raise SfaInvalidArgument(name='key', msg="Could not find file, or wrong key format")
         record_dict['reg-keys'] = [pubkey]
     if hasattr(options, 'slices') and options.slices:
         record_dict['slices'] = options.slices
         record_dict['reg-keys'] = [pubkey]
     if hasattr(options, 'slices') and options.slices:
         record_dict['slices'] = options.slices
@@ -208,6 +224,9 @@ def load_record_from_opts(options):
         record_dict['reg-researchers'] = options.reg_researchers
     if hasattr(options, 'email') and options.email:
         record_dict['email'] = options.email
         record_dict['reg-researchers'] = options.reg_researchers
     if hasattr(options, 'email') and options.email:
         record_dict['email'] = options.email
+    # authorities can have a name for standalone deployment
+    if hasattr(options, 'name') and options.name:
+        record_dict['name'] = options.name
     if hasattr(options, 'reg_pis') and options.reg_pis:
         record_dict['reg-pis'] = options.reg_pis
 
     if hasattr(options, 'reg_pis') and options.reg_pis:
         record_dict['reg-pis'] = options.reg_pis
 
@@ -217,11 +236,9 @@ def load_record_from_opts(options):
     return Record(dict=record_dict)
 
 def load_record_from_file(filename):
     return Record(dict=record_dict)
 
 def load_record_from_file(filename):
-    f=codecs.open(filename, encoding="utf-8", mode="r")
-    xml_string = f.read()
-    f.close()
-    return Record(xml=xml_string)
-
+    with codecs.open(filename, encoding="utf-8", mode="r") as f:
+        xml_str = f.read()
+    return Record(xml=xml_str)
 
 import uuid
 def unique_call_id(): return uuid.uuid4().urn
 
 import uuid
 def unique_call_id(): return uuid.uuid4().urn
@@ -242,27 +259,27 @@ from functools import wraps
 commands_list=[]
 commands_dict={}
 
 commands_list=[]
 commands_dict={}
 
-def declare_command (args_string, example,aliases=None):
+def declare_command(args_string, example, aliases=None):
     def wrap(m): 
     def wrap(m): 
-        name=getattr(m,'__name__')
-        doc=getattr(m,'__doc__',"-- missing doc --")
+        name=getattr(m, '__name__')
+        doc=getattr(m, '__doc__', "-- missing doc --")
         doc=doc.strip(" \t\n")
         commands_list.append(name)
         # last item is 'canonical' name, so we can know which commands are aliases
         doc=doc.strip(" \t\n")
         commands_list.append(name)
         # last item is 'canonical' name, so we can know which commands are aliases
-        command_tuple=(doc, args_string, example,name)
+        command_tuple=(doc, args_string, example, name)
         commands_dict[name]=command_tuple
         if aliases is not None:
             for alias in aliases:
                 commands_list.append(alias)
                 commands_dict[alias]=command_tuple
         @wraps(m)
         commands_dict[name]=command_tuple
         if aliases is not None:
             for alias in aliases:
                 commands_list.append(alias)
                 commands_dict[alias]=command_tuple
         @wraps(m)
-        def new_method (*args, **kwds): return m(*args, **kwds)
+        def new_method(*args, **kwds): return m(*args, **kwds)
         return new_method
     return wrap
 
 
         return new_method
     return wrap
 
 
-def remove_none_fields (record):
-    none_fields=[ k for (k,v) in record.items() if v is None ]
+def remove_none_fields(record):
+    none_fields=[ k for (k, v) in record.items() if v is None ]
     for k in none_fields: del record[k]
 
 ##########
     for k in none_fields: del record[k]
 
 ##########
@@ -273,7 +290,7 @@ class Sfi:
     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user', 'user_private_key']
 
     @staticmethod
     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user', 'user_private_key']
 
     @staticmethod
-    def default_sfi_dir ():
+    def default_sfi_dir():
         if os.path.isfile("./sfi_config"): 
             return os.getcwd()
         else:
         if os.path.isfile("./sfi_config"): 
             return os.getcwd()
         else:
@@ -284,11 +301,13 @@ class Sfi:
     class DummyOptions:
         pass
 
     class DummyOptions:
         pass
 
-    def __init__ (self,options=None):
+    def __init__(self, options=None):
         if options is None: options=Sfi.DummyOptions()
         for opt in Sfi.required_options:
         if options is None: options=Sfi.DummyOptions()
         for opt in Sfi.required_options:
-            if not hasattr(options,opt): setattr(options,opt,None)
-        if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
+            if not hasattr(options, opt):
+                setattr(options, opt, None)
+        if not hasattr(options, 'sfi_dir'):
+            options.sfi_dir = Sfi.default_sfi_dir()
         self.options = options
         self.user = None
         self.authority = None
         self.options = options
         self.user = None
         self.authority = None
@@ -297,60 +316,61 @@ class Sfi:
         ### various auxiliary material that we keep at hand 
         self.command=None
         # need to call this other than just 'config' as we have a command/method with that name
         ### various auxiliary material that we keep at hand 
         self.command=None
         # need to call this other than just 'config' as we have a command/method with that name
-        self.config_instance=None
-        self.config_file=None
-        self.client_bootstrap=None
+        self.config_instance = None
+        self.config_file = None
+        self.client_bootstrap = None
 
     ### suitable if no reasonable command has been provided
 
     ### suitable if no reasonable command has been provided
-    def print_commands_help (self, options):
-        verbose=getattr(options,'verbose')
-        format3="%10s %-35s %s"
-        format3offset=47
-        line=80*'-'
+    def print_commands_help(self, options):
+        verbose = getattr(options, 'verbose')
+        format3 = "%10s %-35s %s"
+        format3offset = 47
+        line = 80*'-'
         if not verbose:
         if not verbose:
-            print format3%("command","cmd_args","description")
-            print line
+            print(format3%("command", "cmd_args", "description"))
+            print(line)
         else:
         else:
-            print line
+            print(line)
             self.create_parser_global().print_help()
         # preserve order from the code
         for command in commands_list:
             try:
                 (doc, args_string, example, canonical) = commands_dict[command]
             except:
             self.create_parser_global().print_help()
         # preserve order from the code
         for command in commands_list:
             try:
                 (doc, args_string, example, canonical) = commands_dict[command]
             except:
-                print "Cannot find info on command %s - skipped"%command
+                print("Cannot find info on command %s - skipped"%command)
                 continue
             if verbose:
                 continue
             if verbose:
-                print line
+                print(line)
             if command==canonical:
             if command==canonical:
-                doc=doc.replace("\n","\n"+format3offset*' ')
-                print format3%(command,args_string,doc)
+                doc = doc.replace("\n", "\n" + format3offset * ' ')
+                print(format3 % (command, args_string, doc))
                 if verbose:
                     self.create_parser_command(command).print_help()
             else:
                 if verbose:
                     self.create_parser_command(command).print_help()
             else:
-                print format3%(command,"<<alias for %s>>"%canonical,"")
+                print(format3 % (command, "<<alias for %s>>"%canonical, ""))
             
     ### now if a known command was found we can be more verbose on that one
             
     ### now if a known command was found we can be more verbose on that one
-    def print_help (self):
-        print "==================== Generic sfi usage"
+    def print_help(self):
+        print("==================== Generic sfi usage")
         self.sfi_parser.print_help()
         self.sfi_parser.print_help()
-        (doc,_,example,canonical)=commands_dict[self.command]
+        (doc, _, example, canonical) = commands_dict[self.command]
         if canonical != self.command:
         if canonical != self.command:
-            print "\n==================== NOTE: %s is an alias for genuine %s"%(self.command,canonical)
-            self.command=canonical
-        print "\n==================== Purpose of %s"%self.command
-        print doc
-        print "\n==================== Specific usage for %s"%self.command
+            print("\n==================== NOTE: {} is an alias for genuine {}"
+                  .format(self.command, canonical))
+            self.command = canonical
+        print("\n==================== Purpose of {}".format(self.command))
+        print(doc)
+        print("\n==================== Specific usage for {}".format(self.command))
         self.command_parser.print_help()
         if example:
         self.command_parser.print_help()
         if example:
-            print "\n==================== %s example(s)"%self.command
-            print example
+            print("\n==================== {} example(s)".format(self.command))
+            print(example)
 
     def create_parser_global(self):
         # Generate command line parser
         parser = OptionParser(add_help_option=False,
                               usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
 
     def create_parser_global(self):
         # Generate command line parser
         parser = OptionParser(add_help_option=False,
                               usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
-                              description="Commands: %s"%(" ".join(commands_list)))
+                              description="Commands: {}".format(" ".join(commands_list)))
         parser.add_option("-r", "--registry", dest="registry",
                          help="root registry", metavar="URL", default=None)
         parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
         parser.add_option("-r", "--registry", dest="registry",
                          help="root registry", metavar="URL", default=None)
         parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
@@ -397,12 +417,12 @@ class Sfi:
             sys.exit(2)
 
         # retrieve args_string
             sys.exit(2)
 
         # retrieve args_string
-        (_, args_string, __,canonical) = commands_dict[command]
+        (_, args_string, __, canonical) = commands_dict[command]
 
         parser = OptionParser(add_help_option=False,
 
         parser = OptionParser(add_help_option=False,
-                              usage="sfi [sfi_options] %s [cmd_options] %s"
-                              (command, args_string))
-        parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
+                              usage="sfi [sfi_options] {} [cmd_options] {}"\
+                              .format(command, args_string))
+        parser.add_option("-h","--help",dest='help',action='store_true',default=False,
                            help="Summary of one command usage")
 
         if canonical in ("config"):
                            help="Summary of one command usage")
 
         if canonical in ("config"):
@@ -414,15 +434,16 @@ class Sfi:
                               action="store_true", dest="version_local", default=False,
                               help="display version of the local client")
 
                               action="store_true", dest="version_local", default=False,
                               help="display version of the local client")
 
-        if canonical in ("version", "trusted"):
+        if canonical in ("version", "trusted", "introspect"):
             parser.add_option("-R","--registry_interface",
                               action="store_true", dest="registry_interface", default=False,
                               help="target the registry interface instead of slice interface")
 
         if canonical in ("register", "update"):
             parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
             parser.add_option("-R","--registry_interface",
                               action="store_true", dest="registry_interface", default=False,
                               help="target the registry interface instead of slice interface")
 
         if canonical in ("register", "update"):
             parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
-            parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
+            parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type (2 first chars is enough)', default=None)
             parser.add_option('-e', '--email', dest='email', default="",  help="email (mandatory for users)") 
             parser.add_option('-e', '--email', dest='email', default="",  help="email (mandatory for users)") 
+            parser.add_option('-n', '--name', dest='name', default="",  help="name (optional for authorities)") 
             parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file', 
                               default=None)
             parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
             parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file', 
                               default=None)
             parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
@@ -432,7 +453,7 @@ class Sfi:
                               callback=optparse_listvalue_callback)
             parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
                               callback=optparse_listvalue_callback)
             parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
-            parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
+            parser.add_option('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
                                action="callback", callback=optparse_dictvalue_callback, nargs=1,
                                help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
 
                                action="callback", callback=optparse_dictvalue_callback, nargs=1,
                                help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
 
@@ -453,10 +474,9 @@ class Sfi:
                               help="renew as long as possible")
         # registy filter option
         if canonical in ("list", "show", "remove"):
                               help="renew as long as possible")
         # registy filter option
         if canonical in ("list", "show", "remove"):
-            parser.add_option("-t", "--type", dest="type", type="choice",
-                            help="type filter ([all]|user|slice|authority|node|aggregate)",
-                            choices=("all", "user", "slice", "authority", "node", "aggregate"),
-                            default="all")
+            parser.add_option("-t", "--type", dest="type", metavar="<type>",
+                              default="all",
+                              help="type filter - 2 first chars is enough ([all]|user|slice|authority|node|aggregate)")
         if canonical in ("show"):
             parser.add_option("-k","--key",dest="keys",action="append",default=[],
                               help="specify specific keys to be displayed from record")
         if canonical in ("show"):
             parser.add_option("-k","--key",dest="keys",action="append",default=[],
                               help="specify specific keys to be displayed from record")
@@ -464,8 +484,8 @@ class Sfi:
                               help="call Resolve without the 'details' option")
         if canonical in ("resources", "describe"):
             # rspec version
                               help="call Resolve without the 'details' option")
         if canonical in ("resources", "describe"):
             # rspec version
-            parser.add_option("-r", "--rspec-version", dest="rspec_version", default="GENI 3",
-                              help="schema type and version of resulting RSpec")
+            parser.add_option("-r", "--rspec-version", dest="rspec_version", default=DEFAULT_RSPEC_VERSION,
+                              help="schema type and version of resulting RSpec (default:{})".format(DEFAULT_RSPEC_VERSION))
             # disable/enable cached rspecs
             parser.add_option("-c", "--current", dest="current", default=False,
                               action="store_true",  
             # disable/enable cached rspecs
             parser.add_option("-c", "--current", dest="current", default=False,
                               action="store_true",  
@@ -477,9 +497,9 @@ class Sfi:
             #panos: a new option to define the type of information about resources a user is interested in
             parser.add_option("-i", "--info", dest="info",
                                 help="optional component information", default=None)
             #panos: a new option to define the type of information about resources a user is interested in
             parser.add_option("-i", "--info", dest="info",
                                 help="optional component information", default=None)
-            # a new option to retreive or not reservation-oriented RSpecs (leases)
+            # a new option to retrieve or not reservation-oriented RSpecs (leases)
             parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
             parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
-                                help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
+                                help="Retrieve or not reservation-oriented RSpecs ([resources]|leases|all)",
                                 choices=("all", "resources", "leases"), default="resources")
 
 
                                 choices=("all", "resources", "leases"), default="resources")
 
 
@@ -535,8 +555,12 @@ use this if you mean an authority instead""")
         (doc, args_string, example, canonical) = commands_dict[command]
         method=getattr(self, canonical, None)
         if not method:
         (doc, args_string, example, canonical) = commands_dict[command]
         method=getattr(self, canonical, None)
         if not method:
-            print "sfi: unknown command %s"%command
-            raise SystemExit,"Unknown command %s"%command
+            print("sfi: unknown command {}".format(command))
+            raise SystemExit("Unknown command {}".format(command))
+        for arg in command_args:
+            if 'help' in arg or arg == '-h':
+                self.print_help()
+                sys.exit(1)
         return method(command_options, command_args)
 
     def main(self):
         return method(command_options, command_args)
 
     def main(self):
@@ -555,14 +579,14 @@ use this if you mean an authority instead""")
             return -1
     
         # complete / find unique match with command set
             return -1
     
         # complete / find unique match with command set
-        command_candidates = Candidates (commands_list)
+        command_candidates = Candidates(commands_list)
         input = args[0]
         command = command_candidates.only_match(input)
         if not command:
             self.print_commands_help(options)
             sys.exit(1)
         # second pass options parsing
         input = args[0]
         command = command_candidates.only_match(input)
         if not command:
             self.print_commands_help(options)
             sys.exit(1)
         # second pass options parsing
-        self.command=command
+        self.command = command
         self.command_parser = self.create_parser_command(command)
         (command_options, command_args) = self.command_parser.parse_args(args[1:])
         if command_options.help:
         self.command_parser = self.create_parser_command(command)
         (command_options, command_args) = self.command_parser.parse_args(args[1:])
         if command_options.help:
@@ -570,26 +594,32 @@ use this if you mean an authority instead""")
             sys.exit(1)
         self.command_options = command_options
 
             sys.exit(1)
         self.command_options = command_options
 
-        self.read_config () 
-        self.bootstrap ()
-        self.logger.debug("Command=%s" % self.command)
+        # allow incoming types on 2 characters only
+        if hasattr(command_options, 'type'):
+            command_options.type = normalize_type(command_options.type)
+            if not command_options.type:
+                sys.exit(1)
+        
+        self.read_config() 
+        self.bootstrap()
+        self.logger.debug("Command={}".format(self.command))
 
         try:
             retcod = self.dispatch(command, command_options, command_args)
         except SystemExit:
             return 1
         except:
 
         try:
             retcod = self.dispatch(command, command_options, command_args)
         except SystemExit:
             return 1
         except:
-            self.logger.log_exc ("sfi command %s failed"%command)
+            self.logger.log_exc("sfi command {} failed".format(command))
             return 1
         return retcod
     
     ####################
     def read_config(self):
             return 1
         return retcod
     
     ####################
     def read_config(self):
-        config_file = os.path.join(self.options.sfi_dir,"sfi_config")
-        shell_config_file  = os.path.join(self.options.sfi_dir,"sfi_config.sh")
+        config_file = os.path.join(self.options.sfi_dir, "sfi_config")
+        shell_config_file  = os.path.join(self.options.sfi_dir, "sfi_config.sh")
         try:
             if Config.is_ini(config_file):
         try:
             if Config.is_ini(config_file):
-                config = Config (config_file)
+                config = Config(config_file)
             else:
                 # try upgrading from shell config format
                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
             else:
                 # try upgrading from shell config format
                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
@@ -609,15 +639,15 @@ use this if you mean an authority instead""")
                 config.save(config_file)
                  
         except:
                 config.save(config_file)
                  
         except:
-            self.logger.critical("Failed to read configuration file %s"%config_file)
+            self.logger.critical("Failed to read configuration file {}".format(config_file))
             self.logger.info("Make sure to remove the export clauses and to add quotes")
             self.logger.info("Make sure to remove the export clauses and to add quotes")
-            if self.options.verbose==0:
+            if self.options.verbose == 0:
                 self.logger.info("Re-run with -v for more details")
             else:
                 self.logger.info("Re-run with -v for more details")
             else:
-                self.logger.log_exc("Could not read config file %s"%config_file)
+                self.logger.log_exc("Could not read config file {}".format(config_file))
             sys.exit(1)
      
             sys.exit(1)
      
-        self.config_instance=config
+        self.config_instance = config
         errors = 0
         # Set SliceMgr URL
         if (self.options.sm is not None):
         errors = 0
         # Set SliceMgr URL
         if (self.options.sm is not None):
@@ -625,7 +655,7 @@ use this if you mean an authority instead""")
         elif hasattr(config, "SFI_SM"):
            self.sm_url = config.SFI_SM
         else:
         elif hasattr(config, "SFI_SM"):
            self.sm_url = config.SFI_SM
         else:
-           self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
+           self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in {}".format(config_file))
            errors += 1 
 
         # Set Registry URL
            errors += 1 
 
         # Set Registry URL
@@ -634,7 +664,7 @@ use this if you mean an authority instead""")
         elif hasattr(config, "SFI_REGISTRY"):
            self.reg_url = config.SFI_REGISTRY
         else:
         elif hasattr(config, "SFI_REGISTRY"):
            self.reg_url = config.SFI_REGISTRY
         else:
-           self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
+           self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in {}".format(config_file))
            errors += 1 
 
         # Set user HRN
            errors += 1 
 
         # Set user HRN
@@ -643,7 +673,7 @@ use this if you mean an authority instead""")
         elif hasattr(config, "SFI_USER"):
            self.user = config.SFI_USER
         else:
         elif hasattr(config, "SFI_USER"):
            self.user = config.SFI_USER
         else:
-           self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
+           self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in {}".format(config_file))
            errors += 1 
 
         # Set authority HRN
            errors += 1 
 
         # Set authority HRN
@@ -652,10 +682,10 @@ use this if you mean an authority instead""")
         elif hasattr(config, "SFI_AUTH"):
            self.authority = config.SFI_AUTH
         else:
         elif hasattr(config, "SFI_AUTH"):
            self.authority = config.SFI_AUTH
         else:
-           self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
+           self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file))
            errors += 1 
 
            errors += 1 
 
-        self.config_file=config_file
+        self.config_file = config_file
         if errors:
            sys.exit(1)
 
         if errors:
            sys.exit(1)
 
@@ -673,22 +703,27 @@ use this if you mean an authority instead""")
     #
     
     # init self-signed cert, user credentials and gid
     #
     
     # init self-signed cert, user credentials and gid
-    def bootstrap (self):
-        client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
+    def bootstrap(self):
+        if self.options.verbose:
+            self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url))
+        client_bootstrap = SfaClientBootstrap(self.user, self.reg_url, self.options.sfi_dir,
                                                logger=self.logger)
         # if -k is provided, use this to initialize private key
         if self.options.user_private_key:
                                                logger=self.logger)
         # if -k is provided, use this to initialize private key
         if self.options.user_private_key:
-            client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
+            client_bootstrap.init_private_key_if_missing(self.options.user_private_key)
         else:
             # trigger legacy compat code if needed 
             # the name has changed from just <leaf>.pkey to <hrn>.pkey
             if not os.path.isfile(client_bootstrap.private_key_filename()):
         else:
             # trigger legacy compat code if needed 
             # the name has changed from just <leaf>.pkey to <hrn>.pkey
             if not os.path.isfile(client_bootstrap.private_key_filename()):
-                self.logger.info ("private key not found, trying legacy name")
+                self.logger.info("private key not found, trying legacy name")
                 try:
                 try:
-                    legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
-                    self.logger.debug("legacy_private_key=%s"%legacy_private_key)
-                    client_bootstrap.init_private_key_if_missing (legacy_private_key)
-                    self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
+                    legacy_private_key = os.path.join(self.options.sfi_dir, "{}.pkey"
+                                                       .format(Xrn.unescape(get_leaf(self.user))))
+                    self.logger.debug("legacy_private_key={}"
+                                      .format(legacy_private_key))
+                    client_bootstrap.init_private_key_if_missing(legacy_private_key)
+                    self.logger.info("Copied private key from legacy location {}"
+                                     .format(legacy_private_key))
                 except:
                     self.logger.log_exc("Can't find private key ")
                     sys.exit(1)
                 except:
                     self.logger.log_exc("Can't find private key ")
                     sys.exit(1)
@@ -697,11 +732,11 @@ use this if you mean an authority instead""")
         client_bootstrap.bootstrap_my_gid()
         # extract what's needed
         self.private_key = client_bootstrap.private_key()
         client_bootstrap.bootstrap_my_gid()
         # extract what's needed
         self.private_key = client_bootstrap.private_key()
-        self.my_credential_string = client_bootstrap.my_credential_string ()
+        self.my_credential_string = client_bootstrap.my_credential_string()
         self.my_credential = {'geni_type': 'geni_sfa',
                               'geni_version': '3', 
                               'geni_value': self.my_credential_string}
         self.my_credential = {'geni_type': 'geni_sfa',
                               'geni_version': '3', 
                               'geni_value': self.my_credential_string}
-        self.my_gid = client_bootstrap.my_gid ()
+        self.my_gid = client_bootstrap.my_gid()
         self.client_bootstrap = client_bootstrap
 
 
         self.client_bootstrap = client_bootstrap
 
 
@@ -709,13 +744,13 @@ use this if you mean an authority instead""")
         if not self.authority:
             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
             sys.exit(-1)
         if not self.authority:
             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
             sys.exit(-1)
-        return self.client_bootstrap.authority_credential_string (self.authority)
+        return self.client_bootstrap.authority_credential_string(self.authority)
 
     def authority_credential_string(self, auth_hrn):
 
     def authority_credential_string(self, auth_hrn):
-        return self.client_bootstrap.authority_credential_string (auth_hrn)
+        return self.client_bootstrap.authority_credential_string(auth_hrn)
 
     def slice_credential_string(self, name):
 
     def slice_credential_string(self, name):
-        return self.client_bootstrap.slice_credential_string (name)
+        return self.client_bootstrap.slice_credential_string(name)
 
     def slice_credential(self, name):
         return {'geni_type': 'geni_sfa',
 
     def slice_credential(self, name):
         return {'geni_type': 'geni_sfa',
@@ -731,14 +766,15 @@ use this if you mean an authority instead""")
         object_hrn = object_gid.get_hrn()
     
         if not object_cred.get_privileges().get_all_delegate():
         object_hrn = object_gid.get_hrn()
     
         if not object_cred.get_privileges().get_all_delegate():
-            self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
+            self.logger.error("Object credential {} does not have delegate bit set"
+                              .format(object_hrn))
             return
 
         # the delegating user's gid
         caller_gidfile = self.my_gid()
   
         # the gid of the user who will be delegated to
             return
 
         # the delegating user's gid
         caller_gidfile = self.my_gid()
   
         # the gid of the user who will be delegated to
-        delegee_gid = self.client_bootstrap.gid(hrn,type)
+        delegee_gid = self.client_bootstrap.gid(hrn, type)
         delegee_hrn = delegee_gid.get_hrn()
         dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
         return dcred.save_to_string(save_parents=True)
         delegee_hrn = delegee_gid.get_hrn()
         dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
         return dcred.save_to_string(save_parents=True)
@@ -747,58 +783,60 @@ use this if you mean an authority instead""")
     # Management of the servers
     # 
 
     # Management of the servers
     # 
 
-    def registry (self):
+    def registry(self):
         # cache the result
         # cache the result
-        if not hasattr (self, 'registry_proxy'):
-            self.logger.info("Contacting Registry at: %s"%self.reg_url)
-            self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
-                                                 timeout=self.options.timeout, verbose=self.options.debug)  
+        if not hasattr(self, 'registry_proxy'):
+            self.logger.info("Contacting Registry at: {}".format(self.reg_url))
+            self.registry_proxy \
+                =  SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
+                                  timeout=self.options.timeout, verbose=self.options.debug)  
         return self.registry_proxy
 
         return self.registry_proxy
 
-    def sliceapi (self):
+    def sliceapi(self):
         # cache the result
         # cache the result
-        if not hasattr (self, 'sliceapi_proxy'):
+        if not hasattr(self, 'sliceapi_proxy'):
             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
-            if hasattr(self.command_options,'component') and self.command_options.component:
+            if hasattr(self.command_options, 'component') and self.command_options.component:
                 # resolve the hrn at the registry
                 node_hrn = self.command_options.component
                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
                 records = filter_records('node', records)
                 if not records:
                 # resolve the hrn at the registry
                 node_hrn = self.command_options.component
                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
                 records = filter_records('node', records)
                 if not records:
-                    self.logger.warning("No such component:%r"% opts.component)
+                    self.logger.warning("No such component:{}".format(opts.component))
                 record = records[0]
                 record = records[0]
-                cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
-                self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
+                cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT)
+                self.sliceapi_proxy = SfaServerProxy(cm_url, self.private_key, self.my_gid)
             else:
                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
                     self.sm_url = 'http://' + self.sm_url
             else:
                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
                     self.sm_url = 'http://' + self.sm_url
-                self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
-                self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
-                                                     timeout=self.options.timeout, verbose=self.options.debug)  
+                self.logger.info("Contacting Slice Manager at: {}".format(self.sm_url))
+                self.sliceapi_proxy \
+                    = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
+                                     timeout=self.options.timeout, verbose=self.options.debug)  
         return self.sliceapi_proxy
 
     def get_cached_server_version(self, server):
         # check local cache first
         cache = None
         version = None 
         return self.sliceapi_proxy
 
     def get_cached_server_version(self, server):
         # check local cache first
         cache = None
         version = None 
-        cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
+        cache_file = os.path.join(self.options.sfi_dir, 'sfi_cache.dat')
         cache_key = server.url + "-version"
         try:
             cache = Cache(cache_file)
         except IOError:
             cache = Cache()
         cache_key = server.url + "-version"
         try:
             cache = Cache(cache_file)
         except IOError:
             cache = Cache()
-            self.logger.info("Local cache not found at: %s" % cache_file)
+            self.logger.info("Local cache not found at: {}".format(cache_file))
 
         if cache:
             version = cache.get(cache_key)
 
         if not version: 
             result = server.GetVersion()
 
         if cache:
             version = cache.get(cache_key)
 
         if not version: 
             result = server.GetVersion()
-            version= ReturnValue.get_value(result)
+            version = ReturnValue.get_value(result)
             # cache version for 20 minutes
             # cache version for 20 minutes
-            cache.add(cache_key, version, ttl= 60*20)
-            self.logger.info("Updating cache file %s" % cache_file)
+            cache.add(cache_key, version, ttl=60*20)
+            self.logger.info("Updating cache file {}".format(cache_file))
             cache.save_to_file(cache_file)
 
         return version   
             cache.save_to_file(cache_file)
 
         return version   
@@ -829,18 +867,18 @@ use this if you mean an authority instead""")
         return result                 
 
     ### ois = options if supported
         return result                 
 
     ### ois = options if supported
-    # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
-    def ois (self, server, option_dict):
-        if self.server_supports_options_arg (server): 
+    # to be used in something like serverproxy.Method(arg1, arg2, *self.ois(api_options))
+    def ois(self, server, option_dict):
+        if self.server_supports_options_arg(server): 
             return [option_dict]
             return [option_dict]
-        elif self.server_supports_call_id_arg (server):
-            return [ unique_call_id () ]
+        elif self.server_supports_call_id_arg(server):
+            return [ unique_call_id() ]
         else: 
             return []
 
     ### cis = call_id if supported - like ois
         else: 
             return []
 
     ### cis = call_id if supported - like ois
-    def cis (self, server):
-        if self.server_supports_call_id_arg (server):
+    def cis(self, server):
+        if self.server_supports_call_id_arg(server):
             return [ unique_call_id ]
         else:
             return []
             return [ unique_call_id ]
         else:
             return []
@@ -854,7 +892,7 @@ use this if you mean an authority instead""")
        if (os.path.isfile(file)):
           return file
        else:
        if (os.path.isfile(file)):
           return file
        else:
-          self.logger.critical("No such rspec file %s"%rspec)
+          self.logger.critical("No such rspec file {}".format(rspec))
           sys.exit(1)
     
     def get_record_file(self, record):
           sys.exit(1)
     
     def get_record_file(self, record):
@@ -865,20 +903,20 @@ use this if you mean an authority instead""")
        if (os.path.isfile(file)):
           return file
        else:
        if (os.path.isfile(file)):
           return file
        else:
-          self.logger.critical("No such registry record file %s"%record)
+          self.logger.critical("No such registry record file {}".format(record))
           sys.exit(1)
 
 
     # helper function to analyze raw output
     # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
           sys.exit(1)
 
 
     # helper function to analyze raw output
     # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
-    def success (self, raw):
-        return_value=ReturnValue (raw)
-        output=ReturnValue.get_output(return_value)
+    def success(self, raw):
+        return_value = ReturnValue(raw)
+        output = ReturnValue.get_output(return_value)
         # means everything is fine
         if not output: 
             return 0
         # something went wrong
         # means everything is fine
         if not output: 
             return 0
         # something went wrong
-        print 'ERROR:',output
+        print('ERROR:', output)
         return 1
 
     #==========================================================================
         return 1
 
     #==========================================================================
@@ -887,43 +925,53 @@ use this if you mean an authority instead""")
     # Registry-related commands
     #==========================================================================
 
     # Registry-related commands
     #==========================================================================
 
-    @declare_command("","")
-    def config (self, options, args):
-        "Display contents of current config"
-        print "# From configuration file %s"%self.config_file
-        flags=[ ('sfi', [ ('registry','reg_url'),
-                          ('auth','authority'),
-                          ('user','user'),
-                          ('sm','sm_url'),
-                          ]),
+    @declare_command("", "")
+    def config(self, options, args):
+        """
+        Display contents of current config
+        """
+        if len(args) != 0:
+            self.print_help()
+            sys.exit(1)
+
+        print("# From configuration file {}".format(self.config_file))
+        flags = [ ('sfi', [ ('registry', 'reg_url'),
+                            ('auth', 'authority'),
+                            ('user', 'user'),
+                            ('sm', 'sm_url'),
+                        ]),
                 ]
         if options.myslice:
                 ]
         if options.myslice:
-            flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
+            flags.append( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
 
         for (section, tuples) in flags:
 
         for (section, tuples) in flags:
-            print "[%s]"%section
+            print("[{}]".format(section))
             try:
             try:
-                for (external_name, internal_name) in tuples:
-                    print "%-20s = %s"%(external_name,getattr(self,internal_name))
+                for external_name, internal_name in tuples:
+                    print("{:<20} = {}".format(external_name, getattr(self, internal_name)))
             except:
             except:
-                for name in tuples:
-                    varname="%s_%s"%(section.upper(),name.upper())
-                    value=getattr(self.config_instance,varname)
-                    print "%-20s = %s"%(name,value)
+                for external_name, internal_name in tuples:
+                    varname = "{}_{}".format(section.upper(), external_name.upper())
+                    value = getattr(self.config_instance, varname)
+                    print("{:<20} = {}".format(external_name, value))
         # xxx should analyze result
         return 0
 
         # xxx should analyze result
         return 0
 
-    @declare_command("","")
+    @declare_command("", "")
     def version(self, options, args):
         """
         display an SFA server version (GetVersion)
     def version(self, options, args):
         """
         display an SFA server version (GetVersion)
-    or version information about sfi itself
+        or version information about sfi itself
         """
         """
+        if len(args) != 0:
+            self.print_help()
+            sys.exit(1)
+        
         if options.version_local:
         if options.version_local:
-            version=version_core()
+            version = version_core()
         else:
             if options.registry_interface:
         else:
             if options.registry_interface:
-                server=self.registry()
+                server = self.registry()
             else:
                 server = self.sliceapi()
             result = server.GetVersion()
             else:
                 server = self.sliceapi()
             result = server.GetVersion()
@@ -936,14 +984,15 @@ use this if you mean an authority instead""")
         # xxx should analyze result
         return 0
 
         # xxx should analyze result
         return 0
 
-    @declare_command("authority","")
+    @declare_command("authority", "")
     def list(self, options, args):
         """
         list entries in named authority registry (List)
         """
     def list(self, options, args):
         """
         list entries in named authority registry (List)
         """
-        if len(args)!= 1:
+        if len(args) != 1:
             self.print_help()
             sys.exit(1)
             self.print_help()
             sys.exit(1)
+
         hrn = args[0]
         opts = {}
         if options.recursive:
         hrn = args[0]
         opts = {}
         if options.recursive:
@@ -954,76 +1003,80 @@ use this if you mean an authority instead""")
         try:
             list = self.registry().List(hrn, self.my_credential_string, options)
         except IndexError:
         try:
             list = self.registry().List(hrn, self.my_credential_string, options)
         except IndexError:
-            raise Exception, "Not enough parameters for the 'list' command"
+            raise Exception("Not enough parameters for the 'list' command")
 
         # filter on person, slice, site, node, etc.
         # This really should be in the self.filter_records funct def comment...
         list = filter_records(options.type, list)
 
         # filter on person, slice, site, node, etc.
         # This really should be in the self.filter_records funct def comment...
         list = filter_records(options.type, list)
-        terminal_render (list, options)
+        terminal_render(list, options)
         if options.file:
             save_records_to_file(options.file, list, options.fileformat)
         # xxx should analyze result
         return 0
     
         if options.file:
             save_records_to_file(options.file, list, options.fileformat)
         # xxx should analyze result
         return 0
     
-    @declare_command("name","")
+    @declare_command("name", "")
     def show(self, options, args):
         """
         show details about named registry record (Resolve)
         """
     def show(self, options, args):
         """
         show details about named registry record (Resolve)
         """
-        if len(args)!= 1:
+        if len(args) != 1:
             self.print_help()
             sys.exit(1)
             self.print_help()
             sys.exit(1)
+
         hrn = args[0]
         # explicitly require Resolve to run in details mode
         hrn = args[0]
         # explicitly require Resolve to run in details mode
-        resolve_options={}
-        if not options.no_details: resolve_options['details']=True
+        resolve_options = {}
+        if not options.no_details:
+            resolve_options['details'] = True
         record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
         record_dicts = filter_records(options.type, record_dicts)
         if not record_dicts:
         record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
         record_dicts = filter_records(options.type, record_dicts)
         if not record_dicts:
-            self.logger.error("No record of type %s"% options.type)
+            self.logger.error("No record of type {}".format(options.type))
             return
         # user has required to focus on some keys
         if options.keys:
             return
         # user has required to focus on some keys
         if options.keys:
-            def project (record):
-                projected={}
+            def project(record):
+                projected = {}
                 for key in options.keys:
                 for key in options.keys:
-                    try: projected[key]=record[key]
+                    try: projected[key] = record[key]
                     except: pass
                 return projected
                     except: pass
                 return projected
-            record_dicts = [ project (record) for record in record_dicts ]
+            record_dicts = [ project(record) for record in record_dicts ]
         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
         for record in records:
             if (options.format == "text"):      record.dump(sort=True)  
         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
         for record in records:
             if (options.format == "text"):      record.dump(sort=True)  
-            else:                               print record.save_as_xml() 
+            else:                               print(record.save_as_xml())
         if options.file:
             save_records_to_file(options.file, record_dicts, options.fileformat)
         # xxx should analyze result
         return 0
     
     # this historically was named 'add', it is now 'register' with an alias for legacy
         if options.file:
             save_records_to_file(options.file, record_dicts, options.fileformat)
         # xxx should analyze result
         return 0
     
     # this historically was named 'add', it is now 'register' with an alias for legacy
-    @declare_command("[xml-filename]","",['add'])
+    @declare_command("[xml-filename]", "", ['add'])
     def register(self, options, args):
     def register(self, options, args):
-        """create new record in registry (Register) 
-    from command line options (recommended) 
-    old-school method involving an xml file still supported"""
+        """
+        create new record in registry (Register) 
+        from command line options (recommended) 
+        old-school method involving an xml file still supported
+        """
+        if len(args) > 1:
+            self.print_help()
+            sys.exit(1)
 
         auth_cred = self.my_authority_credential_string()
         if options.show_credential:
             show_credentials(auth_cred)
         record_dict = {}
 
         auth_cred = self.my_authority_credential_string()
         if options.show_credential:
             show_credentials(auth_cred)
         record_dict = {}
-        if len(args) > 1:
-            self.print_help()
-            sys.exit(1)
-        if len(args)==1:
+        if len(args) == 1:
             try:
                 record_filepath = args[0]
                 rec_file = self.get_record_file(record_filepath)
             try:
                 record_filepath = args[0]
                 rec_file = self.get_record_file(record_filepath)
-                record_dict.update(load_record_from_file(rec_file).todict())
+                record_dict.update(load_record_from_file(rec_file).record_to_dict())
             except:
             except:
-                print "Cannot load record file %s"%record_filepath
+                print("Cannot load record file {}".format(record_filepath))
                 sys.exit(1)
         if options:
                 sys.exit(1)
         if options:
-            record_dict.update(load_record_from_opts(options).todict())
+            record_dict.update(load_record_from_opts(options).record_to_dict())
         # we should have a type by now
         if 'type' not in record_dict :
             self.print_help()
         # we should have a type by now
         if 'type' not in record_dict :
             self.print_help()
@@ -1041,20 +1094,26 @@ use this if you mean an authority instead""")
         # xxx should analyze result
         return 0
     
         # xxx should analyze result
         return 0
     
-    @declare_command("[xml-filename]","")
+    @declare_command("[xml-filename]", "")
     def update(self, options, args):
     def update(self, options, args):
-        """update record into registry (Update) 
-    from command line options (recommended) 
-    old-school method involving an xml file still supported"""
+        """
+        update record into registry (Update) 
+        from command line options (recommended) 
+        old-school method involving an xml file still supported
+        """
+        if len(args) > 1:
+            self.print_help()
+            sys.exit(1)
+
         record_dict = {}
         record_dict = {}
-        if len(args) > 0:
+        if len(args) == 1:
             record_filepath = args[0]
             rec_file = self.get_record_file(record_filepath)
             record_filepath = args[0]
             rec_file = self.get_record_file(record_filepath)
-            record_dict.update(load_record_from_file(rec_file).todict())
+            record_dict.update(load_record_from_file(rec_file).record_to_dict())
         if options:
         if options:
-            record_dict.update(load_record_from_opts(options).todict())
+            record_dict.update(load_record_from_opts(options).record_to_dict())
         # at the very least we need 'type' here
         # at the very least we need 'type' here
-        if 'type' not in record_dict:
+        if 'type' not in record_dict or record_dict['type'] is None:
             self.print_help()
             sys.exit(1)
 
             self.print_help()
             sys.exit(1)
 
@@ -1068,7 +1127,7 @@ use this if you mean an authority instead""")
         elif record_dict['type'] in ['slice']:
             try:
                 cred = self.slice_credential_string(record_dict['hrn'])
         elif record_dict['type'] in ['slice']:
             try:
                 cred = self.slice_credential_string(record_dict['hrn'])
-            except ServerException, e:
+            except ServerException as e:
                # XXX smbaker -- once we have better error return codes, update this
                # to do something better than a string compare
                if "Permission error" in e.args[0]:
                # XXX smbaker -- once we have better error return codes, update this
                # to do something better than a string compare
                if "Permission error" in e.args[0]:
@@ -1080,7 +1139,7 @@ use this if you mean an authority instead""")
         elif record_dict['type'] in ['node']:
             cred = self.my_authority_credential_string()
         else:
         elif record_dict['type'] in ['node']:
             cred = self.my_authority_credential_string()
         else:
-            raise "unknown record type" + record_dict['type']
+            raise Exception("unknown record type {}".format(record_dict['type']))
         if options.show_credential:
             show_credentials(cred)
         update = self.registry().Update(record_dict, cred)
         if options.show_credential:
             show_credentials(cred)
         update = self.registry().Update(record_dict, cred)
@@ -1089,13 +1148,16 @@ use this if you mean an authority instead""")
         # xxx should analyze result
         return 0
   
         # xxx should analyze result
         return 0
   
-    @declare_command("hrn","")
+    @declare_command("hrn", "")
     def remove(self, options, args):
     def remove(self, options, args):
-        "remove registry record by name (Remove)"
+        """
+        remove registry record by name (Remove)
+        """
         auth_cred = self.my_authority_credential_string()
         auth_cred = self.my_authority_credential_string()
-        if len(args)!=1:
+        if len(args) != 1:
             self.print_help()
             sys.exit(1)
             self.print_help()
             sys.exit(1)
+
         hrn = args[0]
         type = options.type 
         if type in ['all']:
         hrn = args[0]
         type = options.type 
         if type in ['all']:
@@ -1113,13 +1175,16 @@ use this if you mean an authority instead""")
     # ==================================================================
 
     # show rspec for named slice
     # ==================================================================
 
     # show rspec for named slice
-    @declare_command("","")
+    @declare_command("", "", ['discover'])
     def resources(self, options, args):
         """
         discover available resources (ListResources)
         """
     def resources(self, options, args):
         """
         discover available resources (ListResources)
         """
-        server = self.sliceapi()
+        if len(args) != 0:
+            self.print_help()
+            sys.exit(1)
 
 
+        server = self.sliceapi()
         # set creds
         creds = [self.my_credential]
         if options.delegate:
         # set creds
         creds = [self.my_credential]
         if options.delegate:
@@ -1143,17 +1208,10 @@ use this if you mean an authority instead""")
                 api_options['cached'] = False
             else:
                 api_options['cached'] = True
                 api_options['cached'] = False
             else:
                 api_options['cached'] = True
-        if options.rspec_version:
-            version_manager = VersionManager()
-            server_version = self.get_cached_server_version(server)
-            if 'sfa' in server_version:
-                # just request the version the client wants
-                api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
-            else:
-                api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
-        else:
-            api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
-        list_resources = server.ListResources (creds, api_options)
+        version_manager = VersionManager()
+        api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
+
+        list_resources = server.ListResources(creds, api_options)
         value = ReturnValue.get_value(list_resources)
         if self.options.raw:
             save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
         value = ReturnValue.get_value(list_resources)
         if self.options.raw:
             save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
@@ -1163,14 +1221,17 @@ use this if you mean an authority instead""")
             display_rspec(value, options.format)
         return self.success(list_resources)
 
             display_rspec(value, options.format)
         return self.success(list_resources)
 
-    @declare_command("slice_hrn","")
+    @declare_command("slice_hrn", "")
     def describe(self, options, args):
         """
         shows currently allocated/provisioned resources 
     def describe(self, options, args):
         """
         shows currently allocated/provisioned resources 
-    of the named slice or set of slivers (Describe) 
+        of the named slice or set of slivers (Describe) 
         """
         """
-        server = self.sliceapi()
+        if len(args) != 1:
+            self.print_help()
+            sys.exit(1)
 
 
+        server = self.sliceapi()
         # set creds
         creds = [self.slice_credential(args[0])]
         if options.delegate:
         # set creds
         creds = [self.slice_credential(args[0])]
         if options.delegate:
@@ -1205,15 +1266,18 @@ use this if you mean an authority instead""")
             save_rspec_to_file(value['geni_rspec'], options.file)
         if (self.options.raw is None) and (options.file is None):
             display_rspec(value['geni_rspec'], options.format)
             save_rspec_to_file(value['geni_rspec'], options.file)
         if (self.options.raw is None) and (options.file is None):
             display_rspec(value['geni_rspec'], options.format)
-        return self.success (describe)
+        return self.success(describe)
 
 
-    @declare_command("slice_hrn [<sliver_urn>...]","")
+    @declare_command("slice_hrn [<sliver_urn>...]", "")
     def delete(self, options, args):
         """
         de-allocate and de-provision all or named slivers of the named slice (Delete)
         """
     def delete(self, options, args):
         """
         de-allocate and de-provision all or named slivers of the named slice (Delete)
         """
-        server = self.sliceapi()
+        if len(args) == 0:
+            self.print_help()
+            sys.exit(1)
 
 
+        server = self.sliceapi()
         # slice urn
         slice_hrn = args[0]
         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
         # slice urn
         slice_hrn = args[0]
         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
@@ -1239,17 +1303,23 @@ use this if you mean an authority instead""")
         if self.options.raw:
             save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
         if self.options.raw:
             save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
-            print value
-        return self.success (delete)
+            print(value)
+        return self.success(delete)
 
 
-    @declare_command("slice_hrn rspec","")
+    @declare_command("slice_hrn rspec", "")
     def allocate(self, options, args):
         """
          allocate resources to the named slice (Allocate)
         """
     def allocate(self, options, args):
         """
          allocate resources to the named slice (Allocate)
         """
+        if len(args) != 2:
+            self.print_help()
+            sys.exit(1)
+
         server = self.sliceapi()
         server_version = self.get_cached_server_version(server)
         slice_hrn = args[0]
         server = self.sliceapi()
         server_version = self.get_cached_server_version(server)
         slice_hrn = args[0]
+        rspec_file = self.get_rspec_file(args[1])
+
         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
 
         # credentials
         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
 
         # credentials
@@ -1269,8 +1339,6 @@ use this if you mean an authority instead""")
             show_credentials(creds)
 
         # rspec
             show_credentials(creds)
 
         # rspec
-        rspec_file = self.get_rspec_file(args[1])
-        rspec = open(rspec_file).read()
         api_options = {}
         api_options ['call_id'] = unique_call_id()
         # users
         api_options = {}
         api_options ['call_id'] = unique_call_id()
         # users
@@ -1278,7 +1346,7 @@ use this if you mean an authority instead""")
         geni_users = []
         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
         remove_none_fields(slice_records[0])
         geni_users = []
         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
         remove_none_fields(slice_records[0])
-        if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
+        if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []:
             slice_record = slice_records[0]
             user_hrns = slice_record['reg-researchers']
             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
             slice_record = slice_records[0]
             user_hrns = slice_record['reg-researchers']
             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
@@ -1289,21 +1357,27 @@ use this if you mean an authority instead""")
         api_options['sfa_users'] = sfa_users
         api_options['geni_users'] = geni_users
 
         api_options['sfa_users'] = sfa_users
         api_options['geni_users'] = geni_users
 
-        allocate = server.Allocate(slice_urn, creds, rspec, api_options)
+        with open(rspec_file) as rspec:
+            rspec_xml = rspec.read()
+            allocate = server.Allocate(slice_urn, creds, rspec_xml, api_options)
         value = ReturnValue.get_value(allocate)
         if self.options.raw:
             save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
         if options.file is not None:
         value = ReturnValue.get_value(allocate)
         if self.options.raw:
             save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
         if options.file is not None:
-            save_rspec_to_file (value['geni_rspec'], options.file)
+            save_rspec_to_file(value['geni_rspec'], options.file)
         if (self.options.raw is None) and (options.file is None):
         if (self.options.raw is None) and (options.file is None):
-            print value
+            print(value)
         return self.success(allocate)
 
         return self.success(allocate)
 
-    @declare_command("slice_hrn [<sliver_urn>...]","")
+    @declare_command("slice_hrn [<sliver_urn>...]", "")
     def provision(self, options, args):
         """
         provision all or named already allocated slivers of the named slice (Provision)
         """
     def provision(self, options, args):
         """
         provision all or named already allocated slivers of the named slice (Provision)
         """
+        if len(args) == 0:
+            self.print_help()
+            sys.exit(1)
+
         server = self.sliceapi()
         server_version = self.get_cached_server_version(server)
         slice_hrn = args[0]
         server = self.sliceapi()
         server_version = self.get_cached_server_version(server)
         slice_hrn = args[0]
@@ -1346,7 +1420,7 @@ use this if you mean an authority instead""")
         #  }]
         users = []
         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
         #  }]
         users = []
         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
-        if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
+        if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []:
             slice_record = slice_records[0]
             user_hrns = slice_record['reg-researchers']
             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
             slice_record = slice_records[0]
             user_hrns = slice_record['reg-researchers']
             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
@@ -1359,18 +1433,21 @@ use this if you mean an authority instead""")
         if self.options.raw:
             save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
         if options.file is not None:
         if self.options.raw:
             save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
         if options.file is not None:
-            save_rspec_to_file (value['geni_rspec'], options.file)
+            save_rspec_to_file(value['geni_rspec'], options.file)
         if (self.options.raw is None) and (options.file is None):
         if (self.options.raw is None) and (options.file is None):
-            print value
+            print(value)
         return self.success(provision)
 
         return self.success(provision)
 
-    @declare_command("slice_hrn","")
+    @declare_command("slice_hrn", "")
     def status(self, options, args):
         """
         retrieve the status of the slivers belonging to the named slice (Status)
         """
     def status(self, options, args):
         """
         retrieve the status of the slivers belonging to the named slice (Status)
         """
-        server = self.sliceapi()
+        if len(args) != 1:
+            self.print_help()
+            sys.exit(1)
 
 
+        server = self.sliceapi()
         # slice urn
         slice_hrn = args[0]
         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
         # slice urn
         slice_hrn = args[0]
         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
@@ -1381,22 +1458,26 @@ use this if you mean an authority instead""")
 
         # options and call_id when supported
         api_options = {}
 
         # options and call_id when supported
         api_options = {}
-        api_options['call_id']=unique_call_id()
+        api_options['call_id'] = unique_call_id()
         if options.show_credential:
             show_credentials(creds)
         if options.show_credential:
             show_credentials(creds)
-        status = server.Status([slice_urn], creds, *self.ois(server,api_options))
+        status = server.Status([slice_urn], creds, *self.ois(server, api_options))
         value = ReturnValue.get_value(status)
         if self.options.raw:
             save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
         value = ReturnValue.get_value(status)
         if self.options.raw:
             save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
-            print value
-        return self.success (status)
+            print(value)
+        return self.success(status)
 
 
-    @declare_command("slice_hrn [<sliver_urn>...] action","")
+    @declare_command("slice_hrn [<sliver_urn>...] action", "")
     def action(self, options, args):
         """
         Perform the named operational action on all or named slivers of the named slice
         """
     def action(self, options, args):
         """
         Perform the named operational action on all or named slivers of the named slice
         """
+        if len(args) == 0:
+            self.print_help()
+            sys.exit(1)
+
         server = self.sliceapi()
         api_options = {}
         # slice urn
         server = self.sliceapi()
         api_options = {}
         # slice urn
@@ -1421,8 +1502,8 @@ use this if you mean an authority instead""")
         if self.options.raw:
             save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
         if self.options.raw:
             save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
-            print value
-        return self.success (perform_action)
+            print(value)
+        return self.success(perform_action)
 
     @declare_command("slice_hrn [<sliver_urn>...] time",
                      "\n".join(["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",
@@ -1432,12 +1513,13 @@ use this if you mean an authority instead""")
                                 "sfi renew onelab.ple.heartbeat +2m",]))
     def renew(self, options, args):
         """
                                 "sfi renew onelab.ple.heartbeat +2m",]))
     def renew(self, options, args):
         """
-        renew slice (Renew)
+        renew slice(Renew)
         """
         """
-        server = self.sliceapi()
         if len(args) < 2:
             self.print_help()
             sys.exit(1)
         if len(args) < 2:
             self.print_help()
             sys.exit(1)
+
+        server = self.sliceapi()
         slice_hrn = args[0]
         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
 
         slice_hrn = args[0]
         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
 
@@ -1455,24 +1537,28 @@ use this if you mean an authority instead""")
         creds = [slice_cred]
         # options and call_id when supported
         api_options = {}
         creds = [slice_cred]
         # options and call_id when supported
         api_options = {}
-        api_options['call_id']=unique_call_id()
+        api_options['call_id'] = unique_call_id()
         if options.alap:
         if options.alap:
-            api_options['geni_extend_alap']=True
+            api_options['geni_extend_alap'] = True
         if options.show_credential:
             show_credentials(creds)
         if options.show_credential:
             show_credentials(creds)
-        renew =  server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
+        renew =  server.Renew(sliver_urns, creds, input_time, *self.ois(server, api_options))
         value = ReturnValue.get_value(renew)
         if self.options.raw:
             save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
         value = ReturnValue.get_value(renew)
         if self.options.raw:
             save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
-            print value
+            print(value)
         return self.success(renew)
 
         return self.success(renew)
 
-    @declare_command("slice_hrn","")
+    @declare_command("slice_hrn", "")
     def shutdown(self, options, args):
         """
         shutdown named slice (Shutdown)
         """
     def shutdown(self, options, args):
         """
         shutdown named slice (Shutdown)
         """
+        if len(args) != 1:
+            self.print_help()
+            sys.exit(1)
+
         server = self.sliceapi()
         # slice urn
         slice_hrn = args[0]
         server = self.sliceapi()
         # slice urn
         slice_hrn = args[0]
@@ -1485,10 +1571,10 @@ use this if you mean an authority instead""")
         if self.options.raw:
             save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
         if self.options.raw:
             save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
-            print value
-        return self.success (shutdown)
+            print(value)
+        return self.success(shutdown)
 
 
-    @declare_command("[name]","")
+    @declare_command("[name]", "")
     def gid(self, options, args):
         """
         Create a GID (CreateGid)
     def gid(self, options, args):
         """
         Create a GID (CreateGid)
@@ -1496,20 +1582,21 @@ use this if you mean an authority instead""")
         if len(args) < 1:
             self.print_help()
             sys.exit(1)
         if len(args) < 1:
             self.print_help()
             sys.exit(1)
+
         target_hrn = args[0]
         my_gid_string = open(self.client_bootstrap.my_gid()).read() 
         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
         if options.file:
             filename = options.file
         else:
         target_hrn = args[0]
         my_gid_string = open(self.client_bootstrap.my_gid()).read() 
         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
         if options.file:
             filename = options.file
         else:
-            filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
-        self.logger.info("writing %s gid to %s" % (target_hrn, filename))
+            filename = os.sep.join([self.options.sfi_dir, '{}.gid'.format(target_hrn)])
+        self.logger.info("writing {} gid to {}".format(target_hrn, filename))
         GID(string=gid).save_to_file(filename)
         # xxx should analyze result
         return 0
          
     ####################
         GID(string=gid).save_to_file(filename)
         # xxx should analyze result
         return 0
          
     ####################
-    @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
+    @declare_command("to_hrn", """$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
 
   will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
   the set of credentials in the scope for this call would be
 
   will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
   the set of credentials in the scope for this call would be
@@ -1522,7 +1609,7 @@ use this if you mean an authority instead""")
       because of the two -s options
 
 """)
       because of the two -s options
 
 """)
-    def delegate (self, options, args):
+    def delegate(self, options, args):
         """
         (locally) create delegate credential for use by given hrn
     make sure to check for 'sfi myslice' instead if you plan
         """
         (locally) create delegate credential for use by given hrn
     make sure to check for 'sfi myslice' instead if you plan
@@ -1531,47 +1618,49 @@ use this if you mean an authority instead""")
         if len(args) != 1:
             self.print_help()
             sys.exit(1)
         if len(args) != 1:
             self.print_help()
             sys.exit(1)
+
         to_hrn = args[0]
         # support for several delegations in the same call
         # so first we gather the things to do
         to_hrn = args[0]
         # support for several delegations in the same call
         # so first we gather the things to do
-        tuples=[]
+        tuples = []
         for slice_hrn in options.delegate_slices:
         for slice_hrn in options.delegate_slices:
-            message="%s.slice"%slice_hrn
+            message = "{}.slice".format(slice_hrn)
             original = self.slice_credential_string(slice_hrn)
             original = self.slice_credential_string(slice_hrn)
-            tuples.append ( (message, original,) )
+            tuples.append( (message, original,) )
         if options.delegate_pi:
         if options.delegate_pi:
-            my_authority=self.authority
-            message="%s.pi"%my_authority
+            my_authority = self.authority
+            message = "{}.pi".format(my_authority)
             original = self.my_authority_credential_string()
             original = self.my_authority_credential_string()
-            tuples.append ( (message, original,) )
+            tuples.append( (message, original,) )
         for auth_hrn in options.delegate_auths:
         for auth_hrn in options.delegate_auths:
-            message="%s.auth"%auth_hrn
-            original=self.authority_credential_string(auth_hrn)
-            tuples.append ( (message, original, ) )
+            message = "{}.auth".format(auth_hrn)
+            original = self.authority_credential_string(auth_hrn)
+            tuples.append( (message, original, ) )
         # if nothing was specified at all at this point, let's assume -u
         # if nothing was specified at all at this point, let's assume -u
-        if not tuples: options.delegate_user=True
+        if not tuples:
+            options.delegate_user = True
         # this user cred
         if options.delegate_user:
         # this user cred
         if options.delegate_user:
-            message="%s.user"%self.user
+            message = "{}.user".format(self.user)
             original = self.my_credential_string
             original = self.my_credential_string
-            tuples.append ( (message, original, ) )
+            tuples.append( (message, original, ) )
 
         # default type for beneficial is user unless -A
 
         # default type for beneficial is user unless -A
-        if options.delegate_to_authority:       to_type='authority'
-        else:                                   to_type='user'
+        to_type = 'authority' if options.delegate_to_authority else 'user'
 
         # let's now handle all this
         # it's all in the filenaming scheme
 
         # let's now handle all this
         # it's all in the filenaming scheme
-        for (message,original) in tuples:
+        for (message, original) in tuples:
             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
-            delegated_credential = Credential (string=delegated_string)
-            filename = os.path.join ( self.options.sfi_dir,
-                                      "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
+            delegated_credential = Credential(string=delegated_string)
+            filename = os.path.join(self.options.sfi_dir,
+                                    "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
             delegated_credential.save_to_file(filename, save_parents=True)
             delegated_credential.save_to_file(filename, save_parents=True)
-            self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
+            self.logger.info("delegated credential for {} to {} and wrote to {}"
+                             .format(message, to_hrn, filename))
     
     ####################
     
     ####################
-    @declare_command("","""$ less +/myslice sfi_config
+    @declare_command("", """$ less +/myslice sfi_config
 [myslice]
 backend  = http://manifold.pl.sophia.inria.fr:7080
 # the HRN that myslice uses, so that we are delegating to
 [myslice]
 backend  = http://manifold.pl.sophia.inria.fr:7080
 # the HRN that myslice uses, so that we are delegating to
@@ -1596,17 +1685,18 @@ $ sfi m -b http://mymanifold.foo.com:7080/
   and uses a custom backend for this one call
 """
 ) # declare_command
   and uses a custom backend for this one call
 """
 ) # declare_command
-    def myslice (self, options, args):
+    def myslice(self, options, args):
 
         """ This helper is for refreshing your credentials at myslice; it will
     * compute all the slices that you currently have credentials on
     * refresh all your credentials (you as a user and pi, your slices)
     * upload them to the manifold backend server
     for last phase, sfi_config is read to look for the [myslice] section, 
 
         """ This helper is for refreshing your credentials at myslice; it will
     * compute all the slices that you currently have credentials on
     * refresh all your credentials (you as a user and pi, your slices)
     * upload them to the manifold backend server
     for last phase, sfi_config is read to look for the [myslice] section, 
-    and namely the 'backend', 'delegate' and 'user' settings"""
+    and namely the 'backend', 'delegate' and 'user' settings
+        """
 
         ##########
 
         ##########
-        if len(args)>0:
+        if len(args) > 0:
             self.print_help()
             sys.exit(1)
         # enable info by default
             self.print_help()
             sys.exit(1)
         # enable info by default
@@ -1616,107 +1706,119 @@ $ sfi m -b http://mymanifold.foo.com:7080/
         self.client_bootstrap.my_pkcs12()
 
         # (a) rain check for sufficient config in sfi_config
         self.client_bootstrap.my_pkcs12()
 
         # (a) rain check for sufficient config in sfi_config
-        myslice_dict={}
-        myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
+        myslice_dict = {}
+        myslice_keys = [ 'backend', 'delegate', 'platform', 'username']
         for key in myslice_keys:
         for key in myslice_keys:
-            value=None
+            value = None
             # oct 2013 - I'm finding myself juggling with config files
             # so a couple of command-line options can now override config
             # oct 2013 - I'm finding myself juggling with config files
             # so a couple of command-line options can now override config
-            if hasattr(options,key) and getattr(options,key) is not None:
-                value=getattr(options,key)
+            if hasattr(options, key) and getattr(options, key) is not None:
+                value = getattr(options, key)
             else:
             else:
-                full_key="MYSLICE_" + key.upper()
-                value=getattr(self.config_instance,full_key,None)
-            if value:   myslice_dict[key]=value
-            else:       print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
+                full_key = "MYSLICE_" + key.upper()
+                value = getattr(self.config_instance, full_key, None)
+            if value:
+                myslice_dict[key] = value
+            else:
+                print("Unsufficient config, missing key {} in [myslice] section of sfi_config"
+                      .format(key))
         if len(myslice_dict) != len(myslice_keys):
             sys.exit(1)
 
         # (b) figure whether we are PI for the authority where we belong
         if len(myslice_dict) != len(myslice_keys):
             sys.exit(1)
 
         # (b) figure whether we are PI for the authority where we belong
-        self.logger.info("Resolving our own id %s"%self.user)
-        my_records=self.registry().Resolve(self.user,self.my_credential_string)
-        if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
-        my_record=my_records[0]
+        self.logger.info("Resolving our own id {}".format(self.user))
+        my_records = self.registry().Resolve(self.user, self.my_credential_string)
+        if len(my_records) != 1:
+            print("Cannot Resolve {} -- exiting".format(self.user))
+            sys.exit(1)
+        my_record = my_records[0]
         my_auths_all = my_record['reg-pi-authorities']
         my_auths_all = my_record['reg-pi-authorities']
-        self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
-        self.logger.debug("They are %s"%(my_auths_all))
+        self.logger.info("Found {} authorities that we are PI for".format(len(my_auths_all)))
+        self.logger.debug("They are {}".format(my_auths_all))
         
         my_auths = my_auths_all
         if options.delegate_auths:
             my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
         
         my_auths = my_auths_all
         if options.delegate_auths:
             my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
-            self.logger.debug("Restricted to user-provided auths"%(my_auths))
+            self.logger.debug("Restricted to user-provided auths {}".format(my_auths))
 
         # (c) get the set of slices that we are in
 
         # (c) get the set of slices that we are in
-        my_slices_all=my_record['reg-slices']
-        self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
-        self.logger.debug("They are: %s"%(my_slices_all))
+        my_slices_all = my_record['reg-slices']
+        self.logger.info("Found {} slices that we are member of".format(len(my_slices_all)))
+        self.logger.debug("They are: {}".format(my_slices_all))
  
         my_slices = my_slices_all
         # if user provided slices, deal only with these - if they are found
         if options.delegate_slices:
             my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
  
         my_slices = my_slices_all
         # if user provided slices, deal only with these - if they are found
         if options.delegate_slices:
             my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
-            self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
+            self.logger.debug("Restricted to user-provided slices: {}".format(my_slices))
 
         # (d) make sure we have *valid* credentials for all these
 
         # (d) make sure we have *valid* credentials for all these
-        hrn_credentials=[]
-        hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
+        hrn_credentials = []
+        hrn_credentials.append( (self.user, 'user', self.my_credential_string,) )
         for auth_hrn in my_auths:
         for auth_hrn in my_auths:
-            hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
+            hrn_credentials.append( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
         for slice_hrn in my_slices:
         for slice_hrn in my_slices:
-            hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
+            try:
+                hrn_credentials.append( (slice_hrn, 'slice', self.slice_credential_string(slice_hrn),) )
+            except:
+                print("WARNING: could not get slice credential for slice {}"
+                      .format(slice_hrn))
 
         # (e) check for the delegated version of these
         # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever 
         # switch to myslice using an authority instead of a user
 
         # (e) check for the delegated version of these
         # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever 
         # switch to myslice using an authority instead of a user
-        delegatee_type='user'
-        delegatee_hrn=myslice_dict['delegate']
+        delegatee_type = 'user'
+        delegatee_hrn = myslice_dict['delegate']
         hrn_delegated_credentials = []
         for (hrn, htype, credential) in hrn_credentials:
         hrn_delegated_credentials = []
         for (hrn, htype, credential) in hrn_credentials:
-            delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
+            delegated_credential = self.client_bootstrap.delegate_credential_string(credential, delegatee_hrn, delegatee_type)
             # save these so user can monitor what she's uploaded
             # save these so user can monitor what she's uploaded
-            filename = os.path.join ( self.options.sfi_dir,
-                                      "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
-            with file(filename,'w') as f:
+            filename = os.path.join( self.options.sfi_dir,
+                                      "{}.{}_for_{}.{}.cred"\
+                                      .format(hrn, htype, delegatee_hrn, delegatee_type))
+            with open(filename, 'w') as f:
                 f.write(delegated_credential)
                 f.write(delegated_credential)
-            self.logger.debug("(Over)wrote %s"%filename)
-            hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
+            self.logger.debug("(Over)wrote {}".format(filename))
+            hrn_delegated_credentials.append((hrn, htype, delegated_credential, filename, ))
 
         # (f) and finally upload them to manifold server
         # xxx todo add an option so the password can be set on the command line
         # (but *NOT* in the config file) so other apps can leverage this
 
         # (f) and finally upload them to manifold server
         # xxx todo add an option so the password can be set on the command line
         # (but *NOT* in the config file) so other apps can leverage this
-        self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
-        uploader = ManifoldUploader (logger=self.logger,
+        self.logger.info("Uploading on backend at {}".format(myslice_dict['backend']))
+        uploader = ManifoldUploader(logger=self.logger,
                                      url=myslice_dict['backend'],
                                      platform=myslice_dict['platform'],
                                      username=myslice_dict['username'],
                                      password=options.password)
         uploader.prompt_all()
                                      url=myslice_dict['backend'],
                                      platform=myslice_dict['platform'],
                                      username=myslice_dict['username'],
                                      password=options.password)
         uploader.prompt_all()
-        (count_all,count_success)=(0,0)
-        for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
+        (count_all, count_success) = (0, 0)
+        for (hrn, htype, delegated_credential, filename) in hrn_delegated_credentials:
             # inspect
             # inspect
-            inspect=Credential(string=delegated_credential)
-            expire_datetime=inspect.get_expiration()
-            message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
-            if uploader.upload(delegated_credential,message=message):
-                count_success+=1
-            count_all+=1
-        self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
+            inspect = Credential(string=delegated_credential)
+            expire_datetime = inspect.get_expiration()
+            message = "{} ({}) [exp:{}]".format(hrn, htype, expire_datetime)
+            if uploader.upload(delegated_credential, message=message):
+                count_success += 1
+            count_all += 1
+        self.logger.info("Successfully uploaded {}/{} credentials"
+                         .format(count_success, count_all))
 
         # at first I thought we would want to save these,
         # like 'sfi delegate does' but on second thought
         # it is probably not helpful as people would not
         # need to run 'sfi delegate' at all anymore
 
         # at first I thought we would want to save these,
         # like 'sfi delegate does' but on second thought
         # it is probably not helpful as people would not
         # need to run 'sfi delegate' at all anymore
-        if count_success != count_all: sys.exit(1)
+        if count_success != count_all:
+            sys.exit(1)
         # xxx should analyze result
         return 0
 
         # xxx should analyze result
         return 0
 
-    @declare_command("cred","")
+    @declare_command("cred", "")
     def trusted(self, options, args):
         """
         return the trusted certs at this interface (get_trusted_certs)
         """ 
         if options.registry_interface:
     def trusted(self, options, args):
         """
         return the trusted certs at this interface (get_trusted_certs)
         """ 
         if options.registry_interface:
-            server=self.registry()
+            server = self.registry()
         else:
             server = self.sliceapi()
         cred = self.my_authority_credential_string()
         else:
             server = self.sliceapi()
         cred = self.my_authority_credential_string()
@@ -1725,11 +1827,39 @@ $ sfi m -b http://mymanifold.foo.com:7080/
             trusted_certs = ReturnValue.get_value(trusted_certs)
 
         for trusted_cert in trusted_certs:
             trusted_certs = ReturnValue.get_value(trusted_certs)
 
         for trusted_cert in trusted_certs:
-            print "\n===========================================================\n"
+            print("\n===========================================================\n")
             gid = GID(string=trusted_cert)
             gid.dump()
             cert = Certificate(string=trusted_cert)
             gid = GID(string=trusted_cert)
             gid.dump()
             cert = Certificate(string=trusted_cert)
-            self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
-            print "Certificate:\n%s\n\n"%trusted_cert
+            self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
+            print("Certificate:\n{}\n\n".format(trusted_cert))
         # xxx should analyze result
         return 0
         # xxx should analyze result
         return 0
+
+    @declare_command("", "")
+    def introspect(self, options, args):
+        """
+        If remote server supports XML-RPC instrospection API, allows
+        to list supported methods
+        """
+        if options.registry_interface:
+            server = self.registry()
+        else:
+            server = self.sliceapi()
+        results = server.serverproxy.system.listMethods()
+        # at first sight a list here means it's fine,
+        # and a dict suggests an error (no support for introspection?)
+        if isinstance(results, list):
+            results = [ name for name in results if 'system.' not in name ]
+            results.sort()
+            print("== methods supported at {}".format(server.url))
+            if 'Discover' in results:
+                print("== has support for 'Discover' - most likely a v3")
+            else:
+                print("== has no support for 'Discover' - most likely a v2")
+            for name in results:
+                print(name)
+        else:
+            print("Got return of type {}, expected a list".format(type(results)))
+            print("This suggests the remote end does not support introspection")
+            print(results)