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