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