add an optional 'options' dict to Resolve (checked that old clients
[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         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1138         if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1139             slice_record = slice_records[0]
1140             user_hrns = slice_record['reg-researchers']
1141             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1142             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1143
1144             if 'sfa' not in server_version:
1145                 users = pg_users_arg(user_records)
1146                 rspec = RSpec(rspec)
1147                 rspec.filter({'component_manager_id': server_version['urn']})
1148                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1149             else:
1150                 users = sfa_users_arg(user_records, slice_record)
1151
1152         # do not append users, keys, or slice tags. Anything
1153         # not contained in this request will be removed from the slice
1154
1155         # CreateSliver has supported the options argument for a while now so it should
1156         # be safe to assume this server support it
1157         api_options = {}
1158         api_options ['append'] = False
1159         api_options ['call_id'] = unique_call_id()
1160         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1161         value = ReturnValue.get_value(result)
1162         if self.options.raw:
1163             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1164         if options.file is not None:
1165             save_rspec_to_file (value, options.file)
1166         if (self.options.raw is None) and (options.file is None):
1167             print value
1168
1169         return value
1170
1171     def delete(self, options, args):
1172         """
1173         delete named slice (DeleteSliver)
1174         """
1175         server = self.sliceapi()
1176
1177         # slice urn
1178         slice_hrn = args[0]
1179         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1180
1181         # creds
1182         slice_cred = self.slice_credential_string(slice_hrn)
1183         creds = [slice_cred]
1184         if options.delegate:
1185             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1186             creds.append(delegated_cred)
1187         
1188         # options and call_id when supported
1189         api_options = {}
1190         api_options ['call_id'] = unique_call_id()
1191         if options.show_credential:
1192             show_credentials(creds)
1193         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1194         value = ReturnValue.get_value(result)
1195         if self.options.raw:
1196             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1197         else:
1198             print value
1199         return value 
1200   
1201     def status(self, options, args):
1202         """
1203         retrieve slice status (SliverStatus)
1204         """
1205         server = self.sliceapi()
1206
1207         # slice urn
1208         slice_hrn = args[0]
1209         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1210
1211         # creds 
1212         slice_cred = self.slice_credential_string(slice_hrn)
1213         creds = [slice_cred]
1214         if options.delegate:
1215             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1216             creds.append(delegated_cred)
1217
1218         # options and call_id when supported
1219         api_options = {}
1220         api_options['call_id']=unique_call_id()
1221         if options.show_credential:
1222             show_credentials(creds)
1223         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1224         value = ReturnValue.get_value(result)
1225         if self.options.raw:
1226             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1227         else:
1228             print value
1229
1230     def start(self, options, args):
1231         """
1232         start named slice (Start)
1233         """
1234         server = self.sliceapi()
1235
1236         # the slice urn
1237         slice_hrn = args[0]
1238         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1239         
1240         # cred
1241         slice_cred = self.slice_credential_string(args[0])
1242         creds = [slice_cred]
1243         if options.delegate:
1244             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1245             creds.append(delegated_cred)
1246         # xxx Thierry - does this not need an api_options as well ?
1247         result = server.Start(slice_urn, creds)
1248         value = ReturnValue.get_value(result)
1249         if self.options.raw:
1250             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1251         else:
1252             print value
1253         return value
1254     
1255     def stop(self, options, args):
1256         """
1257         stop named slice (Stop)
1258         """
1259         server = self.sliceapi()
1260         # slice urn
1261         slice_hrn = args[0]
1262         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1263         # cred
1264         slice_cred = self.slice_credential_string(args[0])
1265         creds = [slice_cred]
1266         if options.delegate:
1267             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1268             creds.append(delegated_cred)
1269         result =  server.Stop(slice_urn, creds)
1270         value = ReturnValue.get_value(result)
1271         if self.options.raw:
1272             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1273         else:
1274             print value
1275         return value
1276     
1277     # reset named slice
1278     def reset(self, options, args):
1279         """
1280         reset named slice (reset_slice)
1281         """
1282         server = self.sliceapi()
1283         # slice urn
1284         slice_hrn = args[0]
1285         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1286         # cred
1287         slice_cred = self.slice_credential_string(args[0])
1288         creds = [slice_cred]
1289         if options.delegate:
1290             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1291             creds.append(delegated_cred)
1292         result = server.reset_slice(creds, slice_urn)
1293         value = ReturnValue.get_value(result)
1294         if self.options.raw:
1295             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1296         else:
1297             print value
1298         return value
1299
1300     def renew(self, options, args):
1301         """
1302         renew slice (RenewSliver)
1303         """
1304         server = self.sliceapi()
1305         if len(args) != 2:
1306             self.print_help()
1307             sys.exit(1)
1308         [ slice_hrn, input_time ] = args
1309         # slice urn    
1310         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1311         # time: don't try to be smart on the time format, server-side will
1312         # creds
1313         slice_cred = self.slice_credential_string(args[0])
1314         creds = [slice_cred]
1315         if options.delegate:
1316             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1317             creds.append(delegated_cred)
1318         # options and call_id when supported
1319         api_options = {}
1320         api_options['call_id']=unique_call_id()
1321         if options.show_credential:
1322             show_credentials(creds)
1323         result =  server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1324         value = ReturnValue.get_value(result)
1325         if self.options.raw:
1326             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1327         else:
1328             print value
1329         return value
1330
1331
1332     def shutdown(self, options, args):
1333         """
1334         shutdown named slice (Shutdown)
1335         """
1336         server = self.sliceapi()
1337         # slice urn
1338         slice_hrn = args[0]
1339         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1340         # creds
1341         slice_cred = self.slice_credential_string(slice_hrn)
1342         creds = [slice_cred]
1343         if options.delegate:
1344             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1345             creds.append(delegated_cred)
1346         result = server.Shutdown(slice_urn, creds)
1347         value = ReturnValue.get_value(result)
1348         if self.options.raw:
1349             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1350         else:
1351             print value
1352         return value         
1353     
1354
1355     def get_ticket(self, options, args):
1356         """
1357         get a ticket for the specified slice
1358         """
1359         server = self.sliceapi()
1360         # slice urn
1361         slice_hrn, rspec_path = args[0], args[1]
1362         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1363         # creds
1364         slice_cred = self.slice_credential_string(slice_hrn)
1365         creds = [slice_cred]
1366         if options.delegate:
1367             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1368             creds.append(delegated_cred)
1369         # rspec
1370         rspec_file = self.get_rspec_file(rspec_path) 
1371         rspec = open(rspec_file).read()
1372         # options and call_id when supported
1373         api_options = {}
1374         api_options['call_id']=unique_call_id()
1375         # get ticket at the server
1376         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1377         # save
1378         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1379         self.logger.info("writing ticket to %s"%file)
1380         ticket = SfaTicket(string=ticket_string)
1381         ticket.save_to_file(filename=file, save_parents=True)
1382
1383     def redeem_ticket(self, options, args):
1384         """
1385         Connects to nodes in a slice and redeems a ticket
1386 (slice hrn is retrieved from the ticket)
1387         """
1388         ticket_file = args[0]
1389         
1390         # get slice hrn from the ticket
1391         # use this to get the right slice credential 
1392         ticket = SfaTicket(filename=ticket_file)
1393         ticket.decode()
1394         ticket_string = ticket.save_to_string(save_parents=True)
1395
1396         slice_hrn = ticket.gidObject.get_hrn()
1397         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1398         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1399         slice_cred = self.slice_credential_string(slice_hrn)
1400         
1401         # get a list of node hostnames from the RSpec 
1402         tree = etree.parse(StringIO(ticket.rspec))
1403         root = tree.getroot()
1404         hostnames = root.xpath("./network/site/node/hostname/text()")
1405         
1406         # create an xmlrpc connection to the component manager at each of these
1407         # components and gall redeem_ticket
1408         connections = {}
1409         for hostname in hostnames:
1410             try:
1411                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1412                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1413                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1414                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1415                                            timeout=self.options.timeout, verbose=self.options.debug)
1416                 server.RedeemTicket(ticket_string, slice_cred)
1417                 self.logger.info("Success")
1418             except socket.gaierror:
1419                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1420             except Exception, e:
1421                 self.logger.log_exc(e.message)
1422         return
1423
1424     def gid(self, options, args):
1425         """
1426         Create a GID (CreateGid)
1427         """
1428         if len(args) < 1:
1429             self.print_help()
1430             sys.exit(1)
1431         target_hrn = args[0]
1432         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1433         if options.file:
1434             filename = options.file
1435         else:
1436             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1437         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1438         GID(string=gid).save_to_file(filename)
1439          
1440
1441     def delegate(self, options, args):
1442         """
1443         (locally) create delegate credential for use by given hrn
1444         """
1445         delegee_hrn = args[0]
1446         if options.delegate_user:
1447             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1448         elif options.delegate_slice:
1449             slice_cred = self.slice_credential_string(options.delegate_slice)
1450             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1451         else:
1452             self.logger.warning("Must specify either --user or --slice <hrn>")
1453             return
1454         delegated_cred = Credential(string=cred)
1455         object_hrn = delegated_cred.get_gid_object().get_hrn()
1456         if options.delegate_user:
1457             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1458                                   + get_leaf(object_hrn) + ".cred")
1459         elif options.delegate_slice:
1460             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1461                                   + get_leaf(object_hrn) + ".cred")
1462
1463         delegated_cred.save_to_file(dest_fn, save_parents=True)
1464
1465         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1466     
1467     def trusted(self, options, args):
1468         """
1469         return uhe trusted certs at this interface (get_trusted_certs)
1470         """ 
1471         trusted_certs = self.registry().get_trusted_certs()
1472         for trusted_cert in trusted_certs:
1473             gid = GID(string=trusted_cert)
1474             gid.dump()
1475             cert = Certificate(string=trusted_cert)
1476             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1477         return 
1478
1479     def config (self, options, args):
1480         "Display contents of current config"
1481         self.show_config()