Merge branch 'upstreammaster'
[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         if command == 'list':
305            parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
306                              help="list all child records", default=False)
307         if command in ("delegate"):
308            parser.add_option("-u", "--user",
309                             action="store_true", dest="delegate_user", default=False,
310                             help="delegate user credential")
311            parser.add_option("-s", "--slice", dest="delegate_slice",
312                             help="delegate slice credential", metavar="HRN", default=None)
313         
314         if command in ("version"):
315             parser.add_option("-R","--registry-version",
316                               action="store_true", dest="version_registry", default=False,
317                               help="probe registry version instead of sliceapi")
318             parser.add_option("-l","--local",
319                               action="store_true", dest="version_local", default=False,
320                               help="display version of the local client")
321
322         return parser
323
324         
325     def create_parser(self):
326
327         # Generate command line parser
328         parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
329                              description="Commands: %s"%(" ".join(self.available_names)))
330         parser.add_option("-r", "--registry", dest="registry",
331                          help="root registry", metavar="URL", default=None)
332         parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
333                          help="slice API - in general a SM URL, but can be used to talk to an aggregate")
334         parser.add_option("-R", "--raw", dest="raw", default=None,
335                           help="Save raw, unparsed server response to a file")
336         parser.add_option("", "--rawformat", dest="rawformat", type="choice",
337                           help="raw file format ([text]|pickled|json)", default="text",
338                           choices=("text","pickled","json"))
339         parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
340                           help="text string to write before and after raw output")
341         parser.add_option("-d", "--dir", dest="sfi_dir",
342                          help="config & working directory - default is %default",
343                          metavar="PATH", default=Sfi.default_sfi_dir())
344         parser.add_option("-u", "--user", dest="user",
345                          help="user name", metavar="HRN", default=None)
346         parser.add_option("-a", "--auth", dest="auth",
347                          help="authority name", metavar="HRN", default=None)
348         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
349                          help="verbose mode - cumulative")
350         parser.add_option("-D", "--debug",
351                           action="store_true", dest="debug", default=False,
352                           help="Debug (xml-rpc) protocol messages")
353         # would it make sense to use ~/.ssh/id_rsa as a default here ?
354         parser.add_option("-k", "--private-key",
355                          action="store", dest="user_private_key", default=None,
356                          help="point to the private key file to use if not yet installed in sfi_dir")
357         parser.add_option("-t", "--timeout", dest="timeout", default=None,
358                          help="Amout of time to wait before timing out the request")
359         parser.add_option("-?", "--commands", 
360                          action="store_true", dest="command_help", default=False,
361                          help="one page summary on commands & exit")
362         parser.disable_interspersed_args()
363
364         return parser
365         
366
367     def print_help (self):
368         self.sfi_parser.print_help()
369         self.command_parser.print_help()
370
371     #
372     # Main: parse arguments and dispatch to command
373     #
374     def dispatch(self, command, command_options, command_args):
375         return getattr(self, command)(command_options, command_args)
376
377     def main(self):
378         self.sfi_parser = self.create_parser()
379         (options, args) = self.sfi_parser.parse_args()
380         if options.command_help: 
381             self.print_command_help(options)
382             sys.exit(1)
383         self.options = options
384
385         self.logger.setLevelFromOptVerbose(self.options.verbose)
386
387         if len(args) <= 0:
388             self.logger.critical("No command given. Use -h for help.")
389             self.print_command_help(options)
390             return -1
391     
392         command = args[0]
393         self.command_parser = self.create_command_parser(command)
394         (command_options, command_args) = self.command_parser.parse_args(args[1:])
395         self.command_options = command_options
396
397         self.read_config () 
398         self.bootstrap ()
399         self.logger.info("Command=%s" % command)
400
401         try:
402             self.dispatch(command, command_options, command_args)
403         except KeyError:
404             self.logger.critical ("Unknown command %s"%command)
405             raise
406             sys.exit(1)
407     
408         return
409     
410     ####################
411     def read_config(self):
412         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
413         try:
414            config = Config (config_file)
415         except:
416            self.logger.critical("Failed to read configuration file %s"%config_file)
417            self.logger.info("Make sure to remove the export clauses and to add quotes")
418            if self.options.verbose==0:
419                self.logger.info("Re-run with -v for more details")
420            else:
421                self.logger.log_exc("Could not read config file %s"%config_file)
422            sys.exit(1)
423      
424         errors = 0
425         # Set SliceMgr URL
426         if (self.options.sm is not None):
427            self.sm_url = self.options.sm
428         elif hasattr(config, "SFI_SM"):
429            self.sm_url = config.SFI_SM
430         else:
431            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
432            errors += 1 
433
434         # Set Registry URL
435         if (self.options.registry is not None):
436            self.reg_url = self.options.registry
437         elif hasattr(config, "SFI_REGISTRY"):
438            self.reg_url = config.SFI_REGISTRY
439         else:
440            self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
441            errors += 1 
442
443         # Set user HRN
444         if (self.options.user is not None):
445            self.user = self.options.user
446         elif hasattr(config, "SFI_USER"):
447            self.user = config.SFI_USER
448         else:
449            self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
450            errors += 1 
451
452         # Set authority HRN
453         if (self.options.auth is not None):
454            self.authority = self.options.auth
455         elif hasattr(config, "SFI_AUTH"):
456            self.authority = config.SFI_AUTH
457         else:
458            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
459            errors += 1 
460
461         self.config_file=config_file
462         if errors:
463            sys.exit(1)
464
465     def show_config (self):
466         print "From configuration file %s"%self.config_file
467         flags=[ 
468             ('SFI_USER','user'),
469             ('SFI_AUTH','authority'),
470             ('SFI_SM','sm_url'),
471             ('SFI_REGISTRY','reg_url'),
472             ]
473         for (external_name, internal_name) in flags:
474             print "%s='%s'"%(external_name,getattr(self,internal_name))
475
476     #
477     # Get various credential and spec files
478     #
479     # Establishes limiting conventions
480     #   - conflates MAs and SAs
481     #   - assumes last token in slice name is unique
482     #
483     # Bootstraps credentials
484     #   - bootstrap user credential from self-signed certificate
485     #   - bootstrap authority credential from user credential
486     #   - bootstrap slice credential from user credential
487     #
488     
489     # init self-signed cert, user credentials and gid
490     def bootstrap (self):
491         client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
492         # if -k is provided, use this to initialize private key
493         if self.options.user_private_key:
494             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
495         else:
496             # trigger legacy compat code if needed 
497             # the name has changed from just <leaf>.pkey to <hrn>.pkey
498             if not os.path.isfile(client_bootstrap.private_key_filename()):
499                 self.logger.info ("private key not found, trying legacy name")
500                 try:
501                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
502                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
503                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
504                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
505                 except:
506                     self.logger.log_exc("Can't find private key ")
507                     sys.exit(1)
508             
509         # make it bootstrap
510         client_bootstrap.bootstrap_my_gid()
511         # extract what's needed
512         self.private_key = client_bootstrap.private_key()
513         self.my_credential_string = client_bootstrap.my_credential_string ()
514         self.my_gid = client_bootstrap.my_gid ()
515         self.client_bootstrap = client_bootstrap
516
517
518     def my_authority_credential_string(self):
519         if not self.authority:
520             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
521             sys.exit(-1)
522         return self.client_bootstrap.authority_credential_string (self.authority)
523
524     def slice_credential_string(self, name):
525         return self.client_bootstrap.slice_credential_string (name)
526
527     # xxx should be supported by sfaclientbootstrap as well
528     def delegate_cred(self, object_cred, hrn, type='authority'):
529         # the gid and hrn of the object we are delegating
530         if isinstance(object_cred, str):
531             object_cred = Credential(string=object_cred) 
532         object_gid = object_cred.get_gid_object()
533         object_hrn = object_gid.get_hrn()
534     
535         if not object_cred.get_privileges().get_all_delegate():
536             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
537             return
538
539         # the delegating user's gid
540         caller_gidfile = self.my_gid()
541   
542         # the gid of the user who will be delegated to
543         delegee_gid = self.client_bootstrap.gid(hrn,type)
544         delegee_hrn = delegee_gid.get_hrn()
545         dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
546         return dcred.save_to_string(save_parents=True)
547      
548     #
549     # Management of the servers
550     # 
551
552     def registry (self):
553         # cache the result
554         if not hasattr (self, 'registry_proxy'):
555             self.logger.info("Contacting Registry at: %s"%self.reg_url)
556             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
557                                                  timeout=self.options.timeout, verbose=self.options.debug)  
558         return self.registry_proxy
559
560     def sliceapi (self):
561         # cache the result
562         if not hasattr (self, 'sliceapi_proxy'):
563             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
564             if hasattr(self.command_options,'component') and self.command_options.component:
565                 # resolve the hrn at the registry
566                 node_hrn = self.command_options.component
567                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
568                 records = filter_records('node', records)
569                 if not records:
570                     self.logger.warning("No such component:%r"% opts.component)
571                 record = records[0]
572                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
573                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
574             else:
575                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
576                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
577                     self.sm_url = 'http://' + self.sm_url
578                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
579                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
580                                                      timeout=self.options.timeout, verbose=self.options.debug)  
581         return self.sliceapi_proxy
582
583     def get_cached_server_version(self, server):
584         # check local cache first
585         cache = None
586         version = None 
587         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
588         cache_key = server.url + "-version"
589         try:
590             cache = Cache(cache_file)
591         except IOError:
592             cache = Cache()
593             self.logger.info("Local cache not found at: %s" % cache_file)
594
595         if cache:
596             version = cache.get(cache_key)
597
598         if not version: 
599             result = server.GetVersion()
600             version= ReturnValue.get_value(result)
601             # cache version for 20 minutes
602             cache.add(cache_key, version, ttl= 60*20)
603             self.logger.info("Updating cache file %s" % cache_file)
604             cache.save_to_file(cache_file)
605
606         return version   
607         
608     ### resurrect this temporarily so we can support V1 aggregates for a while
609     def server_supports_options_arg(self, server):
610         """
611         Returns true if server support the optional call_id arg, false otherwise. 
612         """
613         server_version = self.get_cached_server_version(server)
614         result = False
615         # xxx need to rewrite this 
616         if int(server_version.get('geni_api')) >= 2:
617             result = True
618         return result
619
620     def server_supports_call_id_arg(self, server):
621         server_version = self.get_cached_server_version(server)
622         result = False      
623         if 'sfa' in server_version and 'code_tag' in server_version:
624             code_tag = server_version['code_tag']
625             code_tag_parts = code_tag.split("-")
626             version_parts = code_tag_parts[0].split(".")
627             major, minor = version_parts[0], version_parts[1]
628             rev = code_tag_parts[1]
629             if int(major) == 1 and minor == 0 and build >= 22:
630                 result = True
631         return result                 
632
633     ### ois = options if supported
634     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
635     def ois (self, server, option_dict):
636         if self.server_supports_options_arg (server): 
637             return [option_dict]
638         elif self.server_supports_call_id_arg (server):
639             return [ unique_call_id () ]
640         else: 
641             return []
642
643     ### cis = call_id if supported - like ois
644     def cis (self, server):
645         if self.server_supports_call_id_arg (server):
646             return [ unique_call_id ]
647         else:
648             return []
649
650     ######################################## miscell utilities
651     def get_rspec_file(self, rspec):
652        if (os.path.isabs(rspec)):
653           file = rspec
654        else:
655           file = os.path.join(self.options.sfi_dir, rspec)
656        if (os.path.isfile(file)):
657           return file
658        else:
659           self.logger.critical("No such rspec file %s"%rspec)
660           sys.exit(1)
661     
662     def get_record_file(self, record):
663        if (os.path.isabs(record)):
664           file = record
665        else:
666           file = os.path.join(self.options.sfi_dir, record)
667        if (os.path.isfile(file)):
668           return file
669        else:
670           self.logger.critical("No such registry record file %s"%record)
671           sys.exit(1)
672
673
674     #==========================================================================
675     # Following functions implement the commands
676     #
677     # Registry-related commands
678     #==========================================================================
679
680     def version(self, options, args):
681         """
682         display an SFA server version (GetVersion)
683 or version information about sfi itself
684         """
685         if options.version_local:
686             version=version_core()
687         else:
688             if options.version_registry:
689                 server=self.registry()
690             else:
691                 server = self.sliceapi()
692             result = server.GetVersion()
693             version = ReturnValue.get_value(result)
694         if self.options.raw:
695             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
696         else:
697             pprinter = PrettyPrinter(indent=4)
698             pprinter.pprint(version)
699
700     def list(self, options, args):
701         """
702         list entries in named authority registry (List)
703         """
704         if len(args)!= 1:
705             self.print_help()
706             sys.exit(1)
707         hrn = args[0]
708         opts = {}
709         if options.recursive:
710             opts['recursive'] = options.recursive
711         
712         try:
713             list = self.registry().List(hrn, self.my_credential_string, options)
714         except IndexError:
715             raise Exception, "Not enough parameters for the 'list' command"
716
717         # filter on person, slice, site, node, etc.
718         # THis really should be in the self.filter_records funct def comment...
719         list = filter_records(options.type, list)
720         for record in list:
721             print "%s (%s)" % (record['hrn'], record['type'])
722         if options.file:
723             save_records_to_file(options.file, list, options.fileformat)
724         return
725     
726     def show(self, options, args):
727         """
728         show details about named registry record (Resolve)
729         """
730         if len(args)!= 1:
731             self.print_help()
732             sys.exit(1)
733         hrn = args[0]
734         record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
735         record_dicts = filter_records(options.type, record_dicts)
736         if not record_dicts:
737             self.logger.error("No record of type %s"% options.type)
738         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
739         for record in records:
740             if (options.format == "text"):      record.dump()  
741             else:                               print record.save_as_xml() 
742         if options.file:
743             save_records_to_file(options.file, record_dicts, options.fileformat)
744         return
745     
746     def add(self, options, args):
747         "add record into registry from xml file (Register)"
748         auth_cred = self.my_authority_credential_string()
749         if len(args)!=1:
750             self.print_help()
751             sys.exit(1)
752         record_filepath = args[0]
753         rec_file = self.get_record_file(record_filepath)
754         record = load_record_from_file(rec_file).todict()
755         return self.registry().Register(record, auth_cred)
756     
757     def update(self, options, args):
758         "update record into registry from xml file (Update)"
759         if len(args)!=1:
760             self.print_help()
761             sys.exit(1)
762         rec_file = self.get_record_file(args[0])
763         record = load_record_from_file(rec_file)
764         if record.type == "user":
765             if record.hrn == self.user:
766                 cred = self.my_credential_string
767             else:
768                 cred = self.my_authority_credential_string()
769         elif record.type in ["slice"]:
770             try:
771                 cred = self.slice_credential_string(record.hrn)
772             except ServerException, e:
773                # XXX smbaker -- once we have better error return codes, update this
774                # to do something better than a string compare
775                if "Permission error" in e.args[0]:
776                    cred = self.my_authority_credential_string()
777                else:
778                    raise
779         elif record.type in ["authority"]:
780             cred = self.my_authority_credential_string()
781         elif record.type == 'node':
782             cred = self.my_authority_credential_string()
783         else:
784             raise "unknown record type" + record.type
785         record_dict = record.todict()
786         return self.registry().Update(record_dict, cred)
787   
788     def remove(self, options, args):
789         "remove registry record by name (Remove)"
790         auth_cred = self.my_authority_credential_string()
791         if len(args)!=1:
792             self.print_help()
793             sys.exit(1)
794         hrn = args[0]
795         type = options.type 
796         if type in ['all']:
797             type = '*'
798         return self.registry().Remove(hrn, auth_cred, type)
799     
800     # ==================================================================
801     # Slice-related commands
802     # ==================================================================
803
804     def slices(self, options, args):
805         "list instantiated slices (ListSlices) - returns urn's"
806         server = self.sliceapi()
807         # creds
808         creds = [self.my_credential_string]
809         if options.delegate:
810             delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
811             creds.append(delegated_cred)  
812         # options and call_id when supported
813         api_options = {}
814         api_options['call_id']=unique_call_id()
815         result = server.ListSlices(creds, *self.ois(server,api_options))
816         value = ReturnValue.get_value(result)
817         if self.options.raw:
818             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
819         else:
820             display_list(value)
821         return
822
823     # show rspec for named slice
824     def resources(self, options, args):
825         """
826         with no arg, discover available resources, (ListResources)
827 or with an slice hrn, shows currently provisioned resources
828         """
829         server = self.sliceapi()
830
831         # set creds
832         creds = []
833         if args:
834             creds.append(self.slice_credential_string(args[0]))
835         else:
836             creds.append(self.my_credential_string)
837         if options.delegate:
838             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
839
840         # no need to check if server accepts the options argument since the options has
841         # been a required argument since v1 API
842         api_options = {}
843         # always send call_id to v2 servers
844         api_options ['call_id'] = unique_call_id()
845         # ask for cached value if available
846         api_options ['cached'] = True
847         if args:
848             hrn = args[0]
849             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
850         if options.info:
851             api_options['info'] = options.info
852         if options.current:
853             if options.current == True:
854                 api_options['cached'] = False
855             else:
856                 api_options['cached'] = True
857         if options.rspec_version:
858             version_manager = VersionManager()
859             server_version = self.get_cached_server_version(server)
860             if 'sfa' in server_version:
861                 # just request the version the client wants
862                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
863             else:
864                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
865         else:
866             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
867         result = server.ListResources (creds, api_options)
868         value = ReturnValue.get_value(result)
869         if self.options.raw:
870             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
871         if options.file is not None:
872             save_rspec_to_file(value, options.file)
873         if (self.options.raw is None) and (options.file is None):
874             display_rspec(value, options.format)
875
876         return
877
878     def create(self, options, args):
879         """
880         create or update named slice with given rspec
881         """
882         server = self.sliceapi()
883
884         # xxx do we need to check usage (len(args)) ?
885         # slice urn
886         slice_hrn = args[0]
887         slice_urn = hrn_to_urn(slice_hrn, 'slice')
888
889         # credentials
890         creds = [self.slice_credential_string(slice_hrn)]
891         delegated_cred = None
892         server_version = self.get_cached_server_version(server)
893         if server_version.get('interface') == 'slicemgr':
894             # delegate our cred to the slice manager
895             # do not delegate cred to slicemgr...not working at the moment
896             pass
897             #if server_version.get('hrn'):
898             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
899             #elif server_version.get('urn'):
900             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
901
902         # rspec
903         rspec_file = self.get_rspec_file(args[1])
904         rspec = open(rspec_file).read()
905
906         # users
907         # need to pass along user keys to the aggregate.
908         # users = [
909         #  { urn: urn:publicid:IDN+emulab.net+user+alice
910         #    keys: [<ssh key A>, <ssh key B>]
911         #  }]
912         users = []
913         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
914         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
915             slice_record = slice_records[0]
916             user_hrns = slice_record['researcher']
917             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
918             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
919
920             if 'sfa' not in server_version:
921                 users = pg_users_arg(user_records)
922                 rspec = RSpec(rspec)
923                 rspec.filter({'component_manager_id': server_version['urn']})
924                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
925             else:
926                 users = sfa_users_arg(user_records, slice_record)
927
928         # do not append users, keys, or slice tags. Anything
929         # not contained in this request will be removed from the slice
930
931         # CreateSliver has supported the options argument for a while now so it should
932         # be safe to assume this server support it
933         api_options = {}
934         api_options ['append'] = False
935         api_options ['call_id'] = unique_call_id()
936         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
937         value = ReturnValue.get_value(result)
938         if self.options.raw:
939             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
940         if options.file is not None:
941             save_rspec_to_file (value, options.file)
942         if (self.options.raw is None) and (options.file is None):
943             print value
944
945         return value
946
947     def delete(self, options, args):
948         """
949         delete named slice (DeleteSliver)
950         """
951         server = self.sliceapi()
952
953         # slice urn
954         slice_hrn = args[0]
955         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
956
957         # creds
958         slice_cred = self.slice_credential_string(slice_hrn)
959         creds = [slice_cred]
960         if options.delegate:
961             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
962             creds.append(delegated_cred)
963         
964         # options and call_id when supported
965         api_options = {}
966         api_options ['call_id'] = unique_call_id()
967         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
968         value = ReturnValue.get_value(result)
969         if self.options.raw:
970             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
971         else:
972             print value
973         return value 
974   
975     def status(self, options, args):
976         """
977         retrieve slice status (SliverStatus)
978         """
979         server = self.sliceapi()
980
981         # slice urn
982         slice_hrn = args[0]
983         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
984
985         # creds 
986         slice_cred = self.slice_credential_string(slice_hrn)
987         creds = [slice_cred]
988         if options.delegate:
989             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
990             creds.append(delegated_cred)
991
992         # options and call_id when supported
993         api_options = {}
994         api_options['call_id']=unique_call_id()
995         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
996         value = ReturnValue.get_value(result)
997         if self.options.raw:
998             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
999         else:
1000             print value
1001
1002     def start(self, options, args):
1003         """
1004         start named slice (Start)
1005         """
1006         server = self.sliceapi()
1007
1008         # the slice urn
1009         slice_hrn = args[0]
1010         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1011         
1012         # cred
1013         slice_cred = self.slice_credential_string(args[0])
1014         creds = [slice_cred]
1015         if options.delegate:
1016             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1017             creds.append(delegated_cred)
1018         # xxx Thierry - does this not need an api_options as well ?
1019         result = server.Start(slice_urn, creds)
1020         value = ReturnValue.get_value(result)
1021         if self.options.raw:
1022             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1023         else:
1024             print value
1025         return value
1026     
1027     def stop(self, options, args):
1028         """
1029         stop named slice (Stop)
1030         """
1031         server = self.sliceapi()
1032         # slice urn
1033         slice_hrn = args[0]
1034         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1035         # cred
1036         slice_cred = self.slice_credential_string(args[0])
1037         creds = [slice_cred]
1038         if options.delegate:
1039             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1040             creds.append(delegated_cred)
1041         result =  server.Stop(slice_urn, creds)
1042         value = ReturnValue.get_value(result)
1043         if self.options.raw:
1044             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1045         else:
1046             print value
1047         return value
1048     
1049     # reset named slice
1050     def reset(self, options, args):
1051         """
1052         reset named slice (reset_slice)
1053         """
1054         server = self.sliceapi()
1055         # slice urn
1056         slice_hrn = args[0]
1057         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1058         # cred
1059         slice_cred = self.slice_credential_string(args[0])
1060         creds = [slice_cred]
1061         if options.delegate:
1062             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1063             creds.append(delegated_cred)
1064         result = server.reset_slice(creds, slice_urn)
1065         value = ReturnValue.get_value(result)
1066         if self.options.raw:
1067             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1068         else:
1069             print value
1070         return value
1071
1072     def renew(self, options, args):
1073         """
1074         renew slice (RenewSliver)
1075         """
1076         server = self.sliceapi()
1077         # slice urn    
1078         slice_hrn = args[0]
1079         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1080         # creds
1081         slice_cred = self.slice_credential_string(args[0])
1082         creds = [slice_cred]
1083         if options.delegate:
1084             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1085             creds.append(delegated_cred)
1086         # time
1087         time = args[1]
1088         # options and call_id when supported
1089         api_options = {}
1090         api_options['call_id']=unique_call_id()
1091         result =  server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1092         value = ReturnValue.get_value(result)
1093         if self.options.raw:
1094             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1095         else:
1096             print value
1097         return value
1098
1099
1100     def shutdown(self, options, args):
1101         """
1102         shutdown named slice (Shutdown)
1103         """
1104         server = self.sliceapi()
1105         # slice urn
1106         slice_hrn = args[0]
1107         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1108         # creds
1109         slice_cred = self.slice_credential_string(slice_hrn)
1110         creds = [slice_cred]
1111         if options.delegate:
1112             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1113             creds.append(delegated_cred)
1114         result = server.Shutdown(slice_urn, creds)
1115         value = ReturnValue.get_value(result)
1116         if self.options.raw:
1117             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1118         else:
1119             print value
1120         return value         
1121     
1122
1123     def get_ticket(self, options, args):
1124         """
1125         get a ticket for the specified slice
1126         """
1127         server = self.sliceapi()
1128         # slice urn
1129         slice_hrn, rspec_path = args[0], args[1]
1130         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1131         # creds
1132         slice_cred = self.slice_credential_string(slice_hrn)
1133         creds = [slice_cred]
1134         if options.delegate:
1135             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1136             creds.append(delegated_cred)
1137         # rspec
1138         rspec_file = self.get_rspec_file(rspec_path) 
1139         rspec = open(rspec_file).read()
1140         # options and call_id when supported
1141         api_options = {}
1142         api_options['call_id']=unique_call_id()
1143         # get ticket at the server
1144         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1145         # save
1146         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1147         self.logger.info("writing ticket to %s"%file)
1148         ticket = SfaTicket(string=ticket_string)
1149         ticket.save_to_file(filename=file, save_parents=True)
1150
1151     def redeem_ticket(self, options, args):
1152         """
1153         Connects to nodes in a slice and redeems a ticket
1154 (slice hrn is retrieved from the ticket)
1155         """
1156         ticket_file = args[0]
1157         
1158         # get slice hrn from the ticket
1159         # use this to get the right slice credential 
1160         ticket = SfaTicket(filename=ticket_file)
1161         ticket.decode()
1162         ticket_string = ticket.save_to_string(save_parents=True)
1163
1164         slice_hrn = ticket.gidObject.get_hrn()
1165         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1166         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1167         slice_cred = self.slice_credential_string(slice_hrn)
1168         
1169         # get a list of node hostnames from the RSpec 
1170         tree = etree.parse(StringIO(ticket.rspec))
1171         root = tree.getroot()
1172         hostnames = root.xpath("./network/site/node/hostname/text()")
1173         
1174         # create an xmlrpc connection to the component manager at each of these
1175         # components and gall redeem_ticket
1176         connections = {}
1177         for hostname in hostnames:
1178             try:
1179                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1180                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1181                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1182                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1183                                            timeout=self.options.timeout, verbose=self.options.debug)
1184                 server.RedeemTicket(ticket_string, slice_cred)
1185                 self.logger.info("Success")
1186             except socket.gaierror:
1187                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1188             except Exception, e:
1189                 self.logger.log_exc(e.message)
1190         return
1191
1192     def create_gid(self, options, args):
1193         """
1194         Create a GID (CreateGid)
1195         """
1196         if len(args) < 1:
1197             self.print_help()
1198             sys.exit(1)
1199         target_hrn = args[0]
1200         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1201         if options.file:
1202             filename = options.file
1203         else:
1204             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1205         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1206         GID(string=gid).save_to_file(filename)
1207          
1208
1209     def delegate(self, options, args):
1210         """
1211         (locally) create delegate credential for use by given hrn
1212         """
1213         delegee_hrn = args[0]
1214         if options.delegate_user:
1215             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1216         elif options.delegate_slice:
1217             slice_cred = self.slice_credential_string(options.delegate_slice)
1218             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1219         else:
1220             self.logger.warning("Must specify either --user or --slice <hrn>")
1221             return
1222         delegated_cred = Credential(string=cred)
1223         object_hrn = delegated_cred.get_gid_object().get_hrn()
1224         if options.delegate_user:
1225             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1226                                   + get_leaf(object_hrn) + ".cred")
1227         elif options.delegate_slice:
1228             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1229                                   + get_leaf(object_hrn) + ".cred")
1230
1231         delegated_cred.save_to_file(dest_fn, save_parents=True)
1232
1233         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1234     
1235     def get_trusted_certs(self, options, args):
1236         """
1237         return uhe trusted certs at this interface (get_trusted_certs)
1238         """ 
1239         trusted_certs = self.registry().get_trusted_certs()
1240         for trusted_cert in trusted_certs:
1241             gid = GID(string=trusted_cert)
1242             gid.dump()
1243             cert = Certificate(string=trusted_cert)
1244             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1245         return 
1246
1247     def config (self, options, args):
1248         "Display contents of current config"
1249         self.show_config()