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