open() instead of file()
[sfa.git] / sfa / client / sfi.py
index 5349d43..95acf18 100644 (file)
@@ -3,6 +3,8 @@
 # this module is also used in sfascan
 #
 
+from __future__ import print_function
+
 import sys
 sys.path.append('.')
 
@@ -15,7 +17,6 @@ import pickle
 import json
 import shutil
 from lxml import etree
-from StringIO import StringIO
 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.py23 import StringIO
 
 from sfa.storage.record import Record
 
@@ -67,12 +69,12 @@ def display_rspec(rspec, format='rspec'):
     else:
         result = rspec
 
-    print result
+    print(result)
     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'''
@@ -84,7 +86,7 @@ def display_record(record, dump=False):
         record.dump(sort=True)
     else:
         info = record.getdict()
-        print "{} ({})".format(info['hrn'], info['type'])
+        print("{} ({})".format(info['hrn'], info['type']))
     return
 
 
@@ -96,9 +98,9 @@ def filter_records(type, records):
     return filtered_records
 
 
-def credential_printable (cred):
+def credential_printable(cred):
     credential = Credential(cred=cred)
-    result=""
+    result = ""
     result += credential.pretty_cred()
     result += "\n"
     rights = credential.get_privileges()
@@ -107,10 +109,10 @@ def credential_printable (cred):
     result += "rights={}\n".format(rights)
     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:
-        print "Using Credential {}".format(credential_printable(cred))
+        print("Using Credential {}".format(credential_printable(cred)))
 
 ########## save methods
 
@@ -121,7 +123,7 @@ def save_raw_to_file(var, filename, format='text', banner=None):
     else:
         with open(filename, w) as fileobj:
             _save_raw_to_file(var, fileobj, format, banner)
-        print "(Over)wrote {}".format(filename)
+        print("(Over)wrote {}".format(filename))
 
 def _save_raw_to_file(var, f, format, banner):
     if format == "text":
@@ -134,7 +136,7 @@ def _save_raw_to_file(var, f, format, banner):
         f.write(json.dumps(var))   # python 2.6
     else:
         # this should never happen
-        print "unknown output format", format
+        print("unknown output format", format)
 
 ### 
 def save_rspec_to_file(rspec, filename):
@@ -142,14 +144,14 @@ def save_rspec_to_file(rspec, filename):
         filename = filename + ".rspec"
     with open(filename, 'w') as f:
         f.write("{}".format(rspec))
-    print "(Over)wrote {}".format(filename)
+    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:
+    with codecs.open(filename, encoding='utf-8', mode="w") as f:
         f.write(xml)
-    print "(Over)wrote {}".format(filename)
+    print("(Over)wrote {}".format(filename))
 
 def save_records_to_file(filename, record_dicts, format="xml"):
     if format == "xml":
@@ -162,26 +164,26 @@ def save_records_to_file(filename, record_dicts, format="xml"):
                 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)
+            print("(Over)wrote {}".format(filename))
 
     elif format == "hrnlist":
         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)
+            print("(Over)wrote {}".format(filename))
 
     else:
         # this should never happen
-        print "unknown output format", format
+        print("unknown output format", format)
 
 # 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
-def normalize_type (type):
+def normalize_type(type):
     if type.startswith('au'):
         return 'authority'
     elif type.startswith('us'):
@@ -195,7 +197,7 @@ def normalize_type (type):
     elif type.startswith('al'):
         return 'all'
     else:
-        print 'unknown type {} - should start with one of au|us|sl|no|ag|al'.format(type)
+        print('unknown type {} - should start with one of au|us|sl|no|ag|al'.format(type))
         return None
 
 def load_record_from_opts(options):
@@ -213,8 +215,8 @@ def load_record_from_opts(options):
             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
@@ -257,27 +259,27 @@ from functools import wraps
 commands_list=[]
 commands_dict={}
 
-def declare_command (args_string, example,aliases=None):
+def declare_command(args_string, example, aliases=None):
     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
-        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)
-        def new_method (*args, **kwds): return m(*args, **kwds)
+        def new_method(*args, **kwds): return m(*args, **kwds)
         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]
 
 ##########
@@ -288,7 +290,7 @@ class Sfi:
     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:
@@ -299,11 +301,13 @@ class Sfi:
     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 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
@@ -312,55 +316,55 @@ 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
-        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
-    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:
-            print format3%("command", "cmd_args", "description")
-            print line
+            print(format3%("command", "cmd_args", "description"))
+            print(line)
         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:
-                print "Cannot find info on command %s - skipped"%command
+                print("Cannot find info on command %s - skipped"%command)
                 continue
             if verbose:
-                print line
+                print(line)
             if command==canonical:
                 doc = doc.replace("\n", "\n" + format3offset * ' ')
-                print format3 % (command,args_string,doc)
+                print(format3 % (command, args_string, doc))
                 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
-    def print_help (self):
-        print "==================== Generic sfi usage"
+    def print_help(self):
+        print("==================== Generic sfi usage")
         self.sfi_parser.print_help()
         (doc, _, example, canonical) = commands_dict[self.command]
         if canonical != self.command:
-            print "\n==================== NOTE: {} is an alias for genuine {}"\
-                .format(self.command, canonical)
+            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)
+        print("\n==================== Purpose of {}".format(self.command))
+        print(doc)
+        print("\n==================== Specific usage for {}".format(self.command))
         self.command_parser.print_help()
         if example:
-            print "\n==================== {} example(s)".format(self.command)
-            print example
+            print("\n==================== {} example(s)".format(self.command))
+            print(example)
 
     def create_parser_global(self):
         # Generate command line parser
@@ -413,12 +417,12 @@ class Sfi:
             sys.exit(2)
 
         # retrieve args_string
-        (_, args_string, __,canonical) = commands_dict[command]
+        (_, args_string, __, canonical) = commands_dict[command]
 
         parser = OptionParser(add_help_option=False,
                               usage="sfi [sfi_options] {} [cmd_options] {}"\
                               .format(command, args_string))
-        parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
+        parser.add_option("-h","--help",dest='help',action='store_true',default=False,
                            help="Summary of one command usage")
 
         if canonical in ("config"):
@@ -430,7 +434,7 @@ class Sfi:
                               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")
@@ -449,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)
-            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")
 
@@ -551,7 +555,7 @@ use this if you mean an authority instead""")
         (doc, args_string, example, canonical) = commands_dict[command]
         method=getattr(self, canonical, None)
         if not method:
-            print "sfi: unknown command {}".format(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':
@@ -575,14 +579,14 @@ use this if you mean an authority instead""")
             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
-        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:
@@ -596,8 +600,8 @@ use this if you mean an authority instead""")
             if not command_options.type:
                 sys.exit(1)
         
-        self.read_config () 
-        self.bootstrap ()
+        self.read_config() 
+        self.bootstrap()
         self.logger.debug("Command={}".format(self.command))
 
         try:
@@ -605,17 +609,17 @@ use this if you mean an authority instead""")
         except SystemExit:
             return 1
         except:
-            self.logger.log_exc ("sfi command {} failed".format(command))
+            self.logger.log_exc("sfi command {} failed".format(command))
             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):
-                config = Config (config_file)
+                config = Config(config_file)
             else:
                 # try upgrading from shell config format
                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
@@ -637,13 +641,13 @@ use this if you mean an authority instead""")
         except:
             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")
-            if self.options.verbose==0:
+            if self.options.verbose == 0:
                 self.logger.info("Re-run with -v for more details")
             else:
                 self.logger.log_exc("Could not read config file {}".format(config_file))
             sys.exit(1)
      
-        self.config_instance=config
+        self.config_instance = config
         errors = 0
         # Set SliceMgr URL
         if (self.options.sm is not None):
@@ -681,7 +685,7 @@ use this if you mean an authority instead""")
            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file))
            errors += 1 
 
-        self.config_file=config_file
+        self.config_file = config_file
         if errors:
            sys.exit(1)
 
@@ -699,25 +703,25 @@ use this if you mean an authority instead""")
     #
     
     # init self-signed cert, user credentials and gid
-    def bootstrap (self):
+    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,
+        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:
-            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()):
-                self.logger.info ("private key not found, trying legacy name")
+                self.logger.info("private key not found, trying legacy name")
                 try:
-                    legacy_private_key = os.path.join (self.options.sfi_dir, "{}.pkey"
+                    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)
+                    client_bootstrap.init_private_key_if_missing(legacy_private_key)
                     self.logger.info("Copied private key from legacy location {}"
                                      .format(legacy_private_key))
                 except:
@@ -728,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()
-        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_gid = client_bootstrap.my_gid ()
+        self.my_gid = client_bootstrap.my_gid()
         self.client_bootstrap = client_bootstrap
 
 
@@ -740,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)
-        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):
-        return self.client_bootstrap.authority_credential_string (auth_hrn)
+        return self.client_bootstrap.authority_credential_string(auth_hrn)
 
     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',
@@ -770,7 +774,7 @@ use this if you mean an authority instead""")
         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)
@@ -779,20 +783,20 @@ use this if you mean an authority instead""")
     # Management of the servers
     # 
 
-    def registry (self):
+    def registry(self):
         # cache the result
-        if not hasattr (self, 'registry_proxy'):
+        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
 
-    def sliceapi (self):
+    def sliceapi(self):
         # 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 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)
@@ -801,7 +805,7 @@ use this if you mean an authority instead""")
                     self.logger.warning("No such component:{}".format(opts.component))
                 record = records[0]
                 cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT)
-                self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
+                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://'):
@@ -816,7 +820,7 @@ use this if you mean an authority instead""")
         # 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)
@@ -829,9 +833,9 @@ use this if you mean an authority instead""")
 
         if not version: 
             result = server.GetVersion()
-            version= ReturnValue.get_value(result)
+            version = ReturnValue.get_value(result)
             # cache version for 20 minutes
-            cache.add(cache_key, version, ttl= 60*20)
+            cache.add(cache_key, version, ttl=60*20)
             self.logger.info("Updating cache file {}".format(cache_file))
             cache.save_to_file(cache_file)
 
@@ -863,18 +867,18 @@ use this if you mean an authority instead""")
         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]
-        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
-    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 []
@@ -905,14 +909,14 @@ use this if you mean an authority instead""")
 
     # 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
-        print 'ERROR:',output
+        print('ERROR:', output)
         return 1
 
     #==========================================================================
@@ -921,44 +925,53 @@ use this if you mean an authority instead""")
     # Registry-related commands
     #==========================================================================
 
-    @declare_command("","")
-    def config (self, options, args):
-        "Display contents of current config"
-        print "# From configuration file {}".format(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:
-            flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
+            flags.append( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
 
         for (section, tuples) in flags:
-            print "[{}]".format(section)
+            print("[{}]".format(section))
             try:
                 for external_name, internal_name in tuples:
-                    print "{:<20} = {}".format(external_name, getattr(self, internal_name))
+                    print("{:<20} = {}".format(external_name, getattr(self, internal_name)))
             except:
-                print 'failed'
                 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)
+                    value = getattr(self.config_instance, varname)
+                    print("{:<20} = {}".format(external_name, value))
         # xxx should analyze result
         return 0
 
-    @declare_command("","")
+    @declare_command("", "")
     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:
-            version=version_core()
+            version = version_core()
         else:
             if options.registry_interface:
-                server=self.registry()
+                server = self.registry()
             else:
                 server = self.sliceapi()
             result = server.GetVersion()
@@ -971,14 +984,15 @@ use this if you mean an authority instead""")
         # xxx should analyze result
         return 0
 
-    @declare_command("authority","")
+    @declare_command("authority", "")
     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)
+
         hrn = args[0]
         opts = {}
         if options.recursive:
@@ -989,29 +1003,31 @@ use this if you mean an authority instead""")
         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)
-        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
     
-    @declare_command("name","")
+    @declare_command("name", "")
     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)
+
         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:
@@ -1019,43 +1035,45 @@ use this if you mean an authority instead""")
             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:
-                    try: projected[key]=record[key]
+                    try: projected[key] = record[key]
                     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)  
-            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
-    @declare_command("[xml-filename]","",['add'])
+    @declare_command("[xml-filename]", "", ['add'])
     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 = {}
-        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)
                 record_dict.update(load_record_from_file(rec_file).record_to_dict())
             except:
-                print "Cannot load record file {}".format(record_filepath)
+                print("Cannot load record file {}".format(record_filepath))
                 sys.exit(1)
         if options:
             record_dict.update(load_record_from_opts(options).record_to_dict())
@@ -1076,13 +1094,19 @@ use this if you mean an authority instead""")
         # xxx should analyze result
         return 0
     
-    @declare_command("[xml-filename]","")
+    @declare_command("[xml-filename]", "")
     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 = {}
-        if len(args) > 0:
+        if len(args) == 1:
             record_filepath = args[0]
             rec_file = self.get_record_file(record_filepath)
             record_dict.update(load_record_from_file(rec_file).record_to_dict())
@@ -1103,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'])
-            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]:
@@ -1124,13 +1148,16 @@ use this if you mean an authority instead""")
         # xxx should analyze result
         return 0
   
-    @declare_command("hrn","")
+    @declare_command("hrn", "")
     def remove(self, options, args):
-        "remove registry record by name (Remove)"
+        """
+        remove registry record by name (Remove)
+        """
         auth_cred = self.my_authority_credential_string()
-        if len(args)!=1:
+        if len(args) != 1:
             self.print_help()
             sys.exit(1)
+
         hrn = args[0]
         type = options.type 
         if type in ['all']:
@@ -1148,13 +1175,16 @@ use this if you mean an authority instead""")
     # ==================================================================
 
     # show rspec for named slice
-    @declare_command("","",['discover'])
+    @declare_command("", "", ['discover'])
     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:
@@ -1178,17 +1208,10 @@ use this if you mean an authority instead""")
                 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)
@@ -1198,14 +1221,17 @@ use this if you mean an authority instead""")
             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 
-    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:
@@ -1240,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)
-        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)
         """
-        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') 
@@ -1274,19 +1303,20 @@ 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:
-            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)
         """
-        server = self.sliceapi()
-        server_version = self.get_cached_server_version(server)
         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]
         rspec_file = self.get_rspec_file(args[1])
 
@@ -1316,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])
-        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]
@@ -1334,16 +1364,20 @@ use this if you mean an authority instead""")
         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):
-            print value
+            print(value)
         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)
         """
+        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]
@@ -1386,7 +1420,7 @@ use this if you mean an authority instead""")
         #  }]
         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]
@@ -1399,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:
-            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):
-            print value
+            print(value)
         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)
         """
-        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') 
@@ -1421,22 +1458,26 @@ use this if you mean an authority instead""")
 
         # 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)
-        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:
-            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
         """
+        if len(args) == 0:
+            self.print_help()
+            sys.exit(1)
+
         server = self.sliceapi()
         api_options = {}
         # slice urn
@@ -1461,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:
-            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",
@@ -1472,12 +1513,13 @@ use this if you mean an authority instead""")
                                 "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)
+
+        server = self.sliceapi()
         slice_hrn = args[0]
         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
 
@@ -1495,24 +1537,28 @@ use this if you mean an authority instead""")
         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:
-            api_options['geni_extend_alap']=True
+            api_options['geni_extend_alap'] = True
         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:
-            print value
+            print(value)
         return self.success(renew)
 
-    @declare_command("slice_hrn","")
+    @declare_command("slice_hrn", "")
     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]
@@ -1525,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:
-            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)
@@ -1536,6 +1582,7 @@ use this if you mean an authority instead""")
         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)
@@ -1549,7 +1596,7 @@ use this if you mean an authority instead""")
         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
@@ -1562,7 +1609,7 @@ use this if you mean an authority instead""")
       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
@@ -1571,6 +1618,7 @@ use this if you mean an authority instead""")
         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
@@ -1578,33 +1626,33 @@ use this if you mean an authority instead""")
         for slice_hrn in options.delegate_slices:
             message = "{}.slice".format(slice_hrn)
             original = self.slice_credential_string(slice_hrn)
-            tuples.append ( (message, original,) )
+            tuples.append( (message, original,) )
         if options.delegate_pi:
-            my_authority=self.authority
+            my_authority = self.authority
             message = "{}.pi".format(my_authority)
             original = self.my_authority_credential_string()
-            tuples.append ( (message, original,) )
+            tuples.append( (message, original,) )
         for auth_hrn in options.delegate_auths:
             message = "{}.auth".format(auth_hrn)
             original = self.authority_credential_string(auth_hrn)
-            tuples.append ( (message, original, ) )
+            tuples.append( (message, original, ) )
         # 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:
             message = "{}.user".format(self.user)
             original = self.my_credential_string
-            tuples.append ( (message, original, ) )
+            tuples.append( (message, original, ) )
 
         # 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
-        for (message,original) in tuples:
+        for (message, original) in tuples:
             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
-            delegated_credential = Credential (string=delegated_string)
+            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)
@@ -1612,7 +1660,7 @@ use this if you mean an authority instead""")
                              .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
@@ -1637,17 +1685,18 @@ $ sfi m -b http://mymanifold.foo.com:7080/
   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, 
-    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
@@ -1657,30 +1706,30 @@ $ sfi m -b http://mymanifold.foo.com:7080/
         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:
-            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
-            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:
-                full_key="MYSLICE_" + key.upper()
-                value=getattr(self.config_instance,full_key,None)
+                full_key = "MYSLICE_" + key.upper()
+                value = getattr(self.config_instance, full_key, None)
             if value:
-                myslice_dict[key]=value
+                myslice_dict[key] = value
             else:
-                print "Unsufficient config, missing key {} in [myslice] section of sfi_config"\
-                    .format(key)
+                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
         self.logger.info("Resolving our own id {}".format(self.user))
-        my_records=self.registry().Resolve(self.user,self.my_credential_string)
+        my_records = self.registry().Resolve(self.user, self.my_credential_string)
         if len(my_records) != 1:
-            print "Cannot Resolve {} -- exiting".format(self.user)
+            print("Cannot Resolve {} -- exiting".format(self.user))
             sys.exit(1)
         my_record = my_records[0]
         my_auths_all = my_record['reg-pi-authorities']
@@ -1693,7 +1742,7 @@ $ sfi m -b http://mymanifold.foo.com:7080/
             self.logger.debug("Restricted to user-provided auths {}".format(my_auths))
 
         # (c) get the set of slices that we are in
-        my_slices_all=my_record['reg-slices']
+        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))
  
@@ -1704,49 +1753,53 @@ $ sfi m -b http://mymanifold.foo.com:7080/
             self.logger.debug("Restricted to user-provided slices: {}".format(my_slices))
 
         # (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:
-            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:
-            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
-        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:
-            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
-            filename = os.path.join ( self.options.sfi_dir,
+            filename = os.path.join( self.options.sfi_dir,
                                       "{}.{}_for_{}.{}.cred"\
                                       .format(hrn, htype, delegatee_hrn, delegatee_type))
-            with file(filename,'w') as f:
+            with open(filename, 'w') as f:
                 f.write(delegated_credential)
             self.logger.debug("(Over)wrote {}".format(filename))
-            hrn_delegated_credentials.append ((hrn, htype, delegated_credential, 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
         self.logger.info("Uploading on backend at {}".format(myslice_dict['backend']))
-        uploader = ManifoldUploader (logger=self.logger,
+        uploader = ManifoldUploader(logger=self.logger,
                                      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=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
+            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))
 
@@ -1754,17 +1807,18 @@ $ sfi m -b http://mymanifold.foo.com:7080/
         # 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
 
-    @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:
-            server=self.registry()
+            server = self.registry()
         else:
             server = self.sliceapi()
         cred = self.my_authority_credential_string()
@@ -1773,11 +1827,39 @@ $ sfi m -b http://mymanifold.foo.com:7080/
             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)
             self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
-            print "Certificate:\n{}\n\n".format(trusted_cert)
+            print("Certificate:\n{}\n\n".format(trusted_cert))
         # 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)