Merge branch 'master' into senslab2
[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                 print >>sys.stderr, "\r\n \r\n \r\n WOOOOOO"
927                 users = sfa_users_arg(user_records, slice_record)
928
929         # do not append users, keys, or slice tags. Anything
930         # not contained in this request will be removed from the slice
931
932         # CreateSliver has supported the options argument for a while now so it should
933         # be safe to assume this server support it
934         api_options = {}
935         api_options ['append'] = False
936         api_options ['call_id'] = unique_call_id()
937         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
938         value = ReturnValue.get_value(result)
939         if self.options.raw:
940             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
941         if options.file is not None:
942             save_rspec_to_file (value, options.file)
943         if (self.options.raw is None) and (options.file is None):
944             print value
945
946         return value
947
948     def delete(self, options, args):
949         """
950         delete named slice (DeleteSliver)
951         """
952         server = self.sliceapi()
953
954         # slice urn
955         slice_hrn = args[0]
956         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
957
958         # creds
959         slice_cred = self.slice_credential_string(slice_hrn)
960         creds = [slice_cred]
961         if options.delegate:
962             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
963             creds.append(delegated_cred)
964         
965         # options and call_id when supported
966         api_options = {}
967         api_options ['call_id'] = unique_call_id()
968         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
969         value = ReturnValue.get_value(result)
970         if self.options.raw:
971             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
972         else:
973             print value
974         return value 
975   
976     def status(self, options, args):
977         """
978         retrieve slice status (SliverStatus)
979         """
980         server = self.sliceapi()
981
982         # slice urn
983         slice_hrn = args[0]
984         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
985
986         # creds 
987         slice_cred = self.slice_credential_string(slice_hrn)
988         creds = [slice_cred]
989         if options.delegate:
990             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
991             creds.append(delegated_cred)
992
993         # options and call_id when supported
994         api_options = {}
995         api_options['call_id']=unique_call_id()
996         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
997         value = ReturnValue.get_value(result)
998         if self.options.raw:
999             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1000         else:
1001             print value
1002
1003     def start(self, options, args):
1004         """
1005         start named slice (Start)
1006         """
1007         server = self.sliceapi()
1008
1009         # the slice urn
1010         slice_hrn = args[0]
1011         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1012         
1013         # cred
1014         slice_cred = self.slice_credential_string(args[0])
1015         creds = [slice_cred]
1016         if options.delegate:
1017             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1018             creds.append(delegated_cred)
1019         # xxx Thierry - does this not need an api_options as well ?
1020         result = server.Start(slice_urn, creds)
1021         value = ReturnValue.get_value(result)
1022         if self.options.raw:
1023             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1024         else:
1025             print value
1026         return value
1027     
1028     def stop(self, options, args):
1029         """
1030         stop named slice (Stop)
1031         """
1032         server = self.sliceapi()
1033         # slice urn
1034         slice_hrn = args[0]
1035         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1036         # cred
1037         slice_cred = self.slice_credential_string(args[0])
1038         creds = [slice_cred]
1039         if options.delegate:
1040             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1041             creds.append(delegated_cred)
1042         result =  server.Stop(slice_urn, creds)
1043         value = ReturnValue.get_value(result)
1044         if self.options.raw:
1045             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1046         else:
1047             print value
1048         return value
1049     
1050     # reset named slice
1051     def reset(self, options, args):
1052         """
1053         reset named slice (reset_slice)
1054         """
1055         server = self.sliceapi()
1056         # slice urn
1057         slice_hrn = args[0]
1058         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1059         # cred
1060         slice_cred = self.slice_credential_string(args[0])
1061         creds = [slice_cred]
1062         if options.delegate:
1063             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1064             creds.append(delegated_cred)
1065         result = server.reset_slice(creds, slice_urn)
1066         value = ReturnValue.get_value(result)
1067         if self.options.raw:
1068             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1069         else:
1070             print value
1071         return value
1072
1073     def renew(self, options, args):
1074         """
1075         renew slice (RenewSliver)
1076         """
1077         server = self.sliceapi()
1078         # slice urn    
1079         slice_hrn = args[0]
1080         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1081         # creds
1082         slice_cred = self.slice_credential_string(args[0])
1083         creds = [slice_cred]
1084         if options.delegate:
1085             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1086             creds.append(delegated_cred)
1087         # time
1088         time = args[1]
1089         # options and call_id when supported
1090         api_options = {}
1091         api_options['call_id']=unique_call_id()
1092         result =  server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1093         value = ReturnValue.get_value(result)
1094         if self.options.raw:
1095             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1096         else:
1097             print value
1098         return value
1099
1100
1101     def shutdown(self, options, args):
1102         """
1103         shutdown named slice (Shutdown)
1104         """
1105         server = self.sliceapi()
1106         # slice urn
1107         slice_hrn = args[0]
1108         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1109         # creds
1110         slice_cred = self.slice_credential_string(slice_hrn)
1111         creds = [slice_cred]
1112         if options.delegate:
1113             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1114             creds.append(delegated_cred)
1115         result = server.Shutdown(slice_urn, creds)
1116         value = ReturnValue.get_value(result)
1117         if self.options.raw:
1118             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1119         else:
1120             print value
1121         return value         
1122     
1123
1124     def get_ticket(self, options, args):
1125         """
1126         get a ticket for the specified slice
1127         """
1128         server = self.sliceapi()
1129         # slice urn
1130         slice_hrn, rspec_path = args[0], args[1]
1131         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1132         # creds
1133         slice_cred = self.slice_credential_string(slice_hrn)
1134         creds = [slice_cred]
1135         if options.delegate:
1136             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1137             creds.append(delegated_cred)
1138         # rspec
1139         rspec_file = self.get_rspec_file(rspec_path) 
1140         rspec = open(rspec_file).read()
1141         # options and call_id when supported
1142         api_options = {}
1143         api_options['call_id']=unique_call_id()
1144         # get ticket at the server
1145         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1146         # save
1147         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1148         self.logger.info("writing ticket to %s"%file)
1149         ticket = SfaTicket(string=ticket_string)
1150         ticket.save_to_file(filename=file, save_parents=True)
1151
1152     def redeem_ticket(self, options, args):
1153         """
1154         Connects to nodes in a slice and redeems a ticket
1155 (slice hrn is retrieved from the ticket)
1156         """
1157         ticket_file = args[0]
1158         
1159         # get slice hrn from the ticket
1160         # use this to get the right slice credential 
1161         ticket = SfaTicket(filename=ticket_file)
1162         ticket.decode()
1163         ticket_string = ticket.save_to_string(save_parents=True)
1164
1165         slice_hrn = ticket.gidObject.get_hrn()
1166         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1167         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1168         slice_cred = self.slice_credential_string(slice_hrn)
1169         
1170         # get a list of node hostnames from the RSpec 
1171         tree = etree.parse(StringIO(ticket.rspec))
1172         root = tree.getroot()
1173         hostnames = root.xpath("./network/site/node/hostname/text()")
1174         
1175         # create an xmlrpc connection to the component manager at each of these
1176         # components and gall redeem_ticket
1177         connections = {}
1178         for hostname in hostnames:
1179             try:
1180                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1181                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1182                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1183                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1184                                            timeout=self.options.timeout, verbose=self.options.debug)
1185                 server.RedeemTicket(ticket_string, slice_cred)
1186                 self.logger.info("Success")
1187             except socket.gaierror:
1188                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1189             except Exception, e:
1190                 self.logger.log_exc(e.message)
1191         return
1192
1193     def create_gid(self, options, args):
1194         """
1195         Create a GID (CreateGid)
1196         """
1197         if len(args) < 1:
1198             self.print_help()
1199             sys.exit(1)
1200         target_hrn = args[0]
1201         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1202         if options.file:
1203             filename = options.file
1204         else:
1205             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1206         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1207         GID(string=gid).save_to_file(filename)
1208          
1209
1210     def delegate(self, options, args):
1211         """
1212         (locally) create delegate credential for use by given hrn
1213         """
1214         delegee_hrn = args[0]
1215         if options.delegate_user:
1216             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1217         elif options.delegate_slice:
1218             slice_cred = self.slice_credential_string(options.delegate_slice)
1219             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1220         else:
1221             self.logger.warning("Must specify either --user or --slice <hrn>")
1222             return
1223         delegated_cred = Credential(string=cred)
1224         object_hrn = delegated_cred.get_gid_object().get_hrn()
1225         if options.delegate_user:
1226             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1227                                   + get_leaf(object_hrn) + ".cred")
1228         elif options.delegate_slice:
1229             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1230                                   + get_leaf(object_hrn) + ".cred")
1231
1232         delegated_cred.save_to_file(dest_fn, save_parents=True)
1233
1234         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1235     
1236     def get_trusted_certs(self, options, args):
1237         """
1238         return uhe trusted certs at this interface (get_trusted_certs)
1239         """ 
1240         trusted_certs = self.registry().get_trusted_certs()
1241         for trusted_cert in trusted_certs:
1242             gid = GID(string=trusted_cert)
1243             gid.dump()
1244             cert = Certificate(string=trusted_cert)
1245             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1246         return 
1247
1248     def config (self, options, args):
1249         "Display contents of current config"
1250         self.show_config()