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