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