Merge Master in geni-v3 conflict resolution
[sfa.git] / sfa / client / sfi.py
index 72f15b3..53d655b 100644 (file)
@@ -1,7 +1,6 @@
 #
 # sfi.py - basic SFA command-line client
-# the actual binary in sfa/clientbin essentially runs main()
-# this module is used in sfascan
+# this module is also used in sfascan
 #
 
 import sys
@@ -14,10 +13,12 @@ import datetime
 import codecs
 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
 
 from sfa.trust.certificate import Keypair, Certificate
 from sfa.trust.gid import GID
@@ -41,33 +42,12 @@ from sfa.client.sfaclientlib import SfaClientBootstrap
 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
 from sfa.client.return_value import ReturnValue
+from sfa.client.candidates import Candidates
 
 CM_PORT=12346
 
-# utility methods here
-def optparse_listvalue_callback(option, option_string, value, parser):
-    setattr(parser.values, option.dest, value.split(','))
-
-# a code fragment that could be helpful for argparse which unfortunately is 
-# available with 2.7 only, so this feels like too strong a requirement for the client side
-#class ExtraArgAction  (argparse.Action):
-#    def __call__ (self, parser, namespace, values, option_string=None):
-# would need a try/except of course
-#        (k,v)=values.split('=')
-#        d=getattr(namespace,self.dest)
-#        d[k]=v
-#####
-#parser.add_argument ("-X","--extra",dest='extras', default={}, action=ExtraArgAction,
-#                     help="set extra flags, testbed dependent, e.g. --extra enabled=true")
-    
-def optparse_dictvalue_callback (option, option_string, value, parser):
-    try:
-        (k,v)=value.split('=',1)
-        d=getattr(parser.values, option.dest)
-        d[k]=v
-    except:
-        parser.print_help()
-        sys.exit(1)
+from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
+    terminal_render, filter_records 
 
 # display methods
 def display_rspec(rspec, format='rspec'):
@@ -113,6 +93,22 @@ def filter_records(type, records):
     return filtered_records
 
 
+def credential_printable (cred):
+    credential=Credential(cred=cred)
+    result=""
+    result += credential.get_summary_tostring()
+    result += "\n"
+    rights = credential.get_privileges()
+    result += "type=%s\n" % credential.type    
+    result += "version=%s\n" % credential.version    
+    result += "rights=%s\n"%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)
+
 # save methods
 def save_raw_to_file(var, filename, format="text", banner=None):
     if filename == "-":
@@ -263,24 +259,24 @@ class Sfi:
         ("version", ""),  
         ("list", "authority"),
         ("show", "name"),
-        ("add", "record"),
-        ("update", "record"),
+        ("add", "[record]"),
+        ("update", "[record]"),
         ("remove", "name"),
-        ("slices", ""),
-        ("resources", "[slice_hrn]"),
+        ("resources", ""),
+        ("describe", "slice_hrn"),
         ("create", "slice_hrn rspec"),
+        ("allocate", "slice_hrn rspec"),
+        ("provision", "slice_hrn"),
+        ("action", "slice_hrn action"), 
         ("delete", "slice_hrn"),
         ("status", "slice_hrn"),
-        ("start", "slice_hrn"),
-        ("stop", "slice_hrn"),
-        ("reset", "slice_hrn"),
         ("renew", "slice_hrn time"),
         ("shutdown", "slice_hrn"),
         ("get_ticket", "slice_hrn rspec"),
         ("redeem_ticket", "ticket"),
-        ("delegate", "name"),
-        ("create_gid", "[name]"),
-        ("get_trusted_certs", "cred"),
+        ("delegate", "to_hrn"),
+        ("gid", "[name]"),
+        ("trusted", "cred"),
         ("config", ""),
         ]
 
@@ -323,41 +319,41 @@ class Sfi:
             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('-e', '--email', dest='email', default="",  help="email (mandatory for users)") 
-# use --extra instead
-#            parser.add_option('-u', '--url', dest='url', metavar='<url>', default=None, help="URL, useful for slices") 
-#            parser.add_option('-d', '--description', dest='description', metavar='<description>', 
-#                              help='Description, useful for slices', default=None)
             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='slice xrns',
+            parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
             parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>', 
-                              help='slice researchers', default='', type="str", action='callback', 
+                              help='Set/replace slice researchers', default='', type="str", action='callback', 
                               callback=optparse_listvalue_callback)
-            parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Principal Investigators/Project Managers',
+            parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
-# use --extra instead
-#            parser.add_option('-f', '--firstname', dest='firstname', metavar='<firstname>', help='user first name')
-#            parser.add_option('-l', '--lastname', dest='lastname', metavar='<lastname>', help='user last name')
             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")
 
         # user specifies remote aggregate/sm/component                          
-        if command in ("resources", "slices", "create", "delete", "start", "stop", 
-                       "restart", "shutdown",  "get_ticket", "renew", "status"):
+        if command in ("resources", "describe", "allocate", "provision", "create", "delete", "allocate", "provision", 
+                       "action", "shutdown",  "get_ticket", "renew", "status"):
             parser.add_option("-d", "--delegate", dest="delegate", default=None, 
                              action="store_true",
                              help="Include a credential delegated to the user's root"+\
                                   "authority in set of credentials for this call")
 
+        # show_credential option
+        if command in ("list","resources", "describe", "provision", "allocate", "create","add","update","remove","slices","delete","status","renew"):
+            parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
+                              help="show credential(s) used in human-readable form")
         # registy filter option
         if command 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")
-        if command in ("resources"):
+        if command in ("show"):
+            parser.add_option("-k","--key",dest="keys",action="append",default=[],
+                              help="specify specific keys to be displayed from record")
+        if command in ("resources", "describe"):
             # rspec version
             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
                               help="schema type and version of resulting RSpec")
@@ -379,7 +375,7 @@ class Sfi:
 
 
         # 'create' does return the new rspec, makes sense to save that too
-        if command in ("resources", "show", "list", "create_gid", 'create'):
+        if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid", 'create'):
            parser.add_option("-o", "--output", dest="file",
                             help="output XML to file", metavar="FILE", default=None)
 
@@ -394,12 +390,22 @@ class Sfi:
         if command == 'list':
            parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
                              help="list all child records", default=False)
+           parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
+                             help="gives details, like user keys", default=False)
         if command in ("delegate"):
            parser.add_option("-u", "--user",
-                            action="store_true", dest="delegate_user", default=False,
-                            help="delegate user credential")
-           parser.add_option("-s", "--slice", dest="delegate_slice",
-                            help="delegate slice credential", metavar="HRN", default=None)
+                             action="store_true", dest="delegate_user", default=False,
+                             help="delegate your own credentials; default if no other option is provided")
+           parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
+                             metavar="slice_hrn", help="delegate cred. for slice HRN")
+           parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
+                             metavar='auth_hrn', help="delegate cred for auth HRN")
+           # this primarily is a shorthand for -a my_hrn^
+           parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
+                             help="delegate your PI credentials, so s.t. like -a your_hrn^")
+           parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
+                             help="""by default the mandatory argument is expected to be a user, 
+use this if you mean an authority instead""")
         
         if command in ("version"):
             parser.add_option("-R","--registry-version",
@@ -455,14 +461,20 @@ class Sfi:
         
 
     def print_help (self):
+        print "==================== Generic sfi usage"
         self.sfi_parser.print_help()
+        print "==================== Specific command usage"
         self.command_parser.print_help()
 
     #
     # Main: parse arguments and dispatch to command
     #
     def dispatch(self, command, command_options, command_args):
-        return getattr(self, command)(command_options, command_args)
+        method=getattr(self, command,None)
+        if not method:
+            print "Unknown command %s"%command
+            return
+        return method(command_options, command_args)
 
     def main(self):
         self.sfi_parser = self.create_parser()
@@ -479,37 +491,59 @@ class Sfi:
             self.print_command_help(options)
             return -1
     
-        command = args[0]
+        # complete / find unique match with command set
+        command_candidates = Candidates (self.available_names)
+        input = args[0]
+        command = command_candidates.only_match(input)
+        if not command:
+            self.print_command_help(options)
+            sys.exit(1)
+        # second pass options parsing
         self.command_parser = self.create_command_parser(command)
         (command_options, command_args) = self.command_parser.parse_args(args[1:])
         self.command_options = command_options
 
         self.read_config () 
         self.bootstrap ()
-        self.logger.info("Command=%s" % command)
+        self.logger.debug("Command=%s" % command)
 
         try:
             self.dispatch(command, command_options, command_args)
-        except KeyError:
-            self.logger.critical ("Unknown command %s"%command)
-            raise
+        except:
+            self.logger.log_exc ("sfi command %s failed"%command)
             sys.exit(1)
-    
+
         return
     
     ####################
     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")
         try:
-           config = Config (config_file)
+            if Config.is_ini(config_file):
+                config = Config (config_file)
+            else:
+                # try upgrading from shell config format
+                fp, fn = mkstemp(suffix='sfi_config', text=True)  
+                config = Config(fn)
+                # we need to preload the sections we want parsed 
+                # from the shell config
+                config.add_section('sfi')
+                config.add_section('sface')
+                config.load(config_file)
+                # back up old config
+                shutil.move(config_file, shell_config_file)
+                # write new config
+                config.save(config_file)
+                 
         except:
-           self.logger.critical("Failed to read configuration file %s"%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)
-           sys.exit(1)
+            self.logger.critical("Failed to read configuration file %s"%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)
+            sys.exit(1)
      
         errors = 0
         # Set SliceMgr URL
@@ -527,7 +561,7 @@ class Sfi:
         elif hasattr(config, "SFI_REGISTRY"):
            self.reg_url = config.SFI_REGISTRY
         else:
-           self.logger.errors("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 %s" % config_file)
            errors += 1 
 
         # Set user HRN
@@ -536,7 +570,7 @@ class Sfi:
         elif hasattr(config, "SFI_USER"):
            self.user = config.SFI_USER
         else:
-           self.logger.errors("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 %s" % config_file)
            errors += 1 
 
         # Set authority HRN
@@ -578,7 +612,8 @@ class Sfi:
     
     # init self-signed cert, user credentials and gid
     def bootstrap (self):
-        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)
@@ -588,7 +623,7 @@ class Sfi:
             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"%get_leaf(self.user))
+                    legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
@@ -601,6 +636,9 @@ class Sfi:
         # extract what's needed
         self.private_key = client_bootstrap.private_key()
         self.my_credential_string = client_bootstrap.my_credential_string ()
+        self.my_credential = {'geni_type': 'geni_sfa',
+                              'geni_version': '3.0', 
+                              'geni_value': self.my_credential_string}
         self.my_gid = client_bootstrap.my_gid ()
         self.client_bootstrap = client_bootstrap
 
@@ -611,9 +649,17 @@ class Sfi:
             sys.exit(-1)
         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)
+
     def slice_credential_string(self, name):
         return self.client_bootstrap.slice_credential_string (name)
 
+    def slice_credential(self, name):
+        return {'geni_type': 'geni_sfa',
+                'geni_version': '3.0',
+                'geni_value': self.slice_credential_string(name)}    
+
     # xxx should be supported by sfaclientbootstrap as well
     def delegate_cred(self, object_cred, hrn, type='authority'):
         # the gid and hrn of the object we are delegating
@@ -799,16 +845,17 @@ or version information about sfi itself
         if options.recursive:
             opts['recursive'] = options.recursive
         
+        if options.show_credential:
+            show_credentials(self.my_credential_string)
         try:
             list = self.registry().List(hrn, self.my_credential_string, options)
         except IndexError:
             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...
+        # This really should be in the self.filter_records funct def comment...
         list = filter_records(options.type, list)
-        for record in list:
-            print "%s (%s)" % (record['hrn'], record['type'])
+        terminal_render (list, options)
         if options.file:
             save_records_to_file(options.file, list, options.fileformat)
         return
@@ -821,10 +868,21 @@ or version information about sfi itself
             self.print_help()
             sys.exit(1)
         hrn = args[0]
-        record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
+        # explicitly require Resolve to run in details mode
+        record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
         record_dicts = filter_records(options.type, record_dicts)
         if not record_dicts:
             self.logger.error("No record of type %s"% options.type)
+            return
+        # user has required to focus on some keys
+        if options.keys:
+            def project (record):
+                projected={}
+                for key in options.keys:
+                    try: projected[key]=record[key]
+                    except: pass
+                return projected
+            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)  
@@ -834,13 +892,22 @@ or version information about sfi itself
         return
     
     def add(self, options, args):
-        "add record into registry from xml file (Register)"
+        "add record into registry by using the command options (Recommended) or from xml file (Register)"
         auth_cred = self.my_authority_credential_string()
+        if options.show_credential:
+            show_credentials(auth_cred)
         record_dict = {}
-        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())
+        if len(args) > 1:
+            self.print_help()
+            sys.exit(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).todict())
+            except:
+                print "Cannot load record file %s"%record_filepath
+                sys.exit(1)
         if options:
             record_dict.update(load_record_from_opts(options).todict())
         # we should have a type by now
@@ -857,7 +924,7 @@ or version information about sfi itself
         return self.registry().Register(record_dict, auth_cred)
     
     def update(self, options, args):
-        "update record into registry from xml file (Update)"
+        "update record into registry by using the command options (Recommended) or from xml file (Update)"
         record_dict = {}
         if len(args) > 0:
             record_filepath = args[0]
@@ -893,6 +960,8 @@ or version information about sfi itself
             cred = self.my_authority_credential_string()
         else:
             raise "unknown record type" + record_dict['type']
+        if options.show_credential:
+            show_credentials(cred)
         return self.registry().Update(record_dict, cred)
   
     def remove(self, options, args):
@@ -905,6 +974,8 @@ or version information about sfi itself
         type = options.type 
         if type in ['all']:
             type = '*'
+        if options.show_credential:
+            show_credentials(auth_cred)
         return self.registry().Remove(hrn, auth_cred, type)
     
     # ==================================================================
@@ -916,12 +987,11 @@ or version information about sfi itself
         server = self.sliceapi()
         # creds
         creds = [self.my_credential_string]
-        if options.delegate:
-            delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
-            creds.append(delegated_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.show_credential:
+            show_credentials(creds)
         result = server.ListSlices(creds, *self.ois(server,api_options))
         value = ReturnValue.get_value(result)
         if self.options.raw:
@@ -933,19 +1003,17 @@ or version information about sfi itself
     # show rspec for named slice
     def resources(self, options, args):
         """
-        with no arg, discover available resources, (ListResources)
+        discover available resources
 or with an slice hrn, shows currently provisioned resources
         """
         server = self.sliceapi()
 
         # set creds
-        creds = []
-        if args:
-            creds.append(self.slice_credential_string(args[0]))
-        else:
-            creds.append(self.my_credential_string)
+        creds = [self.my_credential]
         if options.delegate:
             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
+        if options.show_credential:
+            show_credentials(creds)
 
         # no need to check if server accepts the options argument since the options has
         # been a required argument since v1 API
@@ -954,9 +1022,6 @@ or with an slice hrn, shows currently provisioned resources
         api_options ['call_id'] = unique_call_id()
         # ask for cached value if available
         api_options ['cached'] = True
-        if args:
-            hrn = args[0]
-            api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
         if options.info:
             api_options['info'] = options.info
         if options.list_leases:
@@ -987,6 +1052,45 @@ or with an slice hrn, shows currently provisioned resources
 
         return
 
+    def describe(self, options, args):
+        """
+        Shows currently provisioned resources.
+        """
+        server = self.sliceapi()
+
+        # set creds
+        creds = [self.slice_credential(args[0])]
+        if options.delegate:
+            creds.append(self.delegate_cred(cred, get_authority(self.authority)))
+        if options.show_credential:
+            show_credentials(creds)
+
+        api_options = {'call_id': unique_call_id(),
+                       'cached': True,
+                       'info': options.info,
+                       'list_leases': options.list_leases,
+                       'geni_rspec_version': {'type': 'geni', 'version': '3.0'},
+                      }
+        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.0'}
+        urn = Xrn(args[0], type='slice').get_urn()        
+        result = server.Describe([urn], creds, api_options)
+        value = ReturnValue.get_value(result)
+        if self.options.raw:
+            save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
+        if options.file is not None:
+            save_rspec_to_file(value, options.file)
+        if (self.options.raw is None) and (options.file is None):
+            display_rspec(value, options.format)
+
+        return 
+
     def create(self, options, args):
         """
         create or update named slice with given rspec
@@ -1000,6 +1104,7 @@ or with an slice hrn, shows currently provisioned resources
 
         # credentials
         creds = [self.slice_credential_string(slice_hrn)]
+
         delegated_cred = None
         server_version = self.get_cached_server_version(server)
         if server_version.get('interface') == 'slicemgr':
@@ -1011,6 +1116,9 @@ or with an slice hrn, shows currently provisioned resources
             #elif server_version.get('urn'):
             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
 
+        if options.show_credential:
+            show_credentials(creds)
+
         # rspec
         rspec_file = self.get_rspec_file(args[1])
         rspec = open(rspec_file).read()
@@ -1022,10 +1130,14 @@ or with an slice hrn, shows currently provisioned resources
         #    keys: [<ssh key A>, <ssh key B>]
         #  }]
         users = []
+        # xxx Thierry 2012 sept. 21
+        # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
+        # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
-        if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
+        # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
+        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['researcher']
+            user_hrns = slice_record['reg-researchers']
             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
 
@@ -1040,8 +1152,6 @@ or with an slice hrn, shows currently provisioned resources
         # do not append users, keys, or slice tags. Anything
         # not contained in this request will be removed from the slice
 
-        # CreateSliver has supported the options argument for a while now so it should
-        # be safe to assume this server support it
         api_options = {}
         api_options ['append'] = False
         api_options ['call_id'] = unique_call_id()
@@ -1067,23 +1177,116 @@ or with an slice hrn, shows currently provisioned resources
         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
 
         # creds
-        slice_cred = self.slice_credential_string(slice_hrn)
+        slice_cred = self.slice_credential(slice_hrn)
         creds = [slice_cred]
-        if options.delegate:
-            delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
-            creds.append(delegated_cred)
         
         # options and call_id when supported
         api_options = {}
         api_options ['call_id'] = unique_call_id()
-        result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
+        if options.show_credential:
+            show_credentials(creds)
+        result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
         value = ReturnValue.get_value(result)
         if self.options.raw:
             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
         else:
             print value
-        return value 
-  
+        return value
+
+    def allocate(self, options, args):
+        server = self.sliceapi()
+        server_version = self.get_cached_server_version(server)
+        slice_hrn = args[0]
+        slice_urn = Xrn(slice_hrn, type='slice').get_urn()
+
+        # credentials
+        creds = [self.slice_credential(slice_hrn)]
+
+        delegated_cred = None
+        if server_version.get('interface') == 'slicemgr':
+            # delegate our cred to the slice manager
+            # do not delegate cred to slicemgr...not working at the moment
+            pass
+            #if server_version.get('hrn'):
+            #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
+            #elif server_version.get('urn'):
+            #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
+
+        if options.show_credential:
+            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()
+        result = server.Allocate(slice_urn, creds, rspec, api_options)
+        value = ReturnValue.get_value(result)
+        if self.options.raw:
+            save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
+        if options.file is not None:
+            save_rspec_to_file (value, options.file)
+        if (self.options.raw is None) and (options.file is None):
+            print value
+
+        return value
+        
+
+    def provision(self, options, args):
+        server = self.sliceapi()
+        server_version = self.get_cached_server_version(server)
+        slice_hrn = args[0]
+        slice_urn = Xrn(slice_hrn, type='slice').get_urn()
+
+        # credentials
+        creds = [self.slice_credential(slice_hrn)]
+        delegated_cred = None
+        if server_version.get('interface') == 'slicemgr':
+            # delegate our cred to the slice manager
+            # do not delegate cred to slicemgr...not working at the moment
+            pass
+            #if server_version.get('hrn'):
+            #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
+            #elif server_version.get('urn'):
+            #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
+
+        if options.show_credential:
+            show_credentials(creds)
+
+        api_options = {}
+        api_options ['call_id'] = unique_call_id()
+
+        # set the requtested rspec version
+        version_manager = VersionManager()
+        rspec_version = version_manager._get_version('geni', '3.0').to_dict()
+        api_options['geni_rspec_version'] = rspec_version
+
+        # users
+        # need to pass along user keys to the aggregate.
+        # users = [
+        #  { urn: urn:publicid:IDN+emulab.net+user+alice
+        #    keys: [<ssh key A>, <ssh key B>]
+        #  }]
+        users = []
+        slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
+        if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
+            slice_record = slice_records[0]
+            user_hrns = slice_record['researcher']
+            user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
+            user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
+            users = pg_users_arg(user_records)
+        
+        api_options['geni_users'] = users
+        result = server.Provision([slice_urn], creds, api_options)
+        value = ReturnValue.get_value(result)
+        if self.options.raw:
+            save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
+        if options.file is not None:
+            save_rspec_to_file (value, options.file)
+        if (self.options.raw is None) and (options.file is None):
+            print value
+        return value     
+
     def status(self, options, args):
         """
         retrieve slice status (SliverStatus)
@@ -1095,16 +1298,15 @@ or with an slice hrn, shows currently provisioned resources
         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
 
         # creds 
-        slice_cred = self.slice_credential_string(slice_hrn)
+        slice_cred = self.slice_credential(slice_hrn)
         creds = [slice_cred]
-        if options.delegate:
-            delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
-            creds.append(delegated_cred)
 
         # options and call_id when supported
         api_options = {}
         api_options['call_id']=unique_call_id()
-        result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
+        if options.show_credential:
+            show_credentials(creds)
+        result = server.Status([slice_urn], creds, *self.ois(server,api_options))
         value = ReturnValue.get_value(result)
         if self.options.raw:
             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
@@ -1124,9 +1326,6 @@ or with an slice hrn, shows currently provisioned resources
         # cred
         slice_cred = self.slice_credential_string(args[0])
         creds = [slice_cred]
-        if options.delegate:
-            delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
-            creds.append(delegated_cred)
         # xxx Thierry - does this not need an api_options as well ?
         result = server.Start(slice_urn, creds)
         value = ReturnValue.get_value(result)
@@ -1147,9 +1346,6 @@ or with an slice hrn, shows currently provisioned resources
         # cred
         slice_cred = self.slice_credential_string(args[0])
         creds = [slice_cred]
-        if options.delegate:
-            delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
-            creds.append(delegated_cred)
         result =  server.Stop(slice_urn, creds)
         value = ReturnValue.get_value(result)
         if self.options.raw:
@@ -1159,21 +1355,24 @@ or with an slice hrn, shows currently provisioned resources
         return value
     
     # reset named slice
-    def reset(self, options, args):
+    def action(self, options, args):
         """
-        reset named slice (reset_slice)
+        Perform the named operational action on the named slivers
         """
         server = self.sliceapi()
+        api_options = {}
         # slice urn
         slice_hrn = args[0]
-        slice_urn = hrn_to_urn(slice_hrn, 'slice') 
+        action = args[1]
+        slice_urn = Xrn(slice_hrn, type='slice').get_urn() 
         # cred
-        slice_cred = self.slice_credential_string(args[0])
+        slice_cred = self.slice_credential(args[0])
         creds = [slice_cred]
         if options.delegate:
             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
             creds.append(delegated_cred)
-        result = server.reset_slice(creds, slice_urn)
+        
+        result = server.PerformOperationalAction([slice_urn], creds, action , api_options)
         value = ReturnValue.get_value(result)
         if self.options.raw:
             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
@@ -1186,21 +1385,22 @@ or with an slice hrn, shows currently provisioned resources
         renew slice (RenewSliver)
         """
         server = self.sliceapi()
+        if len(args) != 2:
+            self.print_help()
+            sys.exit(1)
+        [ slice_hrn, input_time ] = args
         # slice urn    
-        slice_hrn = args[0]
         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
+        # time: don't try to be smart on the time format, server-side will
         # creds
-        slice_cred = self.slice_credential_string(args[0])
+        slice_cred = self.slice_credential(args[0])
         creds = [slice_cred]
-        if options.delegate:
-            delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
-            creds.append(delegated_cred)
-        # time
-        time = args[1]
         # options and call_id when supported
         api_options = {}
-       api_options['call_id']=unique_call_id()
-        result =  server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
+        api_options['call_id']=unique_call_id()
+        if options.show_credential:
+            show_credentials(creds)
+        result =  server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
         value = ReturnValue.get_value(result)
         if self.options.raw:
             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
@@ -1218,11 +1418,8 @@ or with an slice hrn, shows currently provisioned resources
         slice_hrn = args[0]
         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
         # creds
-        slice_cred = self.slice_credential_string(slice_hrn)
+        slice_cred = self.slice_credential(slice_hrn)
         creds = [slice_cred]
-        if options.delegate:
-            delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
-            creds.append(delegated_cred)
         result = server.Shutdown(slice_urn, creds)
         value = ReturnValue.get_value(result)
         if self.options.raw:
@@ -1243,15 +1440,12 @@ or with an slice hrn, shows currently provisioned resources
         # creds
         slice_cred = self.slice_credential_string(slice_hrn)
         creds = [slice_cred]
-        if options.delegate:
-            delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
-            creds.append(delegated_cred)
         # rspec
         rspec_file = self.get_rspec_file(rspec_path) 
         rspec = open(rspec_file).read()
         # options and call_id when supported
         api_options = {}
-       api_options['call_id']=unique_call_id()
+        api_options['call_id']=unique_call_id()
         # get ticket at the server
         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
         # save
@@ -1301,7 +1495,7 @@ or with an slice hrn, shows currently provisioned resources
                 self.logger.log_exc(e.message)
         return
 
-    def create_gid(self, options, args):
+    def gid(self, options, args):
         """
         Create a GID (CreateGid)
         """
@@ -1309,7 +1503,8 @@ or with an slice hrn, shows currently provisioned resources
             self.print_help()
             sys.exit(1)
         target_hrn = args[0]
-        gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
+        my_gid_string = open(self.client_bootstrap.my_gid()).read() 
+        gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
         if options.file:
             filename = options.file
         else:
@@ -1318,33 +1513,53 @@ or with an slice hrn, shows currently provisioned resources
         GID(string=gid).save_to_file(filename)
          
 
-    def delegate(self, options, args):
+    def delegate (self, options, args):
         """
         (locally) create delegate credential for use by given hrn
         """
-        delegee_hrn = args[0]
-        if options.delegate_user:
-            cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
-        elif options.delegate_slice:
-            slice_cred = self.slice_credential_string(options.delegate_slice)
-            cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
-        else:
-            self.logger.warning("Must specify either --user or --slice <hrn>")
-            return
-        delegated_cred = Credential(string=cred)
-        object_hrn = delegated_cred.get_gid_object().get_hrn()
+        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
+        tuples=[]
+        for slice_hrn in options.delegate_slices:
+            message="%s.slice"%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
+            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)
+            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:
-            dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
-                                  + get_leaf(object_hrn) + ".cred")
-        elif options.delegate_slice:
-            dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
-                                  + get_leaf(object_hrn) + ".cred")
-
-        delegated_cred.save_to_file(dest_fn, save_parents=True)
-
-        self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
+            message="%s.user"%self.user
+            original = self.my_credential_string
+            tuples.append ( (message, original, ) )
+
+        # default type for beneficial is user unless -A
+        if options.delegate_to_authority:       to_type='authority'
+        else:                                   to_type='user'
+
+        # let's now handle all this
+        # it's all in the filenaming scheme
+        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))
+            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))
     
-    def get_trusted_certs(self, options, args):
+    def trusted(self, options, args):
         """
         return uhe trusted certs at this interface (get_trusted_certs)
         """ 
@@ -1353,7 +1568,7 @@ or with an slice hrn, shows currently provisioned resources
             gid = GID(string=trusted_cert)
             gid.dump()
             cert = Certificate(string=trusted_cert)
-            self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
+            self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
         return 
 
     def config (self, options, args):