renamed sfi commands create_gid -> gid & get_trusted_certs -> trusted
[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 from lxml import etree
17 from StringIO import StringIO
18 from optparse import OptionParser
19 from pprint import PrettyPrinter
20
21 from sfa.trust.certificate import Keypair, Certificate
22 from sfa.trust.gid import GID
23 from sfa.trust.credential import Credential
24 from sfa.trust.sfaticket import SfaTicket
25
26 from sfa.util.faults import SfaInvalidArgument
27 from sfa.util.sfalogging import sfi_logger
28 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
29 from sfa.util.config import Config
30 from sfa.util.version import version_core
31 from sfa.util.cache import Cache
32
33 from sfa.storage.record import Record
34
35 from sfa.rspecs.rspec import RSpec
36 from sfa.rspecs.rspec_converter import RSpecConverter
37 from sfa.rspecs.version_manager import VersionManager
38
39 from sfa.client.sfaclientlib import SfaClientBootstrap
40 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
41 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
42 from sfa.client.return_value import ReturnValue
43 from sfa.client.candidates import Candidates
44
45 CM_PORT=12346
46
47 # utility methods here
48 def optparse_listvalue_callback(option, option_string, value, parser):
49     setattr(parser.values, option.dest, value.split(','))
50
51 # a code fragment that could be helpful for argparse which unfortunately is 
52 # available with 2.7 only, so this feels like too strong a requirement for the client side
53 #class ExtraArgAction  (argparse.Action):
54 #    def __call__ (self, parser, namespace, values, option_string=None):
55 # would need a try/except of course
56 #        (k,v)=values.split('=')
57 #        d=getattr(namespace,self.dest)
58 #        d[k]=v
59 #####
60 #parser.add_argument ("-X","--extra",dest='extras', default={}, action=ExtraArgAction,
61 #                     help="set extra flags, testbed dependent, e.g. --extra enabled=true")
62     
63 def optparse_dictvalue_callback (option, option_string, value, parser):
64     try:
65         (k,v)=value.split('=',1)
66         d=getattr(parser.values, option.dest)
67         d[k]=v
68     except:
69         parser.print_help()
70         sys.exit(1)
71
72 # display methods
73 def display_rspec(rspec, format='rspec'):
74     if format in ['dns']:
75         tree = etree.parse(StringIO(rspec))
76         root = tree.getroot()
77         result = root.xpath("./network/site/node/hostname/text()")
78     elif format in ['ip']:
79         # The IP address is not yet part of the new RSpec
80         # so this doesn't do anything yet.
81         tree = etree.parse(StringIO(rspec))
82         root = tree.getroot()
83         result = root.xpath("./network/site/node/ipv4/text()")
84     else:
85         result = rspec
86
87     print result
88     return
89
90 def display_list(results):
91     for result in results:
92         print result
93
94 def display_records(recordList, dump=False):
95     ''' Print all fields in the record'''
96     for record in recordList:
97         display_record(record, dump)
98
99 def display_record(record, dump=False):
100     if dump:
101         record.dump(sort=True)
102     else:
103         info = record.getdict()
104         print "%s (%s)" % (info['hrn'], info['type'])
105     return
106
107
108 def filter_records(type, records):
109     filtered_records = []
110     for record in records:
111         if (record['type'] == type) or (type == "all"):
112             filtered_records.append(record)
113     return filtered_records
114
115
116 def credential_printable (credential_string):
117     credential=Credential(string=credential_string)
118     result=""
119     result += credential.get_summary_tostring()
120     result += "\n"
121     rights = credential.get_privileges()
122     result += "rights=%s"%rights
123     result += "\n"
124     return result
125
126 def show_credentials (cred_s):
127     if not isinstance (cred_s,list): cred_s = [cred_s]
128     for cred in cred_s:
129         print "Using Credential %s"%credential_printable(cred)
130
131 # save methods
132 def save_raw_to_file(var, filename, format="text", banner=None):
133     if filename == "-":
134         # if filename is "-", send it to stdout
135         f = sys.stdout
136     else:
137         f = open(filename, "w")
138     if banner:
139         f.write(banner+"\n")
140     if format == "text":
141         f.write(str(var))
142     elif format == "pickled":
143         f.write(pickle.dumps(var))
144     elif format == "json":
145         if hasattr(json, "dumps"):
146             f.write(json.dumps(var))   # python 2.6
147         else:
148             f.write(json.write(var))   # python 2.5
149     else:
150         # this should never happen
151         print "unknown output format", format
152     if banner:
153         f.write('\n'+banner+"\n")
154
155 def save_rspec_to_file(rspec, filename):
156     if not filename.endswith(".rspec"):
157         filename = filename + ".rspec"
158     f = open(filename, 'w')
159     f.write(rspec)
160     f.close()
161     return
162
163 def save_records_to_file(filename, record_dicts, format="xml"):
164     if format == "xml":
165         index = 0
166         for record_dict in record_dicts:
167             if index > 0:
168                 save_record_to_file(filename + "." + str(index), record_dict)
169             else:
170                 save_record_to_file(filename, record_dict)
171             index = index + 1
172     elif format == "xmllist":
173         f = open(filename, "w")
174         f.write("<recordlist>\n")
175         for record_dict in record_dicts:
176             record_obj=Record(dict=record_dict)
177             f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
178         f.write("</recordlist>\n")
179         f.close()
180     elif format == "hrnlist":
181         f = open(filename, "w")
182         for record_dict in record_dicts:
183             record_obj=Record(dict=record_dict)
184             f.write(record_obj.hrn + "\n")
185         f.close()
186     else:
187         # this should never happen
188         print "unknown output format", format
189
190 def save_record_to_file(filename, record_dict):
191     record = Record(dict=record_dict)
192     xml = record.save_as_xml()
193     f=codecs.open(filename, encoding='utf-8',mode="w")
194     f.write(xml)
195     f.close()
196     return
197
198 # minimally check a key argument
199 def check_ssh_key (key):
200     good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
201     return re.match(good_ssh_key, key, re.IGNORECASE)
202
203 # load methods
204 def load_record_from_opts(options):
205     record_dict = {}
206     if hasattr(options, 'xrn') and options.xrn:
207         if hasattr(options, 'type') and options.type:
208             xrn = Xrn(options.xrn, options.type)
209         else:
210             xrn = Xrn(options.xrn)
211         record_dict['urn'] = xrn.get_urn()
212         record_dict['hrn'] = xrn.get_hrn()
213         record_dict['type'] = xrn.get_type()
214     if hasattr(options, 'key') and options.key:
215         try:
216             pubkey = open(options.key, 'r').read()
217         except IOError:
218             pubkey = options.key
219         if not check_ssh_key (pubkey):
220             raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
221         record_dict['keys'] = [pubkey]
222     if hasattr(options, 'slices') and options.slices:
223         record_dict['slices'] = options.slices
224     if hasattr(options, 'researchers') and options.researchers:
225         record_dict['researcher'] = options.researchers
226     if hasattr(options, 'email') and options.email:
227         record_dict['email'] = options.email
228     if hasattr(options, 'pis') and options.pis:
229         record_dict['pi'] = options.pis
230
231     # handle extra settings
232     record_dict.update(options.extras)
233     
234     return Record(dict=record_dict)
235
236 def load_record_from_file(filename):
237     f=codecs.open(filename, encoding="utf-8", mode="r")
238     xml_string = f.read()
239     f.close()
240     return Record(xml=xml_string)
241
242
243 import uuid
244 def unique_call_id(): return uuid.uuid4().urn
245
246 class Sfi:
247     
248     # dirty hack to make this class usable from the outside
249     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user', 'user_private_key']
250
251     @staticmethod
252     def default_sfi_dir ():
253         if os.path.isfile("./sfi_config"): 
254             return os.getcwd()
255         else:
256             return os.path.expanduser("~/.sfi/")
257
258     # dummy to meet Sfi's expectations for its 'options' field
259     # i.e. s/t we can do setattr on
260     class DummyOptions:
261         pass
262
263     def __init__ (self,options=None):
264         if options is None: options=Sfi.DummyOptions()
265         for opt in Sfi.required_options:
266             if not hasattr(options,opt): setattr(options,opt,None)
267         if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
268         self.options = options
269         self.user = None
270         self.authority = None
271         self.logger = sfi_logger
272         self.logger.enable_console()
273         self.available_names = [ tuple[0] for tuple in Sfi.available ]
274         self.available_dict = dict (Sfi.available)
275    
276     # tuples command-name expected-args in the order in which they should appear in the help
277     available = [ 
278         ("version", ""),  
279         ("list", "authority"),
280         ("show", "name"),
281         ("add", "record"),
282         ("update", "record"),
283         ("remove", "name"),
284         ("slices", ""),
285         ("resources", "[slice_hrn]"),
286         ("create", "slice_hrn rspec"),
287         ("delete", "slice_hrn"),
288         ("status", "slice_hrn"),
289         ("start", "slice_hrn"),
290         ("stop", "slice_hrn"),
291         ("reset", "slice_hrn"),
292         ("renew", "slice_hrn time"),
293         ("shutdown", "slice_hrn"),
294         ("get_ticket", "slice_hrn rspec"),
295         ("redeem_ticket", "ticket"),
296         ("delegate", "name"),
297         ("gid", "[name]"),
298         ("trusted", "cred"),
299         ("config", ""),
300         ]
301
302     def print_command_help (self, options):
303         verbose=getattr(options,'verbose')
304         format3="%18s %-15s %s"
305         line=80*'-'
306         if not verbose:
307             print format3%("command","cmd_args","description")
308             print line
309         else:
310             print line
311             self.create_parser().print_help()
312         for command in self.available_names:
313             args=self.available_dict[command]
314             method=getattr(self,command,None)
315             doc=""
316             if method: doc=getattr(method,'__doc__',"")
317             if not doc: doc="*** no doc found ***"
318             doc=doc.strip(" \t\n")
319             doc=doc.replace("\n","\n"+35*' ')
320             if verbose:
321                 print line
322             print format3%(command,args,doc)
323             if verbose:
324                 self.create_command_parser(command).print_help()
325
326     def create_command_parser(self, command):
327         if command not in self.available_dict:
328             msg="Invalid command\n"
329             msg+="Commands: "
330             msg += ','.join(self.available_names)            
331             self.logger.critical(msg)
332             sys.exit(2)
333
334         parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
335                                      % (command, self.available_dict[command]))
336
337         if command in ("add", "update"):
338             parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
339             parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
340             parser.add_option('-e', '--email', dest='email', default="",  help="email (mandatory for users)") 
341 # use --extra instead
342 #            parser.add_option('-u', '--url', dest='url', metavar='<url>', default=None, help="URL, useful for slices") 
343 #            parser.add_option('-d', '--description', dest='description', metavar='<description>', 
344 #                              help='Description, useful for slices', default=None)
345             parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file', 
346                               default=None)
347             parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='slice xrns',
348                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
349             parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>', 
350                               help='slice researchers', default='', type="str", action='callback', 
351                               callback=optparse_listvalue_callback)
352             parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Principal Investigators/Project Managers',
353                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
354 # use --extra instead
355 #            parser.add_option('-f', '--firstname', dest='firstname', metavar='<firstname>', help='user first name')
356 #            parser.add_option('-l', '--lastname', dest='lastname', metavar='<lastname>', help='user last name')
357             parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
358                                action="callback", callback=optparse_dictvalue_callback, nargs=1,
359                                help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
360
361         # user specifies remote aggregate/sm/component                          
362         if command in ("resources", "slices", "create", "delete", "start", "stop", 
363                        "restart", "shutdown",  "get_ticket", "renew", "status"):
364             parser.add_option("-d", "--delegate", dest="delegate", default=None, 
365                              action="store_true",
366                              help="Include a credential delegated to the user's root"+\
367                                   "authority in set of credentials for this call")
368
369         # show_credential option
370         if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
371             parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
372                               help="show credential(s) used in human-readable form")
373         # registy filter option
374         if command in ("list", "show", "remove"):
375             parser.add_option("-t", "--type", dest="type", type="choice",
376                             help="type filter ([all]|user|slice|authority|node|aggregate)",
377                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
378                             default="all")
379         if command in ("show"):
380             parser.add_option("-k","--key",dest="keys",action="append",default=[],
381                               help="specify specific keys to be displayed from record")
382         if command in ("resources"):
383             # rspec version
384             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
385                               help="schema type and version of resulting RSpec")
386             # disable/enable cached rspecs
387             parser.add_option("-c", "--current", dest="current", default=False,
388                               action="store_true",  
389                               help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
390             # display formats
391             parser.add_option("-f", "--format", dest="format", type="choice",
392                              help="display format ([xml]|dns|ip)", default="xml",
393                              choices=("xml", "dns", "ip"))
394             #panos: a new option to define the type of information about resources a user is interested in
395             parser.add_option("-i", "--info", dest="info",
396                                 help="optional component information", default=None)
397             # a new option to retreive or not reservation-oriented RSpecs (leases)
398             parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
399                                 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
400                                 choices=("all", "resources", "leases"), default="resources")
401
402
403         # 'create' does return the new rspec, makes sense to save that too
404         if command in ("resources", "show", "list", "gid", 'create'):
405            parser.add_option("-o", "--output", dest="file",
406                             help="output XML to file", metavar="FILE", default=None)
407
408         if command in ("show", "list"):
409            parser.add_option("-f", "--format", dest="format", type="choice",
410                              help="display format ([text]|xml)", default="text",
411                              choices=("text", "xml"))
412
413            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
414                              help="output file format ([xml]|xmllist|hrnlist)", default="xml",
415                              choices=("xml", "xmllist", "hrnlist"))
416         if command == 'list':
417            parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
418                              help="list all child records", default=False)
419         if command in ("delegate"):
420            parser.add_option("-u", "--user",
421                             action="store_true", dest="delegate_user", default=False,
422                             help="delegate user credential")
423            parser.add_option("-s", "--slice", dest="delegate_slice",
424                             help="delegate slice credential", metavar="HRN", default=None)
425         
426         if command in ("version"):
427             parser.add_option("-R","--registry-version",
428                               action="store_true", dest="version_registry", default=False,
429                               help="probe registry version instead of sliceapi")
430             parser.add_option("-l","--local",
431                               action="store_true", dest="version_local", default=False,
432                               help="display version of the local client")
433
434         return parser
435
436         
437     def create_parser(self):
438
439         # Generate command line parser
440         parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
441                              description="Commands: %s"%(" ".join(self.available_names)))
442         parser.add_option("-r", "--registry", dest="registry",
443                          help="root registry", metavar="URL", default=None)
444         parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
445                          help="slice API - in general a SM URL, but can be used to talk to an aggregate")
446         parser.add_option("-R", "--raw", dest="raw", default=None,
447                           help="Save raw, unparsed server response to a file")
448         parser.add_option("", "--rawformat", dest="rawformat", type="choice",
449                           help="raw file format ([text]|pickled|json)", default="text",
450                           choices=("text","pickled","json"))
451         parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
452                           help="text string to write before and after raw output")
453         parser.add_option("-d", "--dir", dest="sfi_dir",
454                          help="config & working directory - default is %default",
455                          metavar="PATH", default=Sfi.default_sfi_dir())
456         parser.add_option("-u", "--user", dest="user",
457                          help="user name", metavar="HRN", default=None)
458         parser.add_option("-a", "--auth", dest="auth",
459                          help="authority name", metavar="HRN", default=None)
460         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
461                          help="verbose mode - cumulative")
462         parser.add_option("-D", "--debug",
463                           action="store_true", dest="debug", default=False,
464                           help="Debug (xml-rpc) protocol messages")
465         # would it make sense to use ~/.ssh/id_rsa as a default here ?
466         parser.add_option("-k", "--private-key",
467                          action="store", dest="user_private_key", default=None,
468                          help="point to the private key file to use if not yet installed in sfi_dir")
469         parser.add_option("-t", "--timeout", dest="timeout", default=None,
470                          help="Amout of time to wait before timing out the request")
471         parser.add_option("-?", "--commands", 
472                          action="store_true", dest="command_help", default=False,
473                          help="one page summary on commands & exit")
474         parser.disable_interspersed_args()
475
476         return parser
477         
478
479     def print_help (self):
480         print "==================== Generic sfi usage"
481         self.sfi_parser.print_help()
482         print "==================== Specific command usage"
483         self.command_parser.print_help()
484
485     #
486     # Main: parse arguments and dispatch to command
487     #
488     def dispatch(self, command, command_options, command_args):
489         return getattr(self, command)(command_options, command_args)
490
491     def main(self):
492         self.sfi_parser = self.create_parser()
493         (options, args) = self.sfi_parser.parse_args()
494         if options.command_help: 
495             self.print_command_help(options)
496             sys.exit(1)
497         self.options = options
498
499         self.logger.setLevelFromOptVerbose(self.options.verbose)
500
501         if len(args) <= 0:
502             self.logger.critical("No command given. Use -h for help.")
503             self.print_command_help(options)
504             return -1
505     
506         # complete / find unique match with command set
507         command_candidates = Candidates (self.available_names)
508         input = args[0]
509         command = command_candidates.only_match(input)
510         if not command:
511             self.print_command_help(options)
512             sys.exit(1)
513         # second pass options parsing
514         self.command_parser = self.create_command_parser(command)
515         (command_options, command_args) = self.command_parser.parse_args(args[1:])
516         self.command_options = command_options
517
518         self.read_config () 
519         self.bootstrap ()
520         self.logger.info("Command=%s" % command)
521
522         try:
523             self.dispatch(command, command_options, command_args)
524         except KeyError:
525             self.logger.critical ("Unknown command %s"%command)
526             sys.exit(1)
527
528         return
529     
530     ####################
531     def read_config(self):
532         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
533         try:
534            config = Config (config_file)
535         except:
536            self.logger.critical("Failed to read configuration file %s"%config_file)
537            self.logger.info("Make sure to remove the export clauses and to add quotes")
538            if self.options.verbose==0:
539                self.logger.info("Re-run with -v for more details")
540            else:
541                self.logger.log_exc("Could not read config file %s"%config_file)
542            sys.exit(1)
543      
544         errors = 0
545         # Set SliceMgr URL
546         if (self.options.sm is not None):
547            self.sm_url = self.options.sm
548         elif hasattr(config, "SFI_SM"):
549            self.sm_url = config.SFI_SM
550         else:
551            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
552            errors += 1 
553
554         # Set Registry URL
555         if (self.options.registry is not None):
556            self.reg_url = self.options.registry
557         elif hasattr(config, "SFI_REGISTRY"):
558            self.reg_url = config.SFI_REGISTRY
559         else:
560            self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
561            errors += 1 
562
563         # Set user HRN
564         if (self.options.user is not None):
565            self.user = self.options.user
566         elif hasattr(config, "SFI_USER"):
567            self.user = config.SFI_USER
568         else:
569            self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
570            errors += 1 
571
572         # Set authority HRN
573         if (self.options.auth is not None):
574            self.authority = self.options.auth
575         elif hasattr(config, "SFI_AUTH"):
576            self.authority = config.SFI_AUTH
577         else:
578            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
579            errors += 1 
580
581         self.config_file=config_file
582         if errors:
583            sys.exit(1)
584
585     def show_config (self):
586         print "From configuration file %s"%self.config_file
587         flags=[ 
588             ('SFI_USER','user'),
589             ('SFI_AUTH','authority'),
590             ('SFI_SM','sm_url'),
591             ('SFI_REGISTRY','reg_url'),
592             ]
593         for (external_name, internal_name) in flags:
594             print "%s='%s'"%(external_name,getattr(self,internal_name))
595
596     #
597     # Get various credential and spec files
598     #
599     # Establishes limiting conventions
600     #   - conflates MAs and SAs
601     #   - assumes last token in slice name is unique
602     #
603     # Bootstraps credentials
604     #   - bootstrap user credential from self-signed certificate
605     #   - bootstrap authority credential from user credential
606     #   - bootstrap slice credential from user credential
607     #
608     
609     # init self-signed cert, user credentials and gid
610     def bootstrap (self):
611         client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
612         # if -k is provided, use this to initialize private key
613         if self.options.user_private_key:
614             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
615         else:
616             # trigger legacy compat code if needed 
617             # the name has changed from just <leaf>.pkey to <hrn>.pkey
618             if not os.path.isfile(client_bootstrap.private_key_filename()):
619                 self.logger.info ("private key not found, trying legacy name")
620                 try:
621                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
622                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
623                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
624                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
625                 except:
626                     self.logger.log_exc("Can't find private key ")
627                     sys.exit(1)
628             
629         # make it bootstrap
630         client_bootstrap.bootstrap_my_gid()
631         # extract what's needed
632         self.private_key = client_bootstrap.private_key()
633         self.my_credential_string = client_bootstrap.my_credential_string ()
634         self.my_gid = client_bootstrap.my_gid ()
635         self.client_bootstrap = client_bootstrap
636
637
638     def my_authority_credential_string(self):
639         if not self.authority:
640             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
641             sys.exit(-1)
642         return self.client_bootstrap.authority_credential_string (self.authority)
643
644     def slice_credential_string(self, name):
645         return self.client_bootstrap.slice_credential_string (name)
646
647     # xxx should be supported by sfaclientbootstrap as well
648     def delegate_cred(self, object_cred, hrn, type='authority'):
649         # the gid and hrn of the object we are delegating
650         if isinstance(object_cred, str):
651             object_cred = Credential(string=object_cred) 
652         object_gid = object_cred.get_gid_object()
653         object_hrn = object_gid.get_hrn()
654     
655         if not object_cred.get_privileges().get_all_delegate():
656             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
657             return
658
659         # the delegating user's gid
660         caller_gidfile = self.my_gid()
661   
662         # the gid of the user who will be delegated to
663         delegee_gid = self.client_bootstrap.gid(hrn,type)
664         delegee_hrn = delegee_gid.get_hrn()
665         dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
666         return dcred.save_to_string(save_parents=True)
667      
668     #
669     # Management of the servers
670     # 
671
672     def registry (self):
673         # cache the result
674         if not hasattr (self, 'registry_proxy'):
675             self.logger.info("Contacting Registry at: %s"%self.reg_url)
676             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
677                                                  timeout=self.options.timeout, verbose=self.options.debug)  
678         return self.registry_proxy
679
680     def sliceapi (self):
681         # cache the result
682         if not hasattr (self, 'sliceapi_proxy'):
683             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
684             if hasattr(self.command_options,'component') and self.command_options.component:
685                 # resolve the hrn at the registry
686                 node_hrn = self.command_options.component
687                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
688                 records = filter_records('node', records)
689                 if not records:
690                     self.logger.warning("No such component:%r"% opts.component)
691                 record = records[0]
692                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
693                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
694             else:
695                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
696                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
697                     self.sm_url = 'http://' + self.sm_url
698                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
699                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
700                                                      timeout=self.options.timeout, verbose=self.options.debug)  
701         return self.sliceapi_proxy
702
703     def get_cached_server_version(self, server):
704         # check local cache first
705         cache = None
706         version = None 
707         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
708         cache_key = server.url + "-version"
709         try:
710             cache = Cache(cache_file)
711         except IOError:
712             cache = Cache()
713             self.logger.info("Local cache not found at: %s" % cache_file)
714
715         if cache:
716             version = cache.get(cache_key)
717
718         if not version: 
719             result = server.GetVersion()
720             version= ReturnValue.get_value(result)
721             # cache version for 20 minutes
722             cache.add(cache_key, version, ttl= 60*20)
723             self.logger.info("Updating cache file %s" % cache_file)
724             cache.save_to_file(cache_file)
725
726         return version   
727         
728     ### resurrect this temporarily so we can support V1 aggregates for a while
729     def server_supports_options_arg(self, server):
730         """
731         Returns true if server support the optional call_id arg, false otherwise. 
732         """
733         server_version = self.get_cached_server_version(server)
734         result = False
735         # xxx need to rewrite this 
736         if int(server_version.get('geni_api')) >= 2:
737             result = True
738         return result
739
740     def server_supports_call_id_arg(self, server):
741         server_version = self.get_cached_server_version(server)
742         result = False      
743         if 'sfa' in server_version and 'code_tag' in server_version:
744             code_tag = server_version['code_tag']
745             code_tag_parts = code_tag.split("-")
746             version_parts = code_tag_parts[0].split(".")
747             major, minor = version_parts[0], version_parts[1]
748             rev = code_tag_parts[1]
749             if int(major) == 1 and minor == 0 and build >= 22:
750                 result = True
751         return result                 
752
753     ### ois = options if supported
754     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
755     def ois (self, server, option_dict):
756         if self.server_supports_options_arg (server): 
757             return [option_dict]
758         elif self.server_supports_call_id_arg (server):
759             return [ unique_call_id () ]
760         else: 
761             return []
762
763     ### cis = call_id if supported - like ois
764     def cis (self, server):
765         if self.server_supports_call_id_arg (server):
766             return [ unique_call_id ]
767         else:
768             return []
769
770     ######################################## miscell utilities
771     def get_rspec_file(self, rspec):
772        if (os.path.isabs(rspec)):
773           file = rspec
774        else:
775           file = os.path.join(self.options.sfi_dir, rspec)
776        if (os.path.isfile(file)):
777           return file
778        else:
779           self.logger.critical("No such rspec file %s"%rspec)
780           sys.exit(1)
781     
782     def get_record_file(self, record):
783        if (os.path.isabs(record)):
784           file = record
785        else:
786           file = os.path.join(self.options.sfi_dir, record)
787        if (os.path.isfile(file)):
788           return file
789        else:
790           self.logger.critical("No such registry record file %s"%record)
791           sys.exit(1)
792
793
794     #==========================================================================
795     # Following functions implement the commands
796     #
797     # Registry-related commands
798     #==========================================================================
799
800     def version(self, options, args):
801         """
802         display an SFA server version (GetVersion)
803 or version information about sfi itself
804         """
805         if options.version_local:
806             version=version_core()
807         else:
808             if options.version_registry:
809                 server=self.registry()
810             else:
811                 server = self.sliceapi()
812             result = server.GetVersion()
813             version = ReturnValue.get_value(result)
814         if self.options.raw:
815             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
816         else:
817             pprinter = PrettyPrinter(indent=4)
818             pprinter.pprint(version)
819
820     def list(self, options, args):
821         """
822         list entries in named authority registry (List)
823         """
824         if len(args)!= 1:
825             self.print_help()
826             sys.exit(1)
827         hrn = args[0]
828         opts = {}
829         if options.recursive:
830             opts['recursive'] = options.recursive
831         
832         if options.show_credential:
833             show_credentials(self.my_credential_string)
834         try:
835             list = self.registry().List(hrn, self.my_credential_string, options)
836         except IndexError:
837             raise Exception, "Not enough parameters for the 'list' command"
838
839         # filter on person, slice, site, node, etc.
840         # THis really should be in the self.filter_records funct def comment...
841         list = filter_records(options.type, list)
842         for record in list:
843             print "%s (%s)" % (record['hrn'], record['type'])
844         if options.file:
845             save_records_to_file(options.file, list, options.fileformat)
846         return
847     
848     def show(self, options, args):
849         """
850         show details about named registry record (Resolve)
851         """
852         if len(args)!= 1:
853             self.print_help()
854             sys.exit(1)
855         hrn = args[0]
856         record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
857         record_dicts = filter_records(options.type, record_dicts)
858         if not record_dicts:
859             self.logger.error("No record of type %s"% options.type)
860             return
861         # user has required to focus on some keys
862         if options.keys:
863             def project (record):
864                 projected={}
865                 for key in options.keys:
866                     try: projected[key]=record[key]
867                     except: pass
868                 return projected
869             record_dicts = [ project (record) for record in record_dicts ]
870         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
871         for record in records:
872             if (options.format == "text"):      record.dump(sort=True)  
873             else:                               print record.save_as_xml() 
874         if options.file:
875             save_records_to_file(options.file, record_dicts, options.fileformat)
876         return
877     
878     def add(self, options, args):
879         "add record into registry from xml file (Register)"
880         auth_cred = self.my_authority_credential_string()
881         if options.show_credential:
882             show_credentials(auth_cred)
883         record_dict = {}
884         if len(args) > 0:
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         if options:
889             record_dict.update(load_record_from_opts(options).todict())
890         # we should have a type by now
891         if 'type' not in record_dict :
892             self.print_help()
893             sys.exit(1)
894         # this is still planetlab dependent.. as plc will whine without that
895         # also, it's only for adding
896         if record_dict['type'] == 'user':
897             if not 'first_name' in record_dict:
898                 record_dict['first_name'] = record_dict['hrn']
899             if 'last_name' not in record_dict:
900                 record_dict['last_name'] = record_dict['hrn'] 
901         return self.registry().Register(record_dict, auth_cred)
902     
903     def update(self, options, args):
904         "update record into registry from xml file (Update)"
905         record_dict = {}
906         if len(args) > 0:
907             record_filepath = args[0]
908             rec_file = self.get_record_file(record_filepath)
909             record_dict.update(load_record_from_file(rec_file).todict())
910         if options:
911             record_dict.update(load_record_from_opts(options).todict())
912         # at the very least we need 'type' here
913         if 'type' not in record_dict:
914             self.print_help()
915             sys.exit(1)
916
917         # don't translate into an object, as this would possibly distort
918         # user-provided data; e.g. add an 'email' field to Users
919         if record_dict['type'] == "user":
920             if record_dict['hrn'] == self.user:
921                 cred = self.my_credential_string
922             else:
923                 cred = self.my_authority_credential_string()
924         elif record_dict['type'] in ["slice"]:
925             try:
926                 cred = self.slice_credential_string(record_dict['hrn'])
927             except ServerException, e:
928                # XXX smbaker -- once we have better error return codes, update this
929                # to do something better than a string compare
930                if "Permission error" in e.args[0]:
931                    cred = self.my_authority_credential_string()
932                else:
933                    raise
934         elif record_dict['type'] in ["authority"]:
935             cred = self.my_authority_credential_string()
936         elif record_dict['type'] == 'node':
937             cred = self.my_authority_credential_string()
938         else:
939             raise "unknown record type" + record_dict['type']
940         if options.show_credential:
941             show_credentials(cred)
942         return self.registry().Update(record_dict, cred)
943   
944     def remove(self, options, args):
945         "remove registry record by name (Remove)"
946         auth_cred = self.my_authority_credential_string()
947         if len(args)!=1:
948             self.print_help()
949             sys.exit(1)
950         hrn = args[0]
951         type = options.type 
952         if type in ['all']:
953             type = '*'
954         if options.show_credential:
955             show_credentials(auth_cred)
956         return self.registry().Remove(hrn, auth_cred, type)
957     
958     # ==================================================================
959     # Slice-related commands
960     # ==================================================================
961
962     def slices(self, options, args):
963         "list instantiated slices (ListSlices) - returns urn's"
964         server = self.sliceapi()
965         # creds
966         creds = [self.my_credential_string]
967         if options.delegate:
968             delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
969             creds.append(delegated_cred)  
970         # options and call_id when supported
971         api_options = {}
972         api_options['call_id']=unique_call_id()
973         if options.show_credential:
974             show_credentials(creds)
975         result = server.ListSlices(creds, *self.ois(server,api_options))
976         value = ReturnValue.get_value(result)
977         if self.options.raw:
978             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
979         else:
980             display_list(value)
981         return
982
983     # show rspec for named slice
984     def resources(self, options, args):
985         """
986         with no arg, discover available resources, (ListResources)
987 or with an slice hrn, shows currently provisioned resources
988         """
989         server = self.sliceapi()
990
991         # set creds
992         creds = []
993         if args:
994             creds.append(self.slice_credential_string(args[0]))
995         else:
996             creds.append(self.my_credential_string)
997         if options.delegate:
998             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
999         if options.show_credential:
1000             show_credentials(creds)
1001
1002         # no need to check if server accepts the options argument since the options has
1003         # been a required argument since v1 API
1004         api_options = {}
1005         # always send call_id to v2 servers
1006         api_options ['call_id'] = unique_call_id()
1007         # ask for cached value if available
1008         api_options ['cached'] = True
1009         if args:
1010             hrn = args[0]
1011             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1012         if options.info:
1013             api_options['info'] = options.info
1014         if options.list_leases:
1015             api_options['list_leases'] = options.list_leases
1016         if options.current:
1017             if options.current == True:
1018                 api_options['cached'] = False
1019             else:
1020                 api_options['cached'] = True
1021         if options.rspec_version:
1022             version_manager = VersionManager()
1023             server_version = self.get_cached_server_version(server)
1024             if 'sfa' in server_version:
1025                 # just request the version the client wants
1026                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1027             else:
1028                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1029         else:
1030             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1031         result = server.ListResources (creds, api_options)
1032         value = ReturnValue.get_value(result)
1033         if self.options.raw:
1034             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1035         if options.file is not None:
1036             save_rspec_to_file(value, options.file)
1037         if (self.options.raw is None) and (options.file is None):
1038             display_rspec(value, options.format)
1039
1040         return
1041
1042     def create(self, options, args):
1043         """
1044         create or update named slice with given rspec
1045         """
1046         server = self.sliceapi()
1047
1048         # xxx do we need to check usage (len(args)) ?
1049         # slice urn
1050         slice_hrn = args[0]
1051         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1052
1053         # credentials
1054         creds = [self.slice_credential_string(slice_hrn)]
1055
1056         delegated_cred = None
1057         server_version = self.get_cached_server_version(server)
1058         if server_version.get('interface') == 'slicemgr':
1059             # delegate our cred to the slice manager
1060             # do not delegate cred to slicemgr...not working at the moment
1061             pass
1062             #if server_version.get('hrn'):
1063             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1064             #elif server_version.get('urn'):
1065             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1066
1067         if options.show_credential:
1068             show_credentials(creds)
1069
1070         # rspec
1071         rspec_file = self.get_rspec_file(args[1])
1072         rspec = open(rspec_file).read()
1073
1074         # users
1075         # need to pass along user keys to the aggregate.
1076         # users = [
1077         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1078         #    keys: [<ssh key A>, <ssh key B>]
1079         #  }]
1080         users = []
1081         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1082         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1083             slice_record = slice_records[0]
1084             user_hrns = slice_record['researcher']
1085             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1086             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1087
1088             if 'sfa' not in server_version:
1089                 users = pg_users_arg(user_records)
1090                 rspec = RSpec(rspec)
1091                 rspec.filter({'component_manager_id': server_version['urn']})
1092                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1093             else:
1094                 users = sfa_users_arg(user_records, slice_record)
1095
1096         # do not append users, keys, or slice tags. Anything
1097         # not contained in this request will be removed from the slice
1098
1099         # CreateSliver has supported the options argument for a while now so it should
1100         # be safe to assume this server support it
1101         api_options = {}
1102         api_options ['append'] = False
1103         api_options ['call_id'] = unique_call_id()
1104         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1105         value = ReturnValue.get_value(result)
1106         if self.options.raw:
1107             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1108         if options.file is not None:
1109             save_rspec_to_file (value, options.file)
1110         if (self.options.raw is None) and (options.file is None):
1111             print value
1112
1113         return value
1114
1115     def delete(self, options, args):
1116         """
1117         delete named slice (DeleteSliver)
1118         """
1119         server = self.sliceapi()
1120
1121         # slice urn
1122         slice_hrn = args[0]
1123         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1124
1125         # creds
1126         slice_cred = self.slice_credential_string(slice_hrn)
1127         creds = [slice_cred]
1128         if options.delegate:
1129             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1130             creds.append(delegated_cred)
1131         
1132         # options and call_id when supported
1133         api_options = {}
1134         api_options ['call_id'] = unique_call_id()
1135         if options.show_credential:
1136             show_credentials(creds)
1137         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1138         value = ReturnValue.get_value(result)
1139         if self.options.raw:
1140             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1141         else:
1142             print value
1143         return value 
1144   
1145     def status(self, options, args):
1146         """
1147         retrieve slice status (SliverStatus)
1148         """
1149         server = self.sliceapi()
1150
1151         # slice urn
1152         slice_hrn = args[0]
1153         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1154
1155         # creds 
1156         slice_cred = self.slice_credential_string(slice_hrn)
1157         creds = [slice_cred]
1158         if options.delegate:
1159             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1160             creds.append(delegated_cred)
1161
1162         # options and call_id when supported
1163         api_options = {}
1164         api_options['call_id']=unique_call_id()
1165         if options.show_credential:
1166             show_credentials(creds)
1167         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1168         value = ReturnValue.get_value(result)
1169         if self.options.raw:
1170             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1171         else:
1172             print value
1173
1174     def start(self, options, args):
1175         """
1176         start named slice (Start)
1177         """
1178         server = self.sliceapi()
1179
1180         # the slice urn
1181         slice_hrn = args[0]
1182         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1183         
1184         # cred
1185         slice_cred = self.slice_credential_string(args[0])
1186         creds = [slice_cred]
1187         if options.delegate:
1188             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1189             creds.append(delegated_cred)
1190         # xxx Thierry - does this not need an api_options as well ?
1191         result = server.Start(slice_urn, creds)
1192         value = ReturnValue.get_value(result)
1193         if self.options.raw:
1194             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1195         else:
1196             print value
1197         return value
1198     
1199     def stop(self, options, args):
1200         """
1201         stop named slice (Stop)
1202         """
1203         server = self.sliceapi()
1204         # slice urn
1205         slice_hrn = args[0]
1206         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1207         # cred
1208         slice_cred = self.slice_credential_string(args[0])
1209         creds = [slice_cred]
1210         if options.delegate:
1211             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1212             creds.append(delegated_cred)
1213         result =  server.Stop(slice_urn, creds)
1214         value = ReturnValue.get_value(result)
1215         if self.options.raw:
1216             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1217         else:
1218             print value
1219         return value
1220     
1221     # reset named slice
1222     def reset(self, options, args):
1223         """
1224         reset named slice (reset_slice)
1225         """
1226         server = self.sliceapi()
1227         # slice urn
1228         slice_hrn = args[0]
1229         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1230         # cred
1231         slice_cred = self.slice_credential_string(args[0])
1232         creds = [slice_cred]
1233         if options.delegate:
1234             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1235             creds.append(delegated_cred)
1236         result = server.reset_slice(creds, slice_urn)
1237         value = ReturnValue.get_value(result)
1238         if self.options.raw:
1239             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1240         else:
1241             print value
1242         return value
1243
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         if options.delegate:
1260             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1261             creds.append(delegated_cred)
1262         # options and call_id when supported
1263         api_options = {}
1264         api_options['call_id']=unique_call_id()
1265         if options.show_credential:
1266             show_credentials(creds)
1267         result =  server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1268         value = ReturnValue.get_value(result)
1269         if self.options.raw:
1270             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1271         else:
1272             print value
1273         return value
1274
1275
1276     def shutdown(self, options, args):
1277         """
1278         shutdown named slice (Shutdown)
1279         """
1280         server = self.sliceapi()
1281         # slice urn
1282         slice_hrn = args[0]
1283         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1284         # creds
1285         slice_cred = self.slice_credential_string(slice_hrn)
1286         creds = [slice_cred]
1287         if options.delegate:
1288             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1289             creds.append(delegated_cred)
1290         result = server.Shutdown(slice_urn, creds)
1291         value = ReturnValue.get_value(result)
1292         if self.options.raw:
1293             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1294         else:
1295             print value
1296         return value         
1297     
1298
1299     def get_ticket(self, options, args):
1300         """
1301         get a ticket for the specified slice
1302         """
1303         server = self.sliceapi()
1304         # slice urn
1305         slice_hrn, rspec_path = args[0], args[1]
1306         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1307         # creds
1308         slice_cred = self.slice_credential_string(slice_hrn)
1309         creds = [slice_cred]
1310         if options.delegate:
1311             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1312             creds.append(delegated_cred)
1313         # rspec
1314         rspec_file = self.get_rspec_file(rspec_path) 
1315         rspec = open(rspec_file).read()
1316         # options and call_id when supported
1317         api_options = {}
1318         api_options['call_id']=unique_call_id()
1319         # get ticket at the server
1320         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1321         # save
1322         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1323         self.logger.info("writing ticket to %s"%file)
1324         ticket = SfaTicket(string=ticket_string)
1325         ticket.save_to_file(filename=file, save_parents=True)
1326
1327     def redeem_ticket(self, options, args):
1328         """
1329         Connects to nodes in a slice and redeems a ticket
1330 (slice hrn is retrieved from the ticket)
1331         """
1332         ticket_file = args[0]
1333         
1334         # get slice hrn from the ticket
1335         # use this to get the right slice credential 
1336         ticket = SfaTicket(filename=ticket_file)
1337         ticket.decode()
1338         ticket_string = ticket.save_to_string(save_parents=True)
1339
1340         slice_hrn = ticket.gidObject.get_hrn()
1341         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1342         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1343         slice_cred = self.slice_credential_string(slice_hrn)
1344         
1345         # get a list of node hostnames from the RSpec 
1346         tree = etree.parse(StringIO(ticket.rspec))
1347         root = tree.getroot()
1348         hostnames = root.xpath("./network/site/node/hostname/text()")
1349         
1350         # create an xmlrpc connection to the component manager at each of these
1351         # components and gall redeem_ticket
1352         connections = {}
1353         for hostname in hostnames:
1354             try:
1355                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1356                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1357                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1358                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1359                                            timeout=self.options.timeout, verbose=self.options.debug)
1360                 server.RedeemTicket(ticket_string, slice_cred)
1361                 self.logger.info("Success")
1362             except socket.gaierror:
1363                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1364             except Exception, e:
1365                 self.logger.log_exc(e.message)
1366         return
1367
1368     def gid(self, options, args):
1369         """
1370         Create a GID (CreateGid)
1371         """
1372         if len(args) < 1:
1373             self.print_help()
1374             sys.exit(1)
1375         target_hrn = args[0]
1376         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1377         if options.file:
1378             filename = options.file
1379         else:
1380             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1381         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1382         GID(string=gid).save_to_file(filename)
1383          
1384
1385     def delegate(self, options, args):
1386         """
1387         (locally) create delegate credential for use by given hrn
1388         """
1389         delegee_hrn = args[0]
1390         if options.delegate_user:
1391             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1392         elif options.delegate_slice:
1393             slice_cred = self.slice_credential_string(options.delegate_slice)
1394             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1395         else:
1396             self.logger.warning("Must specify either --user or --slice <hrn>")
1397             return
1398         delegated_cred = Credential(string=cred)
1399         object_hrn = delegated_cred.get_gid_object().get_hrn()
1400         if options.delegate_user:
1401             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1402                                   + get_leaf(object_hrn) + ".cred")
1403         elif options.delegate_slice:
1404             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1405                                   + get_leaf(object_hrn) + ".cred")
1406
1407         delegated_cred.save_to_file(dest_fn, save_parents=True)
1408
1409         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1410     
1411     def trusted(self, options, args):
1412         """
1413         return uhe trusted certs at this interface (get_trusted_certs)
1414         """ 
1415         trusted_certs = self.registry().get_trusted_certs()
1416         for trusted_cert in trusted_certs:
1417             gid = GID(string=trusted_cert)
1418             gid.dump()
1419             cert = Certificate(string=trusted_cert)
1420             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1421         return 
1422
1423     def config (self, options, args):
1424         "Display contents of current config"
1425         self.show_config()