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