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