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