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