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