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