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