Merge branch 'geni-v3' of ssh://git.onelab.eu/git/sfa into geni-v3
[sfa.git] / sfa / client / sfi.py
1 #
2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
4 #
5
6 from __future__ import print_function
7
8 import sys
9 sys.path.append('.')
10
11 import os, os.path
12 import socket
13 import re
14 import datetime
15 import codecs
16 import pickle
17 import json
18 import shutil
19 from lxml import etree
20 from StringIO import StringIO
21 from optparse import OptionParser
22 from pprint import PrettyPrinter
23 from tempfile import mkstemp
24
25 from sfa.trust.certificate import Keypair, Certificate
26 from sfa.trust.gid import GID
27 from sfa.trust.credential import Credential
28 from sfa.trust.sfaticket import SfaTicket
29
30 from sfa.util.faults import SfaInvalidArgument
31 from sfa.util.sfalogging import sfi_logger
32 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
33 from sfa.util.config import Config
34 from sfa.util.version import version_core
35 from sfa.util.cache import Cache
36 from sfa.util.printable import printable
37
38 from sfa.storage.record import Record
39
40 from sfa.rspecs.rspec import RSpec
41 from sfa.rspecs.rspec_converter import RSpecConverter
42 from sfa.rspecs.version_manager import VersionManager
43
44 from sfa.client.sfaclientlib import SfaClientBootstrap
45 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
46 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
47 from sfa.client.return_value import ReturnValue
48 from sfa.client.candidates import Candidates
49 from sfa.client.manifolduploader import ManifoldUploader
50
51 CM_PORT = 12346
52 DEFAULT_RSPEC_VERSION = "GENI 3"
53
54 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
55     terminal_render, filter_records 
56
57 # display methods
58 def display_rspec(rspec, format='rspec'):
59     if format in ['dns']:
60         tree = etree.parse(StringIO(rspec))
61         root = tree.getroot()
62         result = root.xpath("./network/site/node/hostname/text()")
63     elif format in ['ip']:
64         # The IP address is not yet part of the new RSpec
65         # so this doesn't do anything yet.
66         tree = etree.parse(StringIO(rspec))
67         root = tree.getroot()
68         result = root.xpath("./network/site/node/ipv4/text()")
69     else:
70         result = rspec
71
72     print(result)
73     return
74
75 def display_list(results):
76     for result in results:
77         print(result)
78
79 def display_records(recordList, dump=False):
80     ''' Print all fields in the record'''
81     for record in recordList:
82         display_record(record, dump)
83
84 def display_record(record, dump=False):
85     if dump:
86         record.dump(sort=True)
87     else:
88         info = record.getdict()
89         print("{} ({})".format(info['hrn'], info['type']))
90     return
91
92
93 def filter_records(type, records):
94     filtered_records = []
95     for record in records:
96         if (record['type'] == type) or (type == "all"):
97             filtered_records.append(record)
98     return filtered_records
99
100
101 def credential_printable (cred):
102     credential = Credential(cred=cred)
103     result=""
104     result += credential.pretty_cred()
105     result += "\n"
106     rights = credential.get_privileges()
107     result += "type={}\n".format(credential.type)
108     result += "version={}\n".format(credential.version)
109     result += "rights={}\n".format(rights)
110     return result
111
112 def show_credentials (cred_s):
113     if not isinstance (cred_s,list): cred_s = [cred_s]
114     for cred in cred_s:
115         print("Using Credential {}".format(credential_printable(cred)))
116
117 ########## save methods
118
119 ### raw
120 def save_raw_to_file(var, filename, format='text', banner=None):
121     if filename == '-':
122         _save_raw_to_file(var, sys.stdout, format, banner)
123     else:
124         with open(filename, w) as fileobj:
125             _save_raw_to_file(var, fileobj, format, banner)
126         print("(Over)wrote {}".format(filename))
127
128 def _save_raw_to_file(var, f, format, banner):
129     if format == "text":
130         if banner: f.write(banner+"\n")
131         f.write("{}".format(var))
132         if banner: f.write('\n'+banner+"\n")
133     elif format == "pickled":
134         f.write(pickle.dumps(var))
135     elif format == "json":
136         f.write(json.dumps(var))   # python 2.6
137     else:
138         # this should never happen
139         print("unknown output format", format)
140
141 ### 
142 def save_rspec_to_file(rspec, filename):
143     if not filename.endswith(".rspec"):
144         filename = filename + ".rspec"
145     with open(filename, 'w') as f:
146         f.write("{}".format(rspec))
147     print("(Over)wrote {}".format(filename))
148
149 def save_record_to_file(filename, record_dict):
150     record = Record(dict=record_dict)
151     xml = record.save_as_xml()
152     with codecs.open(filename, encoding='utf-8',mode="w") as f:
153         f.write(xml)
154     print("(Over)wrote {}".format(filename))
155
156 def save_records_to_file(filename, record_dicts, format="xml"):
157     if format == "xml":
158         for index, record_dict in enumerate(record_dicts):
159             save_record_to_file(filename + "." + str(index), record_dict)
160     elif format == "xmllist":
161         with open(filename, "w") as f:
162             f.write("<recordlist>\n")
163             for record_dict in record_dicts:
164                 record_obj = Record(dict=record_dict)
165                 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
166             f.write("</recordlist>\n")
167             print("(Over)wrote {}".format(filename))
168
169     elif format == "hrnlist":
170         with open(filename, "w") as f:
171             for record_dict in record_dicts:
172                 record_obj = Record(dict=record_dict)
173                 f.write(record_obj.hrn + "\n")
174             print("(Over)wrote {}".format(filename))
175
176     else:
177         # this should never happen
178         print("unknown output format", format)
179
180 # minimally check a key argument
181 def check_ssh_key (key):
182     good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
183     return re.match(good_ssh_key, key, re.IGNORECASE)
184
185 # load methods
186 def normalize_type (type):
187     if type.startswith('au'):
188         return 'authority'
189     elif type.startswith('us'):
190         return 'user'
191     elif type.startswith('sl'):
192         return 'slice'
193     elif type.startswith('no'):
194         return 'node'
195     elif type.startswith('ag'):
196         return 'aggregate'
197     elif type.startswith('al'):
198         return 'all'
199     else:
200         print('unknown type {} - should start with one of au|us|sl|no|ag|al'.format(type))
201         return None
202
203 def load_record_from_opts(options):
204     record_dict = {}
205     if hasattr(options, 'xrn') and options.xrn:
206         if hasattr(options, 'type') and options.type:
207             xrn = Xrn(options.xrn, options.type)
208         else:
209             xrn = Xrn(options.xrn)
210         record_dict['urn'] = xrn.get_urn()
211         record_dict['hrn'] = xrn.get_hrn()
212         record_dict['type'] = xrn.get_type()
213     if hasattr(options, 'key') and options.key:
214         try:
215             pubkey = open(options.key, 'r').read()
216         except IOError:
217             pubkey = options.key
218         if not check_ssh_key (pubkey):
219             raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
220         record_dict['reg-keys'] = [pubkey]
221     if hasattr(options, 'slices') and options.slices:
222         record_dict['slices'] = options.slices
223     if hasattr(options, 'reg_researchers') and options.reg_researchers is not None:
224         record_dict['reg-researchers'] = options.reg_researchers
225     if hasattr(options, 'email') and options.email:
226         record_dict['email'] = options.email
227     # authorities can have a name for standalone deployment
228     if hasattr(options, 'name') and options.name:
229         record_dict['name'] = options.name
230     if hasattr(options, 'reg_pis') and options.reg_pis:
231         record_dict['reg-pis'] = options.reg_pis
232
233     # handle extra settings
234     record_dict.update(options.extras)
235     
236     return Record(dict=record_dict)
237
238 def load_record_from_file(filename):
239     with codecs.open(filename, encoding="utf-8", mode="r") as f:
240         xml_str = f.read()
241     return Record(xml=xml_str)
242
243 import uuid
244 def unique_call_id(): return uuid.uuid4().urn
245
246 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
247 # essentially for the methods that implement a subcommand like sfi list
248 # we need to keep track of
249 # (*) doc         a few lines that tell what it does, still located in __doc__
250 # (*) args_string a simple one-liner that describes mandatory arguments
251 # (*) example     well, one or several releant examples
252
253 # since __doc__ only accounts for one, we use this simple mechanism below
254 # however we keep doc in place for easier migration
255
256 from functools import wraps
257
258 # we use a list as well as a dict so we can keep track of the order
259 commands_list=[]
260 commands_dict={}
261
262 def declare_command (args_string, example,aliases=None):
263     def wrap(m): 
264         name=getattr(m,'__name__')
265         doc=getattr(m,'__doc__',"-- missing doc --")
266         doc=doc.strip(" \t\n")
267         commands_list.append(name)
268         # last item is 'canonical' name, so we can know which commands are aliases
269         command_tuple=(doc, args_string, example,name)
270         commands_dict[name]=command_tuple
271         if aliases is not None:
272             for alias in aliases:
273                 commands_list.append(alias)
274                 commands_dict[alias]=command_tuple
275         @wraps(m)
276         def new_method (*args, **kwds): return m(*args, **kwds)
277         return new_method
278     return wrap
279
280
281 def remove_none_fields (record):
282     none_fields=[ k for (k,v) in record.items() if v is None ]
283     for k in none_fields: del record[k]
284
285 ##########
286
287 class Sfi:
288     
289     # dirty hack to make this class usable from the outside
290     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user', 'user_private_key']
291
292     @staticmethod
293     def default_sfi_dir ():
294         if os.path.isfile("./sfi_config"): 
295             return os.getcwd()
296         else:
297             return os.path.expanduser("~/.sfi/")
298
299     # dummy to meet Sfi's expectations for its 'options' field
300     # i.e. s/t we can do setattr on
301     class DummyOptions:
302         pass
303
304     def __init__ (self,options=None):
305         if options is None: options=Sfi.DummyOptions()
306         for opt in Sfi.required_options:
307             if not hasattr(options,opt): setattr(options,opt,None)
308         if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
309         self.options = options
310         self.user = None
311         self.authority = None
312         self.logger = sfi_logger
313         self.logger.enable_console()
314         ### various auxiliary material that we keep at hand 
315         self.command=None
316         # need to call this other than just 'config' as we have a command/method with that name
317         self.config_instance=None
318         self.config_file=None
319         self.client_bootstrap=None
320
321     ### suitable if no reasonable command has been provided
322     def print_commands_help (self, options):
323         verbose=getattr(options,'verbose')
324         format3="%10s %-35s %s"
325         format3offset=47
326         line=80*'-'
327         if not verbose:
328             print(format3%("command", "cmd_args", "description"))
329             print(line)
330         else:
331             print(line)
332             self.create_parser_global().print_help()
333         # preserve order from the code
334         for command in commands_list:
335             try:
336                 (doc, args_string, example, canonical) = commands_dict[command]
337             except:
338                 print("Cannot find info on command %s - skipped"%command)
339                 continue
340             if verbose:
341                 print(line)
342             if command==canonical:
343                 doc = doc.replace("\n", "\n" + format3offset * ' ')
344                 print(format3 % (command,args_string,doc))
345                 if verbose:
346                     self.create_parser_command(command).print_help()
347             else:
348                 print(format3 % (command,"<<alias for %s>>"%canonical,""))
349             
350     ### now if a known command was found we can be more verbose on that one
351     def print_help (self):
352         print("==================== Generic sfi usage")
353         self.sfi_parser.print_help()
354         (doc, _, example, canonical) = commands_dict[self.command]
355         if canonical != self.command:
356             print("\n==================== NOTE: {} is an alias for genuine {}"
357                   .format(self.command, canonical))
358             self.command = canonical
359         print("\n==================== Purpose of {}".format(self.command))
360         print(doc)
361         print("\n==================== Specific usage for {}".format(self.command))
362         self.command_parser.print_help()
363         if example:
364             print("\n==================== {} example(s)".format(self.command))
365             print(example)
366
367     def create_parser_global(self):
368         # Generate command line parser
369         parser = OptionParser(add_help_option=False,
370                               usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
371                               description="Commands: {}".format(" ".join(commands_list)))
372         parser.add_option("-r", "--registry", dest="registry",
373                          help="root registry", metavar="URL", default=None)
374         parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
375                          help="slice API - in general a SM URL, but can be used to talk to an aggregate")
376         parser.add_option("-R", "--raw", dest="raw", default=None,
377                           help="Save raw, unparsed server response to a file")
378         parser.add_option("", "--rawformat", dest="rawformat", type="choice",
379                           help="raw file format ([text]|pickled|json)", default="text",
380                           choices=("text","pickled","json"))
381         parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
382                           help="text string to write before and after raw output")
383         parser.add_option("-d", "--dir", dest="sfi_dir",
384                          help="config & working directory - default is %default",
385                          metavar="PATH", default=Sfi.default_sfi_dir())
386         parser.add_option("-u", "--user", dest="user",
387                          help="user name", metavar="HRN", default=None)
388         parser.add_option("-a", "--auth", dest="auth",
389                          help="authority name", metavar="HRN", default=None)
390         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
391                          help="verbose mode - cumulative")
392         parser.add_option("-D", "--debug",
393                           action="store_true", dest="debug", default=False,
394                           help="Debug (xml-rpc) protocol messages")
395         # would it make sense to use ~/.ssh/id_rsa as a default here ?
396         parser.add_option("-k", "--private-key",
397                          action="store", dest="user_private_key", default=None,
398                          help="point to the private key file to use if not yet installed in sfi_dir")
399         parser.add_option("-t", "--timeout", dest="timeout", default=None,
400                          help="Amout of time to wait before timing out the request")
401         parser.add_option("-h", "--help", 
402                          action="store_true", dest="help", default=False,
403                          help="one page summary on commands & exit")
404         parser.disable_interspersed_args()
405
406         return parser
407         
408
409     def create_parser_command(self, command):
410         if command not in commands_dict:
411             msg="Invalid command\n"
412             msg+="Commands: "
413             msg += ','.join(commands_list)            
414             self.logger.critical(msg)
415             sys.exit(2)
416
417         # retrieve args_string
418         (_, args_string, __,canonical) = commands_dict[command]
419
420         parser = OptionParser(add_help_option=False,
421                               usage="sfi [sfi_options] {} [cmd_options] {}"\
422                               .format(command, args_string))
423         parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
424                            help="Summary of one command usage")
425
426         if canonical in ("config"):
427             parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
428                               help='how myslice config variables as well')
429
430         if canonical in ("version"):
431             parser.add_option("-l","--local",
432                               action="store_true", dest="version_local", default=False,
433                               help="display version of the local client")
434
435         if canonical in ("version", "trusted"):
436             parser.add_option("-R","--registry_interface",
437                               action="store_true", dest="registry_interface", default=False,
438                               help="target the registry interface instead of slice interface")
439
440         if canonical in ("register", "update"):
441             parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
442             parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type (2 first chars is enough)', default=None)
443             parser.add_option('-e', '--email', dest='email', default="",  help="email (mandatory for users)") 
444             parser.add_option('-n', '--name', dest='name', default="",  help="name (optional for authorities)") 
445             parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file', 
446                               default=None)
447             parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
448                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
449             parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>', 
450                               help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback', 
451                               callback=optparse_listvalue_callback)
452             parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
453                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
454             parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
455                                action="callback", callback=optparse_dictvalue_callback, nargs=1,
456                                help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
457
458         # user specifies remote aggregate/sm/component                          
459         if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision", 
460                        "action", "shutdown", "renew", "status"):
461             parser.add_option("-d", "--delegate", dest="delegate", default=None, 
462                              action="store_true",
463                              help="Include a credential delegated to the user's root"+\
464                                   "authority in set of credentials for this call")
465
466         # show_credential option
467         if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
468             parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
469                               help="show credential(s) used in human-readable form")
470         if canonical in ("renew"):
471             parser.add_option("-l","--as-long-as-possible",dest='alap',action='store_true',default=False,
472                               help="renew as long as possible")
473         # registy filter option
474         if canonical in ("list", "show", "remove"):
475             parser.add_option("-t", "--type", dest="type", metavar="<type>",
476                               default="all",
477                               help="type filter - 2 first chars is enough ([all]|user|slice|authority|node|aggregate)")
478         if canonical in ("show"):
479             parser.add_option("-k","--key",dest="keys",action="append",default=[],
480                               help="specify specific keys to be displayed from record")
481             parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
482                               help="call Resolve without the 'details' option")
483         if canonical in ("resources", "describe"):
484             # rspec version
485             parser.add_option("-r", "--rspec-version", dest="rspec_version", default=DEFAULT_RSPEC_VERSION,
486                               help="schema type and version of resulting RSpec (default:{})".format(DEFAULT_RSPEC_VERSION))
487             # disable/enable cached rspecs
488             parser.add_option("-c", "--current", dest="current", default=False,
489                               action="store_true",  
490                               help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
491             # display formats
492             parser.add_option("-f", "--format", dest="format", type="choice",
493                              help="display format ([xml]|dns|ip)", default="xml",
494                              choices=("xml", "dns", "ip"))
495             #panos: a new option to define the type of information about resources a user is interested in
496             parser.add_option("-i", "--info", dest="info",
497                                 help="optional component information", default=None)
498             # a new option to retrieve or not reservation-oriented RSpecs (leases)
499             parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
500                                 help="Retrieve or not reservation-oriented RSpecs ([resources]|leases|all)",
501                                 choices=("all", "resources", "leases"), default="resources")
502
503
504         if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
505            parser.add_option("-o", "--output", dest="file",
506                             help="output XML to file", metavar="FILE", default=None)
507
508         if canonical in ("show", "list"):
509            parser.add_option("-f", "--format", dest="format", type="choice",
510                              help="display format ([text]|xml)", default="text",
511                              choices=("text", "xml"))
512
513            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
514                              help="output file format ([xml]|xmllist|hrnlist)", default="xml",
515                              choices=("xml", "xmllist", "hrnlist"))
516         if canonical == 'list':
517            parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
518                              help="list all child records", default=False)
519            parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
520                              help="gives details, like user keys", default=False)
521         if canonical in ("delegate"):
522            parser.add_option("-u", "--user",
523                              action="store_true", dest="delegate_user", default=False,
524                              help="delegate your own credentials; default if no other option is provided")
525            parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
526                              metavar="slice_hrn", help="delegate cred. for slice HRN")
527            parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
528                              metavar='auth_hrn', help="delegate cred for auth HRN")
529            # this primarily is a shorthand for -A my_hrn^
530            parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
531                              help="delegate your PI credentials, so s.t. like -A your_hrn^")
532            parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
533                              help="""by default the mandatory argument is expected to be a user, 
534 use this if you mean an authority instead""")
535
536         if canonical in ("myslice"):
537             parser.add_option("-p","--password",dest='password',action='store',default=None,
538                               help="specify mainfold password on the command line")
539             parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
540                              metavar="slice_hrn", help="delegate cred. for slice HRN")
541             parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
542                              metavar='auth_hrn', help="delegate PI cred for auth HRN")
543             parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
544             parser.add_option('-b', '--backend',  dest='backend',  help="Override 'backend' from the config file")
545         
546         return parser
547
548         
549     #
550     # Main: parse arguments and dispatch to command
551     #
552     def dispatch(self, command, command_options, command_args):
553         (doc, args_string, example, canonical) = commands_dict[command]
554         method=getattr(self, canonical, None)
555         if not method:
556             print("sfi: unknown command {}".format(command))
557             raise SystemExit("Unknown command {}".format(command))
558         for arg in command_args:
559             if 'help' in arg or arg == '-h':
560                 self.print_help()
561                 sys.exit(1)
562         return method(command_options, command_args)
563
564     def main(self):
565         self.sfi_parser = self.create_parser_global()
566         (options, args) = self.sfi_parser.parse_args()
567         if options.help: 
568             self.print_commands_help(options)
569             sys.exit(1)
570         self.options = options
571
572         self.logger.setLevelFromOptVerbose(self.options.verbose)
573
574         if len(args) <= 0:
575             self.logger.critical("No command given. Use -h for help.")
576             self.print_commands_help(options)
577             return -1
578     
579         # complete / find unique match with command set
580         command_candidates = Candidates (commands_list)
581         input = args[0]
582         command = command_candidates.only_match(input)
583         if not command:
584             self.print_commands_help(options)
585             sys.exit(1)
586         # second pass options parsing
587         self.command=command
588         self.command_parser = self.create_parser_command(command)
589         (command_options, command_args) = self.command_parser.parse_args(args[1:])
590         if command_options.help:
591             self.print_help()
592             sys.exit(1)
593         self.command_options = command_options
594
595         # allow incoming types on 2 characters only
596         if hasattr(command_options, 'type'):
597             command_options.type = normalize_type(command_options.type)
598             if not command_options.type:
599                 sys.exit(1)
600         
601         self.read_config () 
602         self.bootstrap ()
603         self.logger.debug("Command={}".format(self.command))
604
605         try:
606             retcod = self.dispatch(command, command_options, command_args)
607         except SystemExit:
608             return 1
609         except:
610             self.logger.log_exc ("sfi command {} failed".format(command))
611             return 1
612         return retcod
613     
614     ####################
615     def read_config(self):
616         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
617         shell_config_file  = os.path.join(self.options.sfi_dir,"sfi_config.sh")
618         try:
619             if Config.is_ini(config_file):
620                 config = Config (config_file)
621             else:
622                 # try upgrading from shell config format
623                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
624                 config = Config(fn)
625                 # we need to preload the sections we want parsed 
626                 # from the shell config
627                 config.add_section('sfi')
628                 # sface users should be able to use this same file to configure their stuff
629                 config.add_section('sface')
630                 # manifold users should be able to specify the details 
631                 # of their backend server here for 'sfi myslice'
632                 config.add_section('myslice')
633                 config.load(config_file)
634                 # back up old config
635                 shutil.move(config_file, shell_config_file)
636                 # write new config
637                 config.save(config_file)
638                  
639         except:
640             self.logger.critical("Failed to read configuration file {}".format(config_file))
641             self.logger.info("Make sure to remove the export clauses and to add quotes")
642             if self.options.verbose==0:
643                 self.logger.info("Re-run with -v for more details")
644             else:
645                 self.logger.log_exc("Could not read config file {}".format(config_file))
646             sys.exit(1)
647      
648         self.config_instance=config
649         errors = 0
650         # Set SliceMgr URL
651         if (self.options.sm is not None):
652            self.sm_url = self.options.sm
653         elif hasattr(config, "SFI_SM"):
654            self.sm_url = config.SFI_SM
655         else:
656            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in {}".format(config_file))
657            errors += 1 
658
659         # Set Registry URL
660         if (self.options.registry is not None):
661            self.reg_url = self.options.registry
662         elif hasattr(config, "SFI_REGISTRY"):
663            self.reg_url = config.SFI_REGISTRY
664         else:
665            self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in {}".format(config_file))
666            errors += 1 
667
668         # Set user HRN
669         if (self.options.user is not None):
670            self.user = self.options.user
671         elif hasattr(config, "SFI_USER"):
672            self.user = config.SFI_USER
673         else:
674            self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in {}".format(config_file))
675            errors += 1 
676
677         # Set authority HRN
678         if (self.options.auth is not None):
679            self.authority = self.options.auth
680         elif hasattr(config, "SFI_AUTH"):
681            self.authority = config.SFI_AUTH
682         else:
683            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file))
684            errors += 1 
685
686         self.config_file=config_file
687         if errors:
688            sys.exit(1)
689
690     #
691     # Get various credential and spec files
692     #
693     # Establishes limiting conventions
694     #   - conflates MAs and SAs
695     #   - assumes last token in slice name is unique
696     #
697     # Bootstraps credentials
698     #   - bootstrap user credential from self-signed certificate
699     #   - bootstrap authority credential from user credential
700     #   - bootstrap slice credential from user credential
701     #
702     
703     # init self-signed cert, user credentials and gid
704     def bootstrap (self):
705         if self.options.verbose:
706             self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url))
707         client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
708                                                logger=self.logger)
709         # if -k is provided, use this to initialize private key
710         if self.options.user_private_key:
711             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
712         else:
713             # trigger legacy compat code if needed 
714             # the name has changed from just <leaf>.pkey to <hrn>.pkey
715             if not os.path.isfile(client_bootstrap.private_key_filename()):
716                 self.logger.info ("private key not found, trying legacy name")
717                 try:
718                     legacy_private_key = os.path.join (self.options.sfi_dir, "{}.pkey"
719                                                        .format(Xrn.unescape(get_leaf(self.user))))
720                     self.logger.debug("legacy_private_key={}"
721                                       .format(legacy_private_key))
722                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
723                     self.logger.info("Copied private key from legacy location {}"
724                                      .format(legacy_private_key))
725                 except:
726                     self.logger.log_exc("Can't find private key ")
727                     sys.exit(1)
728             
729         # make it bootstrap
730         client_bootstrap.bootstrap_my_gid()
731         # extract what's needed
732         self.private_key = client_bootstrap.private_key()
733         self.my_credential_string = client_bootstrap.my_credential_string ()
734         self.my_credential = {'geni_type': 'geni_sfa',
735                               'geni_version': '3', 
736                               'geni_value': self.my_credential_string}
737         self.my_gid = client_bootstrap.my_gid ()
738         self.client_bootstrap = client_bootstrap
739
740
741     def my_authority_credential_string(self):
742         if not self.authority:
743             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
744             sys.exit(-1)
745         return self.client_bootstrap.authority_credential_string (self.authority)
746
747     def authority_credential_string(self, auth_hrn):
748         return self.client_bootstrap.authority_credential_string (auth_hrn)
749
750     def slice_credential_string(self, name):
751         return self.client_bootstrap.slice_credential_string (name)
752
753     def slice_credential(self, name):
754         return {'geni_type': 'geni_sfa',
755                 'geni_version': '3',
756                 'geni_value': self.slice_credential_string(name)}    
757
758     # xxx should be supported by sfaclientbootstrap as well
759     def delegate_cred(self, object_cred, hrn, type='authority'):
760         # the gid and hrn of the object we are delegating
761         if isinstance(object_cred, str):
762             object_cred = Credential(string=object_cred) 
763         object_gid = object_cred.get_gid_object()
764         object_hrn = object_gid.get_hrn()
765     
766         if not object_cred.get_privileges().get_all_delegate():
767             self.logger.error("Object credential {} does not have delegate bit set"
768                               .format(object_hrn))
769             return
770
771         # the delegating user's gid
772         caller_gidfile = self.my_gid()
773   
774         # the gid of the user who will be delegated to
775         delegee_gid = self.client_bootstrap.gid(hrn,type)
776         delegee_hrn = delegee_gid.get_hrn()
777         dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
778         return dcred.save_to_string(save_parents=True)
779      
780     #
781     # Management of the servers
782     # 
783
784     def registry (self):
785         # cache the result
786         if not hasattr (self, 'registry_proxy'):
787             self.logger.info("Contacting Registry at: {}".format(self.reg_url))
788             self.registry_proxy \
789                 =  SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
790                                   timeout=self.options.timeout, verbose=self.options.debug)  
791         return self.registry_proxy
792
793     def sliceapi (self):
794         # cache the result
795         if not hasattr (self, 'sliceapi_proxy'):
796             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
797             if hasattr(self.command_options,'component') and self.command_options.component:
798                 # resolve the hrn at the registry
799                 node_hrn = self.command_options.component
800                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
801                 records = filter_records('node', records)
802                 if not records:
803                     self.logger.warning("No such component:{}".format(opts.component))
804                 record = records[0]
805                 cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT)
806                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
807             else:
808                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
809                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
810                     self.sm_url = 'http://' + self.sm_url
811                 self.logger.info("Contacting Slice Manager at: {}".format(self.sm_url))
812                 self.sliceapi_proxy \
813                     = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
814                                      timeout=self.options.timeout, verbose=self.options.debug)  
815         return self.sliceapi_proxy
816
817     def get_cached_server_version(self, server):
818         # check local cache first
819         cache = None
820         version = None 
821         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
822         cache_key = server.url + "-version"
823         try:
824             cache = Cache(cache_file)
825         except IOError:
826             cache = Cache()
827             self.logger.info("Local cache not found at: {}".format(cache_file))
828
829         if cache:
830             version = cache.get(cache_key)
831
832         if not version: 
833             result = server.GetVersion()
834             version= ReturnValue.get_value(result)
835             # cache version for 20 minutes
836             cache.add(cache_key, version, ttl= 60*20)
837             self.logger.info("Updating cache file {}".format(cache_file))
838             cache.save_to_file(cache_file)
839
840         return version   
841         
842     ### resurrect this temporarily so we can support V1 aggregates for a while
843     def server_supports_options_arg(self, server):
844         """
845         Returns true if server support the optional call_id arg, false otherwise. 
846         """
847         server_version = self.get_cached_server_version(server)
848         result = False
849         # xxx need to rewrite this 
850         if int(server_version.get('geni_api')) >= 2:
851             result = True
852         return result
853
854     def server_supports_call_id_arg(self, server):
855         server_version = self.get_cached_server_version(server)
856         result = False      
857         if 'sfa' in server_version and 'code_tag' in server_version:
858             code_tag = server_version['code_tag']
859             code_tag_parts = code_tag.split("-")
860             version_parts = code_tag_parts[0].split(".")
861             major, minor = version_parts[0], version_parts[1]
862             rev = code_tag_parts[1]
863             if int(major) == 1 and minor == 0 and build >= 22:
864                 result = True
865         return result                 
866
867     ### ois = options if supported
868     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
869     def ois (self, server, option_dict):
870         if self.server_supports_options_arg (server): 
871             return [option_dict]
872         elif self.server_supports_call_id_arg (server):
873             return [ unique_call_id () ]
874         else: 
875             return []
876
877     ### cis = call_id if supported - like ois
878     def cis (self, server):
879         if self.server_supports_call_id_arg (server):
880             return [ unique_call_id ]
881         else:
882             return []
883
884     ######################################## miscell utilities
885     def get_rspec_file(self, rspec):
886        if (os.path.isabs(rspec)):
887           file = rspec
888        else:
889           file = os.path.join(self.options.sfi_dir, rspec)
890        if (os.path.isfile(file)):
891           return file
892        else:
893           self.logger.critical("No such rspec file {}".format(rspec))
894           sys.exit(1)
895     
896     def get_record_file(self, record):
897        if (os.path.isabs(record)):
898           file = record
899        else:
900           file = os.path.join(self.options.sfi_dir, record)
901        if (os.path.isfile(file)):
902           return file
903        else:
904           self.logger.critical("No such registry record file {}".format(record))
905           sys.exit(1)
906
907
908     # helper function to analyze raw output
909     # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
910     def success (self, raw):
911         return_value=ReturnValue (raw)
912         output=ReturnValue.get_output(return_value)
913         # means everything is fine
914         if not output: 
915             return 0
916         # something went wrong
917         print('ERROR:', output)
918         return 1
919
920     #==========================================================================
921     # Following functions implement the commands
922     #
923     # Registry-related commands
924     #==========================================================================
925
926     @declare_command("","")
927     def config (self, options, args):
928         "Display contents of current config"
929         print("# From configuration file {}".format(self.config_file))
930         flags=[ ('sfi', [ ('registry','reg_url'),
931                           ('auth','authority'),
932                           ('user','user'),
933                           ('sm','sm_url'),
934                           ]),
935                 ]
936         if options.myslice:
937             flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
938
939         for (section, tuples) in flags:
940             print("[{}]".format(section))
941             try:
942                 for external_name, internal_name in tuples:
943                     print("{:<20} = {}".format(external_name, getattr(self, internal_name)))
944             except:
945                 for external_name, internal_name in tuples:
946                     varname = "{}_{}".format(section.upper(), external_name.upper())
947                     value = getattr(self.config_instance,varname)
948                     print("{:<20} = {}".format(external_name, value))
949         # xxx should analyze result
950         return 0
951
952     @declare_command("","")
953     def version(self, options, args):
954         """
955         display an SFA server version (GetVersion)
956     or version information about sfi itself
957         """
958         if options.version_local:
959             version=version_core()
960         else:
961             if options.registry_interface:
962                 server=self.registry()
963             else:
964                 server = self.sliceapi()
965             result = server.GetVersion()
966             version = ReturnValue.get_value(result)
967         if self.options.raw:
968             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
969         else:
970             pprinter = PrettyPrinter(indent=4)
971             pprinter.pprint(version)
972         # xxx should analyze result
973         return 0
974
975     @declare_command("authority","")
976     def list(self, options, args):
977         """
978         list entries in named authority registry (List)
979         """
980         if len(args)!= 1:
981             self.print_help()
982             sys.exit(1)
983         hrn = args[0]
984         opts = {}
985         if options.recursive:
986             opts['recursive'] = options.recursive
987         
988         if options.show_credential:
989             show_credentials(self.my_credential_string)
990         try:
991             list = self.registry().List(hrn, self.my_credential_string, options)
992         except IndexError:
993             raise Exception, "Not enough parameters for the 'list' command"
994
995         # filter on person, slice, site, node, etc.
996         # This really should be in the self.filter_records funct def comment...
997         list = filter_records(options.type, list)
998         terminal_render (list, options)
999         if options.file:
1000             save_records_to_file(options.file, list, options.fileformat)
1001         # xxx should analyze result
1002         return 0
1003     
1004     @declare_command("name","")
1005     def show(self, options, args):
1006         """
1007         show details about named registry record (Resolve)
1008         """
1009         if len(args)!= 1:
1010             self.print_help()
1011             sys.exit(1)
1012         hrn = args[0]
1013         # explicitly require Resolve to run in details mode
1014         resolve_options={}
1015         if not options.no_details: resolve_options['details']=True
1016         record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
1017         record_dicts = filter_records(options.type, record_dicts)
1018         if not record_dicts:
1019             self.logger.error("No record of type {}".format(options.type))
1020             return
1021         # user has required to focus on some keys
1022         if options.keys:
1023             def project (record):
1024                 projected={}
1025                 for key in options.keys:
1026                     try: projected[key]=record[key]
1027                     except: pass
1028                 return projected
1029             record_dicts = [ project (record) for record in record_dicts ]
1030         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
1031         for record in records:
1032             if (options.format == "text"):      record.dump(sort=True)  
1033             else:                               print(record.save_as_xml())
1034         if options.file:
1035             save_records_to_file(options.file, record_dicts, options.fileformat)
1036         # xxx should analyze result
1037         return 0
1038     
1039     # this historically was named 'add', it is now 'register' with an alias for legacy
1040     @declare_command("[xml-filename]","",['add'])
1041     def register(self, options, args):
1042         """create new record in registry (Register) 
1043     from command line options (recommended) 
1044     old-school method involving an xml file still supported"""
1045
1046         auth_cred = self.my_authority_credential_string()
1047         if options.show_credential:
1048             show_credentials(auth_cred)
1049         record_dict = {}
1050         if len(args) > 1:
1051             self.print_help()
1052             sys.exit(1)
1053         if len(args)==1:
1054             try:
1055                 record_filepath = args[0]
1056                 rec_file = self.get_record_file(record_filepath)
1057                 record_dict.update(load_record_from_file(rec_file).record_to_dict())
1058             except:
1059                 print("Cannot load record file {}".format(record_filepath))
1060                 sys.exit(1)
1061         if options:
1062             record_dict.update(load_record_from_opts(options).record_to_dict())
1063         # we should have a type by now
1064         if 'type' not in record_dict :
1065             self.print_help()
1066             sys.exit(1)
1067         # this is still planetlab dependent.. as plc will whine without that
1068         # also, it's only for adding
1069         if record_dict['type'] == 'user':
1070             if not 'first_name' in record_dict:
1071                 record_dict['first_name'] = record_dict['hrn']
1072             if 'last_name' not in record_dict:
1073                 record_dict['last_name'] = record_dict['hrn'] 
1074         register = self.registry().Register(record_dict, auth_cred)
1075         # xxx looks like the result here is not ReturnValue-compatible
1076         #return self.success (register)
1077         # xxx should analyze result
1078         return 0
1079     
1080     @declare_command("[xml-filename]","")
1081     def update(self, options, args):
1082         """update record into registry (Update) 
1083     from command line options (recommended) 
1084     old-school method involving an xml file still supported"""
1085         record_dict = {}
1086         if len(args) > 0:
1087             record_filepath = args[0]
1088             rec_file = self.get_record_file(record_filepath)
1089             record_dict.update(load_record_from_file(rec_file).record_to_dict())
1090         if options:
1091             record_dict.update(load_record_from_opts(options).record_to_dict())
1092         # at the very least we need 'type' here
1093         if 'type' not in record_dict or record_dict['type'] is None:
1094             self.print_help()
1095             sys.exit(1)
1096
1097         # don't translate into an object, as this would possibly distort
1098         # user-provided data; e.g. add an 'email' field to Users
1099         if record_dict['type'] in ['user']:
1100             if record_dict['hrn'] == self.user:
1101                 cred = self.my_credential_string
1102             else:
1103                 cred = self.my_authority_credential_string()
1104         elif record_dict['type'] in ['slice']:
1105             try:
1106                 cred = self.slice_credential_string(record_dict['hrn'])
1107             except ServerException, e:
1108                # XXX smbaker -- once we have better error return codes, update this
1109                # to do something better than a string compare
1110                if "Permission error" in e.args[0]:
1111                    cred = self.my_authority_credential_string()
1112                else:
1113                    raise
1114         elif record_dict['type'] in ['authority']:
1115             cred = self.my_authority_credential_string()
1116         elif record_dict['type'] in ['node']:
1117             cred = self.my_authority_credential_string()
1118         else:
1119             raise Exception("unknown record type {}".format(record_dict['type']))
1120         if options.show_credential:
1121             show_credentials(cred)
1122         update = self.registry().Update(record_dict, cred)
1123         # xxx looks like the result here is not ReturnValue-compatible
1124         #return self.success(update)
1125         # xxx should analyze result
1126         return 0
1127   
1128     @declare_command("hrn","")
1129     def remove(self, options, args):
1130         "remove registry record by name (Remove)"
1131         auth_cred = self.my_authority_credential_string()
1132         if len(args)!=1:
1133             self.print_help()
1134             sys.exit(1)
1135         hrn = args[0]
1136         type = options.type 
1137         if type in ['all']:
1138             type = '*'
1139         if options.show_credential:
1140             show_credentials(auth_cred)
1141         remove = self.registry().Remove(hrn, auth_cred, type)
1142         # xxx looks like the result here is not ReturnValue-compatible
1143         #return self.success (remove)
1144         # xxx should analyze result
1145         return 0
1146     
1147     # ==================================================================
1148     # Slice-related commands
1149     # ==================================================================
1150
1151     # show rspec for named slice
1152     @declare_command("","",['discover'])
1153     def resources(self, options, args):
1154         """
1155         discover available resources (ListResources)
1156         """
1157         server = self.sliceapi()
1158
1159         # set creds
1160         creds = [self.my_credential]
1161         if options.delegate:
1162             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1163         if options.show_credential:
1164             show_credentials(creds)
1165
1166         # no need to check if server accepts the options argument since the options has
1167         # been a required argument since v1 API
1168         api_options = {}
1169         # always send call_id to v2 servers
1170         api_options ['call_id'] = unique_call_id()
1171         # ask for cached value if available
1172         api_options ['cached'] = True
1173         if options.info:
1174             api_options['info'] = options.info
1175         if options.list_leases:
1176             api_options['list_leases'] = options.list_leases
1177         if options.current:
1178             if options.current == True:
1179                 api_options['cached'] = False
1180             else:
1181                 api_options['cached'] = True
1182         if options.rspec_version:
1183             version_manager = VersionManager()
1184             server_version = self.get_cached_server_version(server)
1185             if 'sfa' in server_version:
1186                 # just request the version the client wants
1187                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1188             else:
1189                 api_options['geni_rspec_version'] = {'type': options.rspec_version}
1190         else:
1191             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1192
1193         list_resources = server.ListResources (creds, api_options)
1194         value = ReturnValue.get_value(list_resources)
1195         if self.options.raw:
1196             save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
1197         if options.file is not None:
1198             save_rspec_to_file(value, options.file)
1199         if (self.options.raw is None) and (options.file is None):
1200             display_rspec(value, options.format)
1201         return self.success(list_resources)
1202
1203     @declare_command("slice_hrn","")
1204     def describe(self, options, args):
1205         """
1206         shows currently allocated/provisioned resources 
1207     of the named slice or set of slivers (Describe) 
1208         """
1209         server = self.sliceapi()
1210
1211         # set creds
1212         creds = [self.slice_credential(args[0])]
1213         if options.delegate:
1214             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1215         if options.show_credential:
1216             show_credentials(creds)
1217
1218         api_options = {'call_id': unique_call_id(),
1219                        'cached': True,
1220                        'info': options.info,
1221                        'list_leases': options.list_leases,
1222                        'geni_rspec_version': {'type': 'geni', 'version': '3'},
1223                       }
1224         if options.info:
1225             api_options['info'] = options.info
1226
1227         if options.rspec_version:
1228             version_manager = VersionManager()
1229             server_version = self.get_cached_server_version(server)
1230             if 'sfa' in server_version:
1231                 # just request the version the client wants
1232                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1233             else:
1234                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1235         urn = Xrn(args[0], type='slice').get_urn()
1236         remove_none_fields(api_options) 
1237         describe = server.Describe([urn], creds, api_options)
1238         value = ReturnValue.get_value(describe)
1239         if self.options.raw:
1240             save_raw_to_file(describe, self.options.raw, self.options.rawformat, self.options.rawbanner)
1241         if options.file is not None:
1242             save_rspec_to_file(value['geni_rspec'], options.file)
1243         if (self.options.raw is None) and (options.file is None):
1244             display_rspec(value['geni_rspec'], options.format)
1245         return self.success (describe)
1246
1247     @declare_command("slice_hrn [<sliver_urn>...]","")
1248     def delete(self, options, args):
1249         """
1250         de-allocate and de-provision all or named slivers of the named slice (Delete)
1251         """
1252         server = self.sliceapi()
1253
1254         # slice urn
1255         slice_hrn = args[0]
1256         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1257
1258         if len(args) > 1:
1259             # we have sliver urns
1260             sliver_urns = args[1:]
1261         else:
1262             # we provision all the slivers of the slice
1263             sliver_urns = [slice_urn]
1264
1265         # creds
1266         slice_cred = self.slice_credential(slice_hrn)
1267         creds = [slice_cred]
1268         
1269         # options and call_id when supported
1270         api_options = {}
1271         api_options ['call_id'] = unique_call_id()
1272         if options.show_credential:
1273             show_credentials(creds)
1274         delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1275         value = ReturnValue.get_value(delete)
1276         if self.options.raw:
1277             save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
1278         else:
1279             print(value)
1280         return self.success (delete)
1281
1282     @declare_command("slice_hrn rspec","")
1283     def allocate(self, options, args):
1284         """
1285          allocate resources to the named slice (Allocate)
1286         """
1287         server = self.sliceapi()
1288         server_version = self.get_cached_server_version(server)
1289         if len(args) != 2:
1290             self.print_help()
1291             sys.exit(1)
1292         slice_hrn = args[0]
1293         rspec_file = self.get_rspec_file(args[1])
1294
1295         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1296
1297         # credentials
1298         creds = [self.slice_credential(slice_hrn)]
1299
1300         delegated_cred = None
1301         if server_version.get('interface') == 'slicemgr':
1302             # delegate our cred to the slice manager
1303             # do not delegate cred to slicemgr...not working at the moment
1304             pass
1305             #if server_version.get('hrn'):
1306             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1307             #elif server_version.get('urn'):
1308             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1309
1310         if options.show_credential:
1311             show_credentials(creds)
1312
1313         # rspec
1314         api_options = {}
1315         api_options ['call_id'] = unique_call_id()
1316         # users
1317         sfa_users = []
1318         geni_users = []
1319         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1320         remove_none_fields(slice_records[0])
1321         if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1322             slice_record = slice_records[0]
1323             user_hrns = slice_record['reg-researchers']
1324             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1325             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1326             sfa_users = sfa_users_arg(user_records, slice_record)
1327             geni_users = pg_users_arg(user_records)
1328
1329         api_options['sfa_users'] = sfa_users
1330         api_options['geni_users'] = geni_users
1331
1332         with open(rspec_file) as rspec:
1333             rspec_xml = rspec.read()
1334             allocate = server.Allocate(slice_urn, creds, rspec_xml, api_options)
1335         value = ReturnValue.get_value(allocate)
1336         if self.options.raw:
1337             save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
1338         if options.file is not None:
1339             save_rspec_to_file (value['geni_rspec'], options.file)
1340         if (self.options.raw is None) and (options.file is None):
1341             print(value)
1342         return self.success(allocate)
1343
1344     @declare_command("slice_hrn [<sliver_urn>...]","")
1345     def provision(self, options, args):
1346         """
1347         provision all or named already allocated slivers of the named slice (Provision)
1348         """
1349         server = self.sliceapi()
1350         server_version = self.get_cached_server_version(server)
1351         slice_hrn = args[0]
1352         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1353         if len(args) > 1:
1354             # we have sliver urns
1355             sliver_urns = args[1:]
1356         else:
1357             # we provision all the slivers of the slice
1358             sliver_urns = [slice_urn]
1359
1360         # credentials
1361         creds = [self.slice_credential(slice_hrn)]
1362         delegated_cred = None
1363         if server_version.get('interface') == 'slicemgr':
1364             # delegate our cred to the slice manager
1365             # do not delegate cred to slicemgr...not working at the moment
1366             pass
1367             #if server_version.get('hrn'):
1368             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1369             #elif server_version.get('urn'):
1370             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1371
1372         if options.show_credential:
1373             show_credentials(creds)
1374
1375         api_options = {}
1376         api_options ['call_id'] = unique_call_id()
1377
1378         # set the requtested rspec version
1379         version_manager = VersionManager()
1380         rspec_version = version_manager._get_version('geni', '3').to_dict()
1381         api_options['geni_rspec_version'] = rspec_version
1382
1383         # users
1384         # need to pass along user keys to the aggregate.
1385         # users = [
1386         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1387         #    keys: [<ssh key A>, <ssh key B>]
1388         #  }]
1389         users = []
1390         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1391         if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1392             slice_record = slice_records[0]
1393             user_hrns = slice_record['reg-researchers']
1394             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1395             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1396             users = pg_users_arg(user_records)
1397         
1398         api_options['geni_users'] = users
1399         provision = server.Provision(sliver_urns, creds, api_options)
1400         value = ReturnValue.get_value(provision)
1401         if self.options.raw:
1402             save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
1403         if options.file is not None:
1404             save_rspec_to_file (value['geni_rspec'], options.file)
1405         if (self.options.raw is None) and (options.file is None):
1406             print(value)
1407         return self.success(provision)
1408
1409     @declare_command("slice_hrn","")
1410     def status(self, options, args):
1411         """
1412         retrieve the status of the slivers belonging to the named slice (Status)
1413         """
1414         server = self.sliceapi()
1415
1416         # slice urn
1417         slice_hrn = args[0]
1418         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1419
1420         # creds 
1421         slice_cred = self.slice_credential(slice_hrn)
1422         creds = [slice_cred]
1423
1424         # options and call_id when supported
1425         api_options = {}
1426         api_options['call_id']=unique_call_id()
1427         if options.show_credential:
1428             show_credentials(creds)
1429         status = server.Status([slice_urn], creds, *self.ois(server,api_options))
1430         value = ReturnValue.get_value(status)
1431         if self.options.raw:
1432             save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
1433         else:
1434             print(value)
1435         return self.success (status)
1436
1437     @declare_command("slice_hrn [<sliver_urn>...] action","")
1438     def action(self, options, args):
1439         """
1440         Perform the named operational action on all or named slivers of the named slice
1441         """
1442         server = self.sliceapi()
1443         api_options = {}
1444         # slice urn
1445         slice_hrn = args[0]
1446         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1447         if len(args) > 2:
1448             # we have sliver urns
1449             sliver_urns = args[1:-1]
1450         else:
1451             # we provision all the slivers of the slice
1452             sliver_urns = [slice_urn]
1453         action = args[-1]
1454         # cred
1455         slice_cred = self.slice_credential(args[0])
1456         creds = [slice_cred]
1457         if options.delegate:
1458             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1459             creds.append(delegated_cred)
1460         
1461         perform_action = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1462         value = ReturnValue.get_value(perform_action)
1463         if self.options.raw:
1464             save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
1465         else:
1466             print(value)
1467         return self.success (perform_action)
1468
1469     @declare_command("slice_hrn [<sliver_urn>...] time",
1470                      "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1471                                 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1472                                 "sfi renew onelab.ple.heartbeat +5d",
1473                                 "sfi renew onelab.ple.heartbeat +3w",
1474                                 "sfi renew onelab.ple.heartbeat +2m",]))
1475     def renew(self, options, args):
1476         """
1477         renew slice (Renew)
1478         """
1479         server = self.sliceapi()
1480         if len(args) < 2:
1481             self.print_help()
1482             sys.exit(1)
1483         slice_hrn = args[0]
1484         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1485
1486         if len(args) > 2:
1487             # we have sliver urns
1488             sliver_urns = args[1:-1]
1489         else:
1490             # we provision all the slivers of the slice
1491             sliver_urns = [slice_urn]
1492         input_time = args[-1]
1493
1494         # time: don't try to be smart on the time format, server-side will
1495         # creds
1496         slice_cred = self.slice_credential(args[0])
1497         creds = [slice_cred]
1498         # options and call_id when supported
1499         api_options = {}
1500         api_options['call_id']=unique_call_id()
1501         if options.alap:
1502             api_options['geni_extend_alap']=True
1503         if options.show_credential:
1504             show_credentials(creds)
1505         renew =  server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1506         value = ReturnValue.get_value(renew)
1507         if self.options.raw:
1508             save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
1509         else:
1510             print(value)
1511         return self.success(renew)
1512
1513     @declare_command("slice_hrn","")
1514     def shutdown(self, options, args):
1515         """
1516         shutdown named slice (Shutdown)
1517         """
1518         server = self.sliceapi()
1519         # slice urn
1520         slice_hrn = args[0]
1521         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1522         # creds
1523         slice_cred = self.slice_credential(slice_hrn)
1524         creds = [slice_cred]
1525         shutdown = server.Shutdown(slice_urn, creds)
1526         value = ReturnValue.get_value(shutdown)
1527         if self.options.raw:
1528             save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
1529         else:
1530             print(value)
1531         return self.success (shutdown)
1532
1533     @declare_command("[name]","")
1534     def gid(self, options, args):
1535         """
1536         Create a GID (CreateGid)
1537         """
1538         if len(args) < 1:
1539             self.print_help()
1540             sys.exit(1)
1541         target_hrn = args[0]
1542         my_gid_string = open(self.client_bootstrap.my_gid()).read() 
1543         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1544         if options.file:
1545             filename = options.file
1546         else:
1547             filename = os.sep.join([self.options.sfi_dir, '{}.gid'.format(target_hrn)])
1548         self.logger.info("writing {} gid to {}".format(target_hrn, filename))
1549         GID(string=gid).save_to_file(filename)
1550         # xxx should analyze result
1551         return 0
1552          
1553     ####################
1554     @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1555
1556   will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1557   the set of credentials in the scope for this call would be
1558   (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1559       as per -u/--user
1560   (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1561       as per -p/--pi
1562   (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1563   (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1564       because of the two -s options
1565
1566 """)
1567     def delegate (self, options, args):
1568         """
1569         (locally) create delegate credential for use by given hrn
1570     make sure to check for 'sfi myslice' instead if you plan
1571     on using MySlice
1572         """
1573         if len(args) != 1:
1574             self.print_help()
1575             sys.exit(1)
1576         to_hrn = args[0]
1577         # support for several delegations in the same call
1578         # so first we gather the things to do
1579         tuples = []
1580         for slice_hrn in options.delegate_slices:
1581             message = "{}.slice".format(slice_hrn)
1582             original = self.slice_credential_string(slice_hrn)
1583             tuples.append ( (message, original,) )
1584         if options.delegate_pi:
1585             my_authority=self.authority
1586             message = "{}.pi".format(my_authority)
1587             original = self.my_authority_credential_string()
1588             tuples.append ( (message, original,) )
1589         for auth_hrn in options.delegate_auths:
1590             message = "{}.auth".format(auth_hrn)
1591             original = self.authority_credential_string(auth_hrn)
1592             tuples.append ( (message, original, ) )
1593         # if nothing was specified at all at this point, let's assume -u
1594         if not tuples: options.delegate_user=True
1595         # this user cred
1596         if options.delegate_user:
1597             message = "{}.user".format(self.user)
1598             original = self.my_credential_string
1599             tuples.append ( (message, original, ) )
1600
1601         # default type for beneficial is user unless -A
1602         if options.delegate_to_authority:       to_type='authority'
1603         else:                                   to_type='user'
1604
1605         # let's now handle all this
1606         # it's all in the filenaming scheme
1607         for (message,original) in tuples:
1608             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1609             delegated_credential = Credential (string=delegated_string)
1610             filename = os.path.join(self.options.sfi_dir,
1611                                     "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
1612             delegated_credential.save_to_file(filename, save_parents=True)
1613             self.logger.info("delegated credential for {} to {} and wrote to {}"
1614                              .format(message, to_hrn, filename))
1615     
1616     ####################
1617     @declare_command("","""$ less +/myslice sfi_config
1618 [myslice]
1619 backend  = http://manifold.pl.sophia.inria.fr:7080
1620 # the HRN that myslice uses, so that we are delegating to
1621 delegate = ple.upmc.slicebrowser
1622 # platform - this is a myslice concept
1623 platform = ple
1624 # username - as of this writing (May 2013) a simple login name
1625 username = thierry
1626
1627 $ sfi myslice
1628   will first collect the slices that you are part of, then make sure
1629   all your credentials are up-to-date (read: refresh expired ones)
1630   then compute delegated credentials for user 'ple.upmc.slicebrowser'
1631   and upload them all on myslice backend, using 'platform' and 'user'.
1632   A password will be prompted for the upload part.
1633
1634 $ sfi -v myslice  -- or sfi -vv myslice
1635   same but with more and more verbosity
1636
1637 $ sfi m -b http://mymanifold.foo.com:7080/
1638   is synonym to sfi myslice as no other command starts with an 'm'
1639   and uses a custom backend for this one call
1640 """
1641 ) # declare_command
1642     def myslice (self, options, args):
1643
1644         """ This helper is for refreshing your credentials at myslice; it will
1645     * compute all the slices that you currently have credentials on
1646     * refresh all your credentials (you as a user and pi, your slices)
1647     * upload them to the manifold backend server
1648     for last phase, sfi_config is read to look for the [myslice] section, 
1649     and namely the 'backend', 'delegate' and 'user' settings"""
1650
1651         ##########
1652         if len(args)>0:
1653             self.print_help()
1654             sys.exit(1)
1655         # enable info by default
1656         self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1657         ### the rough sketch goes like this
1658         # (0) produce a p12 file
1659         self.client_bootstrap.my_pkcs12()
1660
1661         # (a) rain check for sufficient config in sfi_config
1662         myslice_dict={}
1663         myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1664         for key in myslice_keys:
1665             value=None
1666             # oct 2013 - I'm finding myself juggling with config files
1667             # so a couple of command-line options can now override config
1668             if hasattr(options,key) and getattr(options,key) is not None:
1669                 value=getattr(options,key)
1670             else:
1671                 full_key="MYSLICE_" + key.upper()
1672                 value=getattr(self.config_instance,full_key,None)
1673             if value:
1674                 myslice_dict[key]=value
1675             else:
1676                 print("Unsufficient config, missing key {} in [myslice] section of sfi_config"
1677                       .format(key))
1678         if len(myslice_dict) != len(myslice_keys):
1679             sys.exit(1)
1680
1681         # (b) figure whether we are PI for the authority where we belong
1682         self.logger.info("Resolving our own id {}".format(self.user))
1683         my_records=self.registry().Resolve(self.user,self.my_credential_string)
1684         if len(my_records) != 1:
1685             print("Cannot Resolve {} -- exiting".format(self.user))
1686             sys.exit(1)
1687         my_record = my_records[0]
1688         my_auths_all = my_record['reg-pi-authorities']
1689         self.logger.info("Found {} authorities that we are PI for".format(len(my_auths_all)))
1690         self.logger.debug("They are {}".format(my_auths_all))
1691         
1692         my_auths = my_auths_all
1693         if options.delegate_auths:
1694             my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1695             self.logger.debug("Restricted to user-provided auths {}".format(my_auths))
1696
1697         # (c) get the set of slices that we are in
1698         my_slices_all=my_record['reg-slices']
1699         self.logger.info("Found {} slices that we are member of".format(len(my_slices_all)))
1700         self.logger.debug("They are: {}".format(my_slices_all))
1701  
1702         my_slices = my_slices_all
1703         # if user provided slices, deal only with these - if they are found
1704         if options.delegate_slices:
1705             my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1706             self.logger.debug("Restricted to user-provided slices: {}".format(my_slices))
1707
1708         # (d) make sure we have *valid* credentials for all these
1709         hrn_credentials=[]
1710         hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1711         for auth_hrn in my_auths:
1712             hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1713         for slice_hrn in my_slices:
1714             hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1715
1716         # (e) check for the delegated version of these
1717         # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever 
1718         # switch to myslice using an authority instead of a user
1719         delegatee_type='user'
1720         delegatee_hrn=myslice_dict['delegate']
1721         hrn_delegated_credentials = []
1722         for (hrn, htype, credential) in hrn_credentials:
1723             delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1724             # save these so user can monitor what she's uploaded
1725             filename = os.path.join ( self.options.sfi_dir,
1726                                       "{}.{}_for_{}.{}.cred"\
1727                                       .format(hrn, htype, delegatee_hrn, delegatee_type))
1728             with file(filename,'w') as f:
1729                 f.write(delegated_credential)
1730             self.logger.debug("(Over)wrote {}".format(filename))
1731             hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1732
1733         # (f) and finally upload them to manifold server
1734         # xxx todo add an option so the password can be set on the command line
1735         # (but *NOT* in the config file) so other apps can leverage this
1736         self.logger.info("Uploading on backend at {}".format(myslice_dict['backend']))
1737         uploader = ManifoldUploader (logger=self.logger,
1738                                      url=myslice_dict['backend'],
1739                                      platform=myslice_dict['platform'],
1740                                      username=myslice_dict['username'],
1741                                      password=options.password)
1742         uploader.prompt_all()
1743         (count_all,count_success)=(0,0)
1744         for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1745             # inspect
1746             inspect=Credential(string=delegated_credential)
1747             expire_datetime=inspect.get_expiration()
1748             message="{} ({}) [exp:{}]".format(hrn, htype, expire_datetime)
1749             if uploader.upload(delegated_credential,message=message):
1750                 count_success+=1
1751             count_all+=1
1752         self.logger.info("Successfully uploaded {}/{} credentials"
1753                          .format(count_success, count_all))
1754
1755         # at first I thought we would want to save these,
1756         # like 'sfi delegate does' but on second thought
1757         # it is probably not helpful as people would not
1758         # need to run 'sfi delegate' at all anymore
1759         if count_success != count_all: sys.exit(1)
1760         # xxx should analyze result
1761         return 0
1762
1763     @declare_command("cred","")
1764     def trusted(self, options, args):
1765         """
1766         return the trusted certs at this interface (get_trusted_certs)
1767         """ 
1768         if options.registry_interface:
1769             server=self.registry()
1770         else:
1771             server = self.sliceapi()
1772         cred = self.my_authority_credential_string()
1773         trusted_certs = server.get_trusted_certs(cred)
1774         if not options.registry_interface:
1775             trusted_certs = ReturnValue.get_value(trusted_certs)
1776
1777         for trusted_cert in trusted_certs:
1778             print("\n===========================================================\n")
1779             gid = GID(string=trusted_cert)
1780             gid.dump()
1781             cert = Certificate(string=trusted_cert)
1782             self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
1783             print("Certificate:\n{}\n\n".format(trusted_cert))
1784         # xxx should analyze result
1785         return 0