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