Merge branch 'geni-v3' of ssh://git.onelab.eu/git/sfa into geni-v3
authorLoic Baron <loic.baron@lip6.fr>
Mon, 28 Sep 2015 14:01:27 +0000 (16:01 +0200)
committerLoic Baron <loic.baron@lip6.fr>
Mon, 28 Sep 2015 14:01:27 +0000 (16:01 +0200)
1  2 
sfa/client/sfi.py

diff --combined sfa/client/sfi.py
@@@ -3,6 -3,8 +3,8 @@@
  # this module is also used in sfascan
  #
  
+ from __future__ import print_function
  import sys
  sys.path.append('.')
  
@@@ -46,7 -48,8 +48,8 @@@ from sfa.client.return_value import Ret
  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 
@@@ -66,12 -69,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'''
@@@ -83,7 -86,7 +86,7 @@@ def display_record(record, dump=False)
          record.dump(sort=True)
      else:
          info = record.getdict()
-         print "%s (%s)" % (info['hrn'], info['type'])
+         print("{} ({})".format(info['hrn'], info['type']))
      return
  
  
@@@ -96,87 -99,83 +99,83 @@@ def filter_records(type, records)
  
  
  def credential_printable (cred):
-     credential=Credential(cred=cred)
+     credential = Credential(cred=cred)
      result=""
-     result += credential.get_summary_tostring()
+     result += credential.pretty_cred()
      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
  
  def show_credentials (cred_s):
      if not isinstance (cred_s,list): cred_s = [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:
-         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":
-         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":
-         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
-         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"
-     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":
-         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":
-         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":
-         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
-         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
  def check_ssh_key (key):
      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:
          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
  
      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
@@@ -308,49 -325,50 +325,50 @@@ class Sfi
          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)
+                 doc = doc.replace("\n", "\n" + format3offset * ' ')
+                 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"
+         print("==================== Generic sfi usage")
          self.sfi_parser.print_help()
-         (doc,_,example,canonical)=commands_dict[self.command]
+         (doc, _, example, canonical) = commands_dict[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:
-             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]",
-                               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",
          (_, args_string, __,canonical) = commands_dict[command]
  
          parser = OptionParser(add_help_option=False,
-                               usage="sfi [sfi_options] %s [cmd_options] %s"
-                               (command, args_string))
+                               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 ("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('-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',
                                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")
                                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",  
              #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",
-                                 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")
  
  
@@@ -535,8 -553,12 +553,12 @@@ use this if you mean an authority inste
          (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):
              sys.exit(1)
          self.command_options = command_options
  
+         # 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=%s" % self.command)
+         self.logger.debug("Command={}".format(self.command))
  
          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
      
                  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")
              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 %s"%config_file)
+                 self.logger.log_exc("Could not read config file {}".format(config_file))
              sys.exit(1)
       
          self.config_instance=config
          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
          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
          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
          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 
  
          self.config_file=config_file
              if not os.path.isfile(client_bootstrap.private_key_filename()):
                  self.logger.info ("private key not found, trying legacy name")
                  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)
+                     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 %s"%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)
          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
      def registry (self):
          # 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)  
+             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):
                  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]
-                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
+                 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
-                 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):
              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)
              version= ReturnValue.get_value(result)
              # cache version for 20 minutes
              cache.add(cache_key, version, ttl= 60*20)
-             self.logger.info("Updating cache file %s" % cache_file)
+             self.logger.info("Updating cache file {}".format(cache_file))
              cache.save_to_file(cache_file)
  
          return version   
         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):
         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)
  
  
          if not output: 
              return 0
          # something went wrong
-         print 'ERROR:',output
+         print('ERROR:', output)
          return 1
  
      #==========================================================================
      @declare_command("","")
      def config (self, options, args):
          "Display contents of current config"
-         print "# From configuration file %s"%self.config_file
+         print("# From configuration file {}".format(self.config_file))
          flags=[ ('sfi', [ ('registry','reg_url'),
                            ('auth','authority'),
                            ('user','user'),
              flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
  
          for (section, tuples) in flags:
-             print "[%s]"%section
+             print("[{}]".format(section))
              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:
-                 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
  
          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:
          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
              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:
-                 print "Cannot load record file %s"%record_filepath
+                 print("Cannot load record file {}".format(record_filepath))
                  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()
          if len(args) > 0:
              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:
-             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
-         if 'type' not in record_dict:
+         if 'type' not in record_dict or record_dict['type'] is None:
              self.print_help()
              sys.exit(1)
  
          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)
      # ==================================================================
  
      # show rspec for named slice
-     @declare_command("","")
+     @declare_command("","",['discover'])
      def resources(self, options, args):
          """
          discover available resources (ListResources)
                  # 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'}
 +                api_options['geni_rspec_version'] = {'type': options.rspec_version}
          else:
              api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
 +
          list_resources = server.ListResources (creds, api_options)
          value = ReturnValue.get_value(list_resources)
          if self.options.raw:
          if self.options.raw:
              save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
          else:
-             print value
+             print(value)
          return self.success (delete)
  
      @declare_command("slice_hrn rspec","")
          """
          server = self.sliceapi()
          server_version = self.get_cached_server_version(server)
+         if len(args) != 2:
+             self.print_help()
+             sys.exit(1)
          slice_hrn = args[0]
+         rspec_file = self.get_rspec_file(args[1])
          slice_urn = Xrn(slice_hrn, type='slice').get_urn()
  
          # credentials
              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['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:
              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>...]","")
          if options.file is not None:
              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","")
          if self.options.raw:
              save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
          else:
-             print value
+             print(value)
          return self.success (status)
  
      @declare_command("slice_hrn [<sliver_urn>...] action","")
          if self.options.raw:
              save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
          else:
-             print value
+             print(value)
          return self.success (perform_action)
  
      @declare_command("slice_hrn [<sliver_urn>...] time",
          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","")
          if self.options.raw:
              save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
          else:
-             print value
+             print(value)
          return self.success (shutdown)
  
      @declare_command("[name]","")
          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
          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:
-             message="%s.slice"%slice_hrn
+             message = "{}.slice".format(slice_hrn)
              original = self.slice_credential_string(slice_hrn)
              tuples.append ( (message, original,) )
          if options.delegate_pi:
              my_authority=self.authority
-             message="%s.pi"%my_authority
+             message = "{}.pi".format(my_authority)
              original = self.my_authority_credential_string()
              tuples.append ( (message, original,) )
          for auth_hrn in options.delegate_auths:
-             message="%s.auth"%auth_hrn
-             original=self.authority_credential_string(auth_hrn)
+             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 not tuples: options.delegate_user=True
          # this user cred
          if options.delegate_user:
-             message="%s.user"%self.user
+             message = "{}.user".format(self.user)
              original = self.my_credential_string
              tuples.append ( (message, original, ) )
  
          for (message,original) in tuples:
              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))
+             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)
-             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
@@@ -1630,35 -1669,40 +1670,40 @@@ $ sfi m -b http://mymanifold.foo.com:70
              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
+             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
-         self.logger.info("Resolving our own id %s"%self.user)
+         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 %s -- exiting"%self.user; sys.exit(1)
-         my_record=my_records[0]
+         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']
-         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)))
-             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
          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))
+         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)))
-             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
          hrn_credentials=[]
              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,
-                                       "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
+                                       "{}.{}_for_{}.{}.cred"\
+                                       .format(hrn, htype, delegatee_hrn, delegatee_type))
              with file(filename,'w') as f:
                  f.write(delegated_credential)
-             self.logger.debug("(Over)wrote %s"%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
-         self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
+         self.logger.info("Uploading on backend at {}".format(myslice_dict['backend']))
          uploader = ManifoldUploader (logger=self.logger,
                                       url=myslice_dict['backend'],
                                       platform=myslice_dict['platform'],
              # inspect
              inspect=Credential(string=delegated_credential)
              expire_datetime=inspect.get_expiration()
-             message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
+             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 %d/%d credentials"%(count_success,count_all))
+         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
              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 -> %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