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