cosmetic
[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.debug("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                                                logger=self.logger)
613         # if -k is provided, use this to initialize private key
614         if self.options.user_private_key:
615             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
616         else:
617             # trigger legacy compat code if needed 
618             # the name has changed from just <leaf>.pkey to <hrn>.pkey
619             if not os.path.isfile(client_bootstrap.private_key_filename()):
620                 self.logger.info ("private key not found, trying legacy name")
621                 try:
622                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
623                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
624                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
625                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
626                 except:
627                     self.logger.log_exc("Can't find private key ")
628                     sys.exit(1)
629             
630         # make it bootstrap
631         client_bootstrap.bootstrap_my_gid()
632         # extract what's needed
633         self.private_key = client_bootstrap.private_key()
634         self.my_credential_string = client_bootstrap.my_credential_string ()
635         self.my_gid = client_bootstrap.my_gid ()
636         self.client_bootstrap = client_bootstrap
637
638
639     def my_authority_credential_string(self):
640         if not self.authority:
641             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
642             sys.exit(-1)
643         return self.client_bootstrap.authority_credential_string (self.authority)
644
645     def slice_credential_string(self, name):
646         return self.client_bootstrap.slice_credential_string (name)
647
648     # xxx should be supported by sfaclientbootstrap as well
649     def delegate_cred(self, object_cred, hrn, type='authority'):
650         # the gid and hrn of the object we are delegating
651         if isinstance(object_cred, str):
652             object_cred = Credential(string=object_cred) 
653         object_gid = object_cred.get_gid_object()
654         object_hrn = object_gid.get_hrn()
655     
656         if not object_cred.get_privileges().get_all_delegate():
657             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
658             return
659
660         # the delegating user's gid
661         caller_gidfile = self.my_gid()
662   
663         # the gid of the user who will be delegated to
664         delegee_gid = self.client_bootstrap.gid(hrn,type)
665         delegee_hrn = delegee_gid.get_hrn()
666         dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
667         return dcred.save_to_string(save_parents=True)
668      
669     #
670     # Management of the servers
671     # 
672
673     def registry (self):
674         # cache the result
675         if not hasattr (self, 'registry_proxy'):
676             self.logger.info("Contacting Registry at: %s"%self.reg_url)
677             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
678                                                  timeout=self.options.timeout, verbose=self.options.debug)  
679         return self.registry_proxy
680
681     def sliceapi (self):
682         # cache the result
683         if not hasattr (self, 'sliceapi_proxy'):
684             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
685             if hasattr(self.command_options,'component') and self.command_options.component:
686                 # resolve the hrn at the registry
687                 node_hrn = self.command_options.component
688                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
689                 records = filter_records('node', records)
690                 if not records:
691                     self.logger.warning("No such component:%r"% opts.component)
692                 record = records[0]
693                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
694                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
695             else:
696                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
697                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
698                     self.sm_url = 'http://' + self.sm_url
699                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
700                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
701                                                      timeout=self.options.timeout, verbose=self.options.debug)  
702         return self.sliceapi_proxy
703
704     def get_cached_server_version(self, server):
705         # check local cache first
706         cache = None
707         version = None 
708         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
709         cache_key = server.url + "-version"
710         try:
711             cache = Cache(cache_file)
712         except IOError:
713             cache = Cache()
714             self.logger.info("Local cache not found at: %s" % cache_file)
715
716         if cache:
717             version = cache.get(cache_key)
718
719         if not version: 
720             result = server.GetVersion()
721             version= ReturnValue.get_value(result)
722             # cache version for 20 minutes
723             cache.add(cache_key, version, ttl= 60*20)
724             self.logger.info("Updating cache file %s" % cache_file)
725             cache.save_to_file(cache_file)
726
727         return version   
728         
729     ### resurrect this temporarily so we can support V1 aggregates for a while
730     def server_supports_options_arg(self, server):
731         """
732         Returns true if server support the optional call_id arg, false otherwise. 
733         """
734         server_version = self.get_cached_server_version(server)
735         result = False
736         # xxx need to rewrite this 
737         if int(server_version.get('geni_api')) >= 2:
738             result = True
739         return result
740
741     def server_supports_call_id_arg(self, server):
742         server_version = self.get_cached_server_version(server)
743         result = False      
744         if 'sfa' in server_version and 'code_tag' in server_version:
745             code_tag = server_version['code_tag']
746             code_tag_parts = code_tag.split("-")
747             version_parts = code_tag_parts[0].split(".")
748             major, minor = version_parts[0], version_parts[1]
749             rev = code_tag_parts[1]
750             if int(major) == 1 and minor == 0 and build >= 22:
751                 result = True
752         return result                 
753
754     ### ois = options if supported
755     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
756     def ois (self, server, option_dict):
757         if self.server_supports_options_arg (server): 
758             return [option_dict]
759         elif self.server_supports_call_id_arg (server):
760             return [ unique_call_id () ]
761         else: 
762             return []
763
764     ### cis = call_id if supported - like ois
765     def cis (self, server):
766         if self.server_supports_call_id_arg (server):
767             return [ unique_call_id ]
768         else:
769             return []
770
771     ######################################## miscell utilities
772     def get_rspec_file(self, rspec):
773        if (os.path.isabs(rspec)):
774           file = rspec
775        else:
776           file = os.path.join(self.options.sfi_dir, rspec)
777        if (os.path.isfile(file)):
778           return file
779        else:
780           self.logger.critical("No such rspec file %s"%rspec)
781           sys.exit(1)
782     
783     def get_record_file(self, record):
784        if (os.path.isabs(record)):
785           file = record
786        else:
787           file = os.path.join(self.options.sfi_dir, record)
788        if (os.path.isfile(file)):
789           return file
790        else:
791           self.logger.critical("No such registry record file %s"%record)
792           sys.exit(1)
793
794
795     #==========================================================================
796     # Following functions implement the commands
797     #
798     # Registry-related commands
799     #==========================================================================
800
801     def version(self, options, args):
802         """
803         display an SFA server version (GetVersion)
804 or version information about sfi itself
805         """
806         if options.version_local:
807             version=version_core()
808         else:
809             if options.version_registry:
810                 server=self.registry()
811             else:
812                 server = self.sliceapi()
813             result = server.GetVersion()
814             version = ReturnValue.get_value(result)
815         if self.options.raw:
816             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
817         else:
818             pprinter = PrettyPrinter(indent=4)
819             pprinter.pprint(version)
820
821     def list(self, options, args):
822         """
823         list entries in named authority registry (List)
824         """
825         if len(args)!= 1:
826             self.print_help()
827             sys.exit(1)
828         hrn = args[0]
829         opts = {}
830         if options.recursive:
831             opts['recursive'] = options.recursive
832         
833         if options.show_credential:
834             show_credentials(self.my_credential_string)
835         try:
836             list = self.registry().List(hrn, self.my_credential_string, options)
837         except IndexError:
838             raise Exception, "Not enough parameters for the 'list' command"
839
840         # filter on person, slice, site, node, etc.
841         # THis really should be in the self.filter_records funct def comment...
842         list = filter_records(options.type, list)
843         for record in list:
844             print "%s (%s)" % (record['hrn'], record['type'])
845         if options.file:
846             save_records_to_file(options.file, list, options.fileformat)
847         return
848     
849     def show(self, options, args):
850         """
851         show details about named registry record (Resolve)
852         """
853         if len(args)!= 1:
854             self.print_help()
855             sys.exit(1)
856         hrn = args[0]
857         record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
858         record_dicts = filter_records(options.type, record_dicts)
859         if not record_dicts:
860             self.logger.error("No record of type %s"% options.type)
861             return
862         # user has required to focus on some keys
863         if options.keys:
864             def project (record):
865                 projected={}
866                 for key in options.keys:
867                     try: projected[key]=record[key]
868                     except: pass
869                 return projected
870             record_dicts = [ project (record) for record in record_dicts ]
871         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
872         for record in records:
873             if (options.format == "text"):      record.dump(sort=True)  
874             else:                               print record.save_as_xml() 
875         if options.file:
876             save_records_to_file(options.file, record_dicts, options.fileformat)
877         return
878     
879     def add(self, options, args):
880         "add record into registry from xml file (Register)"
881         auth_cred = self.my_authority_credential_string()
882         if options.show_credential:
883             show_credentials(auth_cred)
884         record_dict = {}
885         if len(args) > 0:
886             record_filepath = args[0]
887             rec_file = self.get_record_file(record_filepath)
888             record_dict.update(load_record_from_file(rec_file).todict())
889         if options:
890             record_dict.update(load_record_from_opts(options).todict())
891         # we should have a type by now
892         if 'type' not in record_dict :
893             self.print_help()
894             sys.exit(1)
895         # this is still planetlab dependent.. as plc will whine without that
896         # also, it's only for adding
897         if record_dict['type'] == 'user':
898             if not 'first_name' in record_dict:
899                 record_dict['first_name'] = record_dict['hrn']
900             if 'last_name' not in record_dict:
901                 record_dict['last_name'] = record_dict['hrn'] 
902         return self.registry().Register(record_dict, auth_cred)
903     
904     def update(self, options, args):
905         "update record into registry from xml file (Update)"
906         record_dict = {}
907         if len(args) > 0:
908             record_filepath = args[0]
909             rec_file = self.get_record_file(record_filepath)
910             record_dict.update(load_record_from_file(rec_file).todict())
911         if options:
912             record_dict.update(load_record_from_opts(options).todict())
913         # at the very least we need 'type' here
914         if 'type' not in record_dict:
915             self.print_help()
916             sys.exit(1)
917
918         # don't translate into an object, as this would possibly distort
919         # user-provided data; e.g. add an 'email' field to Users
920         if record_dict['type'] == "user":
921             if record_dict['hrn'] == self.user:
922                 cred = self.my_credential_string
923             else:
924                 cred = self.my_authority_credential_string()
925         elif record_dict['type'] in ["slice"]:
926             try:
927                 cred = self.slice_credential_string(record_dict['hrn'])
928             except ServerException, e:
929                # XXX smbaker -- once we have better error return codes, update this
930                # to do something better than a string compare
931                if "Permission error" in e.args[0]:
932                    cred = self.my_authority_credential_string()
933                else:
934                    raise
935         elif record_dict['type'] in ["authority"]:
936             cred = self.my_authority_credential_string()
937         elif record_dict['type'] == 'node':
938             cred = self.my_authority_credential_string()
939         else:
940             raise "unknown record type" + record_dict['type']
941         if options.show_credential:
942             show_credentials(cred)
943         return self.registry().Update(record_dict, cred)
944   
945     def remove(self, options, args):
946         "remove registry record by name (Remove)"
947         auth_cred = self.my_authority_credential_string()
948         if len(args)!=1:
949             self.print_help()
950             sys.exit(1)
951         hrn = args[0]
952         type = options.type 
953         if type in ['all']:
954             type = '*'
955         if options.show_credential:
956             show_credentials(auth_cred)
957         return self.registry().Remove(hrn, auth_cred, type)
958     
959     # ==================================================================
960     # Slice-related commands
961     # ==================================================================
962
963     def slices(self, options, args):
964         "list instantiated slices (ListSlices) - returns urn's"
965         server = self.sliceapi()
966         # creds
967         creds = [self.my_credential_string]
968         if options.delegate:
969             delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
970             creds.append(delegated_cred)  
971         # options and call_id when supported
972         api_options = {}
973         api_options['call_id']=unique_call_id()
974         if options.show_credential:
975             show_credentials(creds)
976         result = server.ListSlices(creds, *self.ois(server,api_options))
977         value = ReturnValue.get_value(result)
978         if self.options.raw:
979             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
980         else:
981             display_list(value)
982         return
983
984     # show rspec for named slice
985     def resources(self, options, args):
986         """
987         with no arg, discover available resources, (ListResources)
988 or with an slice hrn, shows currently provisioned resources
989         """
990         server = self.sliceapi()
991
992         # set creds
993         creds = []
994         if args:
995             creds.append(self.slice_credential_string(args[0]))
996         else:
997             creds.append(self.my_credential_string)
998         if options.delegate:
999             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1000         if options.show_credential:
1001             show_credentials(creds)
1002
1003         # no need to check if server accepts the options argument since the options has
1004         # been a required argument since v1 API
1005         api_options = {}
1006         # always send call_id to v2 servers
1007         api_options ['call_id'] = unique_call_id()
1008         # ask for cached value if available
1009         api_options ['cached'] = True
1010         if args:
1011             hrn = args[0]
1012             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1013         if options.info:
1014             api_options['info'] = options.info
1015         if options.list_leases:
1016             api_options['list_leases'] = options.list_leases
1017         if options.current:
1018             if options.current == True:
1019                 api_options['cached'] = False
1020             else:
1021                 api_options['cached'] = True
1022         if options.rspec_version:
1023             version_manager = VersionManager()
1024             server_version = self.get_cached_server_version(server)
1025             if 'sfa' in server_version:
1026                 # just request the version the client wants
1027                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1028             else:
1029                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1030         else:
1031             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1032         result = server.ListResources (creds, api_options)
1033         value = ReturnValue.get_value(result)
1034         if self.options.raw:
1035             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1036         if options.file is not None:
1037             save_rspec_to_file(value, options.file)
1038         if (self.options.raw is None) and (options.file is None):
1039             display_rspec(value, options.format)
1040
1041         return
1042
1043     def create(self, options, args):
1044         """
1045         create or update named slice with given rspec
1046         """
1047         server = self.sliceapi()
1048
1049         # xxx do we need to check usage (len(args)) ?
1050         # slice urn
1051         slice_hrn = args[0]
1052         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1053
1054         # credentials
1055         creds = [self.slice_credential_string(slice_hrn)]
1056
1057         delegated_cred = None
1058         server_version = self.get_cached_server_version(server)
1059         if server_version.get('interface') == 'slicemgr':
1060             # delegate our cred to the slice manager
1061             # do not delegate cred to slicemgr...not working at the moment
1062             pass
1063             #if server_version.get('hrn'):
1064             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1065             #elif server_version.get('urn'):
1066             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1067
1068         if options.show_credential:
1069             show_credentials(creds)
1070
1071         # rspec
1072         rspec_file = self.get_rspec_file(args[1])
1073         rspec = open(rspec_file).read()
1074
1075         # users
1076         # need to pass along user keys to the aggregate.
1077         # users = [
1078         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1079         #    keys: [<ssh key A>, <ssh key B>]
1080         #  }]
1081         users = []
1082         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1083         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1084             slice_record = slice_records[0]
1085             user_hrns = slice_record['researcher']
1086             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1087             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1088
1089             if 'sfa' not in server_version:
1090                 users = pg_users_arg(user_records)
1091                 rspec = RSpec(rspec)
1092                 rspec.filter({'component_manager_id': server_version['urn']})
1093                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1094             else:
1095                 users = sfa_users_arg(user_records, slice_record)
1096
1097         # do not append users, keys, or slice tags. Anything
1098         # not contained in this request will be removed from the slice
1099
1100         # CreateSliver has supported the options argument for a while now so it should
1101         # be safe to assume this server support it
1102         api_options = {}
1103         api_options ['append'] = False
1104         api_options ['call_id'] = unique_call_id()
1105         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1106         value = ReturnValue.get_value(result)
1107         if self.options.raw:
1108             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1109         if options.file is not None:
1110             save_rspec_to_file (value, options.file)
1111         if (self.options.raw is None) and (options.file is None):
1112             print value
1113
1114         return value
1115
1116     def delete(self, options, args):
1117         """
1118         delete named slice (DeleteSliver)
1119         """
1120         server = self.sliceapi()
1121
1122         # slice urn
1123         slice_hrn = args[0]
1124         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1125
1126         # creds
1127         slice_cred = self.slice_credential_string(slice_hrn)
1128         creds = [slice_cred]
1129         if options.delegate:
1130             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1131             creds.append(delegated_cred)
1132         
1133         # options and call_id when supported
1134         api_options = {}
1135         api_options ['call_id'] = unique_call_id()
1136         if options.show_credential:
1137             show_credentials(creds)
1138         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1139         value = ReturnValue.get_value(result)
1140         if self.options.raw:
1141             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1142         else:
1143             print value
1144         return value 
1145   
1146     def status(self, options, args):
1147         """
1148         retrieve slice status (SliverStatus)
1149         """
1150         server = self.sliceapi()
1151
1152         # slice urn
1153         slice_hrn = args[0]
1154         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1155
1156         # creds 
1157         slice_cred = self.slice_credential_string(slice_hrn)
1158         creds = [slice_cred]
1159         if options.delegate:
1160             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1161             creds.append(delegated_cred)
1162
1163         # options and call_id when supported
1164         api_options = {}
1165         api_options['call_id']=unique_call_id()
1166         if options.show_credential:
1167             show_credentials(creds)
1168         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1169         value = ReturnValue.get_value(result)
1170         if self.options.raw:
1171             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1172         else:
1173             print value
1174
1175     def start(self, options, args):
1176         """
1177         start named slice (Start)
1178         """
1179         server = self.sliceapi()
1180
1181         # the slice urn
1182         slice_hrn = args[0]
1183         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1184         
1185         # cred
1186         slice_cred = self.slice_credential_string(args[0])
1187         creds = [slice_cred]
1188         if options.delegate:
1189             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1190             creds.append(delegated_cred)
1191         # xxx Thierry - does this not need an api_options as well ?
1192         result = server.Start(slice_urn, creds)
1193         value = ReturnValue.get_value(result)
1194         if self.options.raw:
1195             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1196         else:
1197             print value
1198         return value
1199     
1200     def stop(self, options, args):
1201         """
1202         stop named slice (Stop)
1203         """
1204         server = self.sliceapi()
1205         # slice urn
1206         slice_hrn = args[0]
1207         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1208         # cred
1209         slice_cred = self.slice_credential_string(args[0])
1210         creds = [slice_cred]
1211         if options.delegate:
1212             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1213             creds.append(delegated_cred)
1214         result =  server.Stop(slice_urn, creds)
1215         value = ReturnValue.get_value(result)
1216         if self.options.raw:
1217             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1218         else:
1219             print value
1220         return value
1221     
1222     # reset named slice
1223     def reset(self, options, args):
1224         """
1225         reset named slice (reset_slice)
1226         """
1227         server = self.sliceapi()
1228         # slice urn
1229         slice_hrn = args[0]
1230         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1231         # cred
1232         slice_cred = self.slice_credential_string(args[0])
1233         creds = [slice_cred]
1234         if options.delegate:
1235             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1236             creds.append(delegated_cred)
1237         result = server.reset_slice(creds, slice_urn)
1238         value = ReturnValue.get_value(result)
1239         if self.options.raw:
1240             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1241         else:
1242             print value
1243         return value
1244
1245     def renew(self, options, args):
1246         """
1247         renew slice (RenewSliver)
1248         """
1249         server = self.sliceapi()
1250         if len(args) != 2:
1251             self.print_help()
1252             sys.exit(1)
1253         [ slice_hrn, input_time ] = args
1254         # slice urn    
1255         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1256         # time: don't try to be smart on the time format, server-side will
1257         # creds
1258         slice_cred = self.slice_credential_string(args[0])
1259         creds = [slice_cred]
1260         if options.delegate:
1261             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1262             creds.append(delegated_cred)
1263         # options and call_id when supported
1264         api_options = {}
1265         api_options['call_id']=unique_call_id()
1266         if options.show_credential:
1267             show_credentials(creds)
1268         result =  server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1269         value = ReturnValue.get_value(result)
1270         if self.options.raw:
1271             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1272         else:
1273             print value
1274         return value
1275
1276
1277     def shutdown(self, options, args):
1278         """
1279         shutdown named slice (Shutdown)
1280         """
1281         server = self.sliceapi()
1282         # slice urn
1283         slice_hrn = args[0]
1284         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1285         # creds
1286         slice_cred = self.slice_credential_string(slice_hrn)
1287         creds = [slice_cred]
1288         if options.delegate:
1289             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1290             creds.append(delegated_cred)
1291         result = server.Shutdown(slice_urn, creds)
1292         value = ReturnValue.get_value(result)
1293         if self.options.raw:
1294             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1295         else:
1296             print value
1297         return value         
1298     
1299
1300     def get_ticket(self, options, args):
1301         """
1302         get a ticket for the specified slice
1303         """
1304         server = self.sliceapi()
1305         # slice urn
1306         slice_hrn, rspec_path = args[0], args[1]
1307         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1308         # creds
1309         slice_cred = self.slice_credential_string(slice_hrn)
1310         creds = [slice_cred]
1311         if options.delegate:
1312             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1313             creds.append(delegated_cred)
1314         # rspec
1315         rspec_file = self.get_rspec_file(rspec_path) 
1316         rspec = open(rspec_file).read()
1317         # options and call_id when supported
1318         api_options = {}
1319         api_options['call_id']=unique_call_id()
1320         # get ticket at the server
1321         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1322         # save
1323         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1324         self.logger.info("writing ticket to %s"%file)
1325         ticket = SfaTicket(string=ticket_string)
1326         ticket.save_to_file(filename=file, save_parents=True)
1327
1328     def redeem_ticket(self, options, args):
1329         """
1330         Connects to nodes in a slice and redeems a ticket
1331 (slice hrn is retrieved from the ticket)
1332         """
1333         ticket_file = args[0]
1334         
1335         # get slice hrn from the ticket
1336         # use this to get the right slice credential 
1337         ticket = SfaTicket(filename=ticket_file)
1338         ticket.decode()
1339         ticket_string = ticket.save_to_string(save_parents=True)
1340
1341         slice_hrn = ticket.gidObject.get_hrn()
1342         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1343         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1344         slice_cred = self.slice_credential_string(slice_hrn)
1345         
1346         # get a list of node hostnames from the RSpec 
1347         tree = etree.parse(StringIO(ticket.rspec))
1348         root = tree.getroot()
1349         hostnames = root.xpath("./network/site/node/hostname/text()")
1350         
1351         # create an xmlrpc connection to the component manager at each of these
1352         # components and gall redeem_ticket
1353         connections = {}
1354         for hostname in hostnames:
1355             try:
1356                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1357                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1358                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1359                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1360                                            timeout=self.options.timeout, verbose=self.options.debug)
1361                 server.RedeemTicket(ticket_string, slice_cred)
1362                 self.logger.info("Success")
1363             except socket.gaierror:
1364                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1365             except Exception, e:
1366                 self.logger.log_exc(e.message)
1367         return
1368
1369     def gid(self, options, args):
1370         """
1371         Create a GID (CreateGid)
1372         """
1373         if len(args) < 1:
1374             self.print_help()
1375             sys.exit(1)
1376         target_hrn = args[0]
1377         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1378         if options.file:
1379             filename = options.file
1380         else:
1381             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1382         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1383         GID(string=gid).save_to_file(filename)
1384          
1385
1386     def delegate(self, options, args):
1387         """
1388         (locally) create delegate credential for use by given hrn
1389         """
1390         delegee_hrn = args[0]
1391         if options.delegate_user:
1392             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1393         elif options.delegate_slice:
1394             slice_cred = self.slice_credential_string(options.delegate_slice)
1395             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1396         else:
1397             self.logger.warning("Must specify either --user or --slice <hrn>")
1398             return
1399         delegated_cred = Credential(string=cred)
1400         object_hrn = delegated_cred.get_gid_object().get_hrn()
1401         if options.delegate_user:
1402             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1403                                   + get_leaf(object_hrn) + ".cred")
1404         elif options.delegate_slice:
1405             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1406                                   + get_leaf(object_hrn) + ".cred")
1407
1408         delegated_cred.save_to_file(dest_fn, save_parents=True)
1409
1410         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1411     
1412     def trusted(self, options, args):
1413         """
1414         return uhe trusted certs at this interface (get_trusted_certs)
1415         """ 
1416         trusted_certs = self.registry().get_trusted_certs()
1417         for trusted_cert in trusted_certs:
1418             gid = GID(string=trusted_cert)
1419             gid.dump()
1420             cert = Certificate(string=trusted_cert)
1421             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1422         return 
1423
1424     def config (self, options, args):
1425         "Display contents of current config"
1426         self.show_config()