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