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