-s --sliceapi option no longer requires users to prefix url with 'http://'
[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                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
558                     self.sm_url = 'http://' + self.sm_url
559                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
560                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
561                                                      timeout=self.options.timeout, verbose=self.options.debug)  
562         return self.sliceapi_proxy
563
564     def get_cached_server_version(self, server):
565         # check local cache first
566         cache = None
567         version = None 
568         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
569         cache_key = server.url + "-version"
570         try:
571             cache = Cache(cache_file)
572         except IOError:
573             cache = Cache()
574             self.logger.info("Local cache not found at: %s" % cache_file)
575
576         if cache:
577             version = cache.get(cache_key)
578
579         if not version: 
580             result = server.GetVersion()
581             version= ReturnValue.get_value(result)
582             # cache version for 20 minutes
583             cache.add(cache_key, version, ttl= 60*20)
584             self.logger.info("Updating cache file %s" % cache_file)
585             cache.save_to_file(cache_file)
586
587         return version   
588         
589     ### resurrect this temporarily so we can support V1 aggregates for a while
590     def server_supports_options_arg(self, server):
591         """
592         Returns true if server support the optional call_id arg, false otherwise. 
593         """
594         server_version = self.get_cached_server_version(server)
595         result = False
596         # xxx need to rewrite this 
597         if int(server_version.get('geni_api')) >= 2:
598             result = True
599         return result
600
601     def server_supports_call_id_arg(self, server):
602         server_version = self.get_cached_server_version(server)
603         result = False      
604         if 'sfa' in server_version and 'code_tag' in server_version:
605             code_tag = server_version['code_tag']
606             code_tag_parts = code_tag.split("-")
607             version_parts = code_tag_parts[0].split(".")
608             major, minor = version_parts[0], version_parts[1]
609             rev = code_tag_parts[1]
610             if int(major) == 1 and minor == 0 and build >= 22:
611                 result = True
612         return result                 
613
614     ### ois = options if supported
615     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
616     def ois (self, server, option_dict):
617         if self.server_supports_options_arg (server): 
618             return [option_dict]
619         elif self.server_supports_call_id_arg (server):
620             return [ unique_call_id () ]
621         else: 
622             return []
623
624     ### cis = call_id if supported - like ois
625     def cis (self, server):
626         if self.server_supports_call_id_arg (server):
627             return [ unique_call_id ]
628         else:
629             return []
630
631     ######################################## miscell utilities
632     def get_rspec_file(self, rspec):
633        if (os.path.isabs(rspec)):
634           file = rspec
635        else:
636           file = os.path.join(self.options.sfi_dir, rspec)
637        if (os.path.isfile(file)):
638           return file
639        else:
640           self.logger.critical("No such rspec file %s"%rspec)
641           sys.exit(1)
642     
643     def get_record_file(self, record):
644        if (os.path.isabs(record)):
645           file = record
646        else:
647           file = os.path.join(self.options.sfi_dir, record)
648        if (os.path.isfile(file)):
649           return file
650        else:
651           self.logger.critical("No such registry record file %s"%record)
652           sys.exit(1)
653     
654
655     #==========================================================================
656     # Following functions implement the commands
657     #
658     # Registry-related commands
659     #==========================================================================
660   
661     def version(self, options, args):
662         """
663         display an SFA server version (GetVersion) 
664 or version information about sfi itself
665         """
666         if options.version_local:
667             version=version_core()
668         else:
669             if options.version_registry:
670                 server=self.registry()
671             else:
672                 server = self.sliceapi()
673             result = server.GetVersion()
674             version = ReturnValue.get_value(result)
675         pprinter = PrettyPrinter(indent=4)
676         pprinter.pprint(version)
677         if options.file:
678             save_variable_to_file(version, options.file, options.fileformat)
679
680     def list(self, options, args):
681         """
682         list entries in named authority registry (List)
683         """
684         if len(args)!= 1:
685             self.print_help()
686             sys.exit(1)
687         hrn = args[0]
688         try:
689             list = self.registry().List(hrn, self.my_credential_string)
690         except IndexError:
691             raise Exception, "Not enough parameters for the 'list' command"
692
693         # filter on person, slice, site, node, etc.
694         # THis really should be in the self.filter_records funct def comment...
695         list = filter_records(options.type, list)
696         for record in list:
697             print "%s (%s)" % (record['hrn'], record['type'])
698         if options.file:
699             save_records_to_file(options.file, list, options.fileformat)
700         return
701     
702     def show(self, options, args):
703         """
704         show details about named registry record (Resolve)
705         """
706         if len(args)!= 1:
707             self.print_help()
708             sys.exit(1)
709         hrn = args[0]
710         records = self.registry().Resolve(hrn, self.my_credential_string)
711         records = filter_records(options.type, records)
712         if not records:
713             self.logger.error("No record of type %s"% options.type)
714         for record in records:
715             if record['type'] in ['user']:
716                 record = UserRecord(dict=record)
717             elif record['type'] in ['slice']:
718                 record = SliceRecord(dict=record)
719             elif record['type'] in ['node']:
720                 record = NodeRecord(dict=record)
721             elif record['type'].startswith('authority'):
722                 record = AuthorityRecord(dict=record)
723             else:
724                 record = SfaRecord(dict=record)
725             if (options.format == "text"): 
726                 record.dump()  
727             else:
728                 print record.save_to_string() 
729         if options.file:
730             save_records_to_file(options.file, records, options.fileformat)
731         return
732     
733     def add(self, options, args):
734         "add record into registry from xml file (Register)"
735         auth_cred = self.my_authority_credential_string()
736         if len(args)!=1:
737             self.print_help()
738             sys.exit(1)
739         record_filepath = args[0]
740         rec_file = self.get_record_file(record_filepath)
741         record = load_record_from_file(rec_file).as_dict()
742         return self.registry().Register(record, auth_cred)
743     
744     def update(self, options, args):
745         "update record into registry from xml file (Update)"
746         if len(args)!=1:
747             self.print_help()
748             sys.exit(1)
749         rec_file = self.get_record_file(args[0])
750         record = load_record_from_file(rec_file)
751         if record['type'] == "user":
752             if record.get_name() == self.user:
753                 cred = self.my_credential_string
754             else:
755                 cred = self.my_authority_credential_string()
756         elif record['type'] in ["slice"]:
757             try:
758                 cred = self.slice_credential_string(record.get_name())
759             except ServerException, e:
760                # XXX smbaker -- once we have better error return codes, update this
761                # to do something better than a string compare
762                if "Permission error" in e.args[0]:
763                    cred = self.my_authority_credential_string()
764                else:
765                    raise
766         elif record.get_type() in ["authority"]:
767             cred = self.my_authority_credential_string()
768         elif record.get_type() == 'node':
769             cred = self.my_authority_credential_string()
770         else:
771             raise "unknown record type" + record.get_type()
772         record = record.as_dict()
773         return self.registry().Update(record, cred)
774   
775     def remove(self, options, args):
776         "remove registry record by name (Remove)"
777         auth_cred = self.my_authority_credential_string()
778         if len(args)!=1:
779             self.print_help()
780             sys.exit(1)
781         hrn = args[0]
782         type = options.type 
783         if type in ['all']:
784             type = '*'
785         return self.registry().Remove(hrn, auth_cred, type)
786     
787     # ==================================================================
788     # Slice-related commands
789     # ==================================================================
790
791     def slices(self, options, args):
792         "list instantiated slices (ListSlices) - returns urn's"
793         server = self.sliceapi()
794         # creds
795         creds = [self.my_credential_string]
796         if options.delegate:
797             delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
798             creds.append(delegated_cred)  
799         # options and call_id when supported
800         api_options = {}
801         api_options['call_id']=unique_call_id()
802         result = server.ListSlices(creds, *self.ois(server,api_options))
803         value = ReturnValue.get_value(result)
804         display_list(value)
805         return
806     
807     # show rspec for named slice
808     def resources(self, options, args):
809         """
810         with no arg, discover available resources, (ListResources)
811 or with an slice hrn, shows currently provisioned resources
812         """
813         server = self.sliceapi()
814
815         # set creds
816         creds = []
817         if args:
818             creds.append(self.slice_credential_string(args[0]))
819         else:
820             creds.append(self.my_credential_string)
821         if options.delegate:
822             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
823        
824         # no need to check if server accepts the options argument since the options has
825         # been a required argument since v1 API   
826         api_options = {}
827         # always send call_id to v2 servers
828         api_options ['call_id'] = unique_call_id()
829         # ask for cached value if available
830         api_options ['cached'] = True
831         if args:
832             hrn = args[0]
833             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
834         if options.info:
835             api_options['info'] = options.info
836         if options.current:
837             if options.current == True:
838                 api_options['cached'] = False
839             else:
840                 api_options['cached'] = True
841         if options.rspec_version:
842             version_manager = VersionManager()
843             server_version = self.get_cached_server_version(server)
844             if 'sfa' in server_version:
845                 # just request the version the client wants
846                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
847             else:
848                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}    
849         else:
850             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}    
851         result = server.ListResources (creds, api_options)
852         value = ReturnValue.get_value(result)
853         if options.file is None:
854             display_rspec(value, options.format)
855         else:
856             save_rspec_to_file(value, options.file)
857         return
858
859     def create(self, options, args):
860         """
861         create or update named slice with given rspec
862         """
863         server = self.sliceapi()
864
865         # xxx do we need to check usage (len(args)) ?
866         # slice urn
867         slice_hrn = args[0]
868         slice_urn = hrn_to_urn(slice_hrn, 'slice')
869
870         # credentials
871         creds = [self.slice_credential_string(slice_hrn)]
872         delegated_cred = None
873         server_version = self.get_cached_server_version(server)
874         if server_version.get('interface') == 'slicemgr':
875             # delegate our cred to the slice manager
876             # do not delegate cred to slicemgr...not working at the moment
877             pass
878             #if server_version.get('hrn'):
879             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
880             #elif server_version.get('urn'):
881             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
882                 
883         # rspec 
884         rspec_file = self.get_rspec_file(args[1])
885         rspec = open(rspec_file).read()
886
887         # users
888         # need to pass along user keys to the aggregate.
889         # users = [
890         #  { urn: urn:publicid:IDN+emulab.net+user+alice
891         #    keys: [<ssh key A>, <ssh key B>]
892         #  }]
893         users = []
894         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
895         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
896             slice_record = slice_records[0]
897             user_hrns = slice_record['researcher']
898             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
899             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
900
901             if 'sfa' not in server_version:
902                 users = pg_users_arg(user_records)
903                 rspec = RSpec(rspec)
904                 rspec.filter({'component_manager_id': server_version['urn']})
905                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
906             else:
907                 users = sfa_users_arg(user_records, slice_record)
908         
909         # do not append users, keys, or slice tags. Anything 
910         # not contained in this request will be removed from the slice
911
912         # CreateSliver has supported the options argument for a while now so it should
913         # be safe to assume this server support it
914         api_options = {}
915         api_options ['append'] = False
916         api_options ['call_id'] = unique_call_id()
917
918         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
919         value = ReturnValue.get_value(result)
920         if options.file is None:
921             print value
922         else:
923             save_rspec_to_file (value, options.file)
924         return value
925
926     def delete(self, options, args):
927         """
928         delete named slice (DeleteSliver)
929         """
930         server = self.sliceapi()
931
932         # slice urn
933         slice_hrn = args[0]
934         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
935
936         # creds
937         slice_cred = self.slice_credential_string(slice_hrn)
938         creds = [slice_cred]
939         if options.delegate:
940             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
941             creds.append(delegated_cred)
942         
943         # options and call_id when supported
944         api_options = {}
945         api_options ['call_id'] = unique_call_id()
946         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
947         # xxx no ReturnValue ??
948         return result
949   
950     def status(self, options, args):
951         """
952         retrieve slice status (SliverStatus)
953         """
954         server = self.sliceapi()
955
956         # slice urn
957         slice_hrn = args[0]
958         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
959
960         # creds 
961         slice_cred = self.slice_credential_string(slice_hrn)
962         creds = [slice_cred]
963         if options.delegate:
964             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
965             creds.append(delegated_cred)
966
967         # options and call_id when supported
968         api_options = {}
969         api_options['call_id']=unique_call_id()
970         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
971         value = ReturnValue.get_value(result)
972         print value
973         if options.file:
974             save_variable_to_file(value, options.file, options.fileformat)
975
976     def start(self, options, args):
977         """
978         start named slice (Start)
979         """
980         server = self.sliceapi()
981
982         # the slice urn
983         slice_hrn = args[0]
984         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
985         
986         # cred
987         slice_cred = self.slice_credential_string(args[0])
988         creds = [slice_cred]
989         if options.delegate:
990             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
991             creds.append(delegated_cred)
992         # xxx Thierry - does this not need an api_options as well ?
993         return server.Start(slice_urn, creds)
994     
995     def stop(self, options, args):
996         """
997         stop named slice (Stop)
998         """
999         server = self.sliceapi()
1000         # slice urn
1001         slice_hrn = args[0]
1002         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1003         # cred
1004         slice_cred = self.slice_credential_string(args[0])
1005         creds = [slice_cred]
1006         if options.delegate:
1007             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1008             creds.append(delegated_cred)
1009         return server.Stop(slice_urn, creds)
1010     
1011     # reset named slice
1012     def reset(self, options, args):
1013         """
1014         reset named slice (reset_slice)
1015         """
1016         server = self.sliceapi()
1017         # slice urn
1018         slice_hrn = args[0]
1019         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1020         # cred
1021         slice_cred = self.slice_credential_string(args[0])
1022         creds = [slice_cred]
1023         if options.delegate:
1024             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1025             creds.append(delegated_cred)
1026         return server.reset_slice(creds, slice_urn)
1027
1028     def renew(self, options, args):
1029         """
1030         renew slice (RenewSliver)
1031         """
1032         server = self.sliceapi()
1033         # slice urn    
1034         slice_hrn = args[0]
1035         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1036         # creds
1037         slice_cred = self.slice_credential_string(args[0])
1038         creds = [slice_cred]
1039         if options.delegate:
1040             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1041             creds.append(delegated_cred)
1042         # time
1043         time = args[1]
1044         # options and call_id when supported
1045         api_options = {}
1046         api_options['call_id']=unique_call_id()
1047         result =  server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
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         server = self.sliceapi()
1057         # slice urn
1058         slice_hrn = args[0]
1059         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1060         # creds
1061         slice_cred = self.slice_credential_string(slice_hrn)
1062         creds = [slice_cred]
1063         if options.delegate:
1064             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1065             creds.append(delegated_cred)
1066         return server.Shutdown(slice_urn, creds)         
1067     
1068
1069     def get_ticket(self, options, args):
1070         """
1071         get a ticket for the specified slice
1072         """
1073         server = self.sliceapi()
1074         # slice urn
1075         slice_hrn, rspec_path = args[0], args[1]
1076         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1077         # creds
1078         slice_cred = self.slice_credential_string(slice_hrn)
1079         creds = [slice_cred]
1080         if options.delegate:
1081             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1082             creds.append(delegated_cred)
1083         # rspec
1084         rspec_file = self.get_rspec_file(rspec_path) 
1085         rspec = open(rspec_file).read()
1086         # options and call_id when supported
1087         api_options = {}
1088         api_options['call_id']=unique_call_id()
1089         # get ticket at the server
1090         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1091         # save
1092         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1093         self.logger.info("writing ticket to %s"%file)
1094         ticket = SfaTicket(string=ticket_string)
1095         ticket.save_to_file(filename=file, save_parents=True)
1096
1097     def redeem_ticket(self, options, args):
1098         """
1099         Connects to nodes in a slice and redeems a ticket
1100 (slice hrn is retrieved from the ticket)
1101         """
1102         ticket_file = args[0]
1103         
1104         # get slice hrn from the ticket
1105         # use this to get the right slice credential 
1106         ticket = SfaTicket(filename=ticket_file)
1107         ticket.decode()
1108         ticket_string = ticket.save_to_string(save_parents=True)
1109
1110         slice_hrn = ticket.gidObject.get_hrn()
1111         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1112         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1113         slice_cred = self.slice_credential_string(slice_hrn)
1114         
1115         # get a list of node hostnames from the RSpec 
1116         tree = etree.parse(StringIO(ticket.rspec))
1117         root = tree.getroot()
1118         hostnames = root.xpath("./network/site/node/hostname/text()")
1119         
1120         # create an xmlrpc connection to the component manager at each of these
1121         # components and gall redeem_ticket
1122         connections = {}
1123         for hostname in hostnames:
1124             try:
1125                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1126                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1127                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1128                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1129                                            timeout=self.options.timeout, verbose=self.options.debug)
1130                 server.RedeemTicket(ticket_string, slice_cred)
1131                 self.logger.info("Success")
1132             except socket.gaierror:
1133                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1134             except Exception, e:
1135                 self.logger.log_exc(e.message)
1136         return
1137
1138     def create_gid(self, options, args):
1139         """
1140         Create a GID (CreateGid)
1141         """
1142         if len(args) < 1:
1143             self.print_help()
1144             sys.exit(1)
1145         target_hrn = args[0]
1146         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1147         if options.file:
1148             filename = options.file
1149         else:
1150             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1151         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1152         GID(string=gid).save_to_file(filename)
1153          
1154
1155     def delegate(self, options, args):
1156         """
1157         (locally) create delegate credential for use by given hrn
1158         """
1159         delegee_hrn = args[0]
1160         if options.delegate_user:
1161             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1162         elif options.delegate_slice:
1163             slice_cred = self.slice_credential_string(options.delegate_slice)
1164             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1165         else:
1166             self.logger.warning("Must specify either --user or --slice <hrn>")
1167             return
1168         delegated_cred = Credential(string=cred)
1169         object_hrn = delegated_cred.get_gid_object().get_hrn()
1170         if options.delegate_user:
1171             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1172                                   + get_leaf(object_hrn) + ".cred")
1173         elif options.delegate_slice:
1174             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1175                                   + get_leaf(object_hrn) + ".cred")
1176
1177         delegated_cred.save_to_file(dest_fn, save_parents=True)
1178
1179         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1180     
1181     def get_trusted_certs(self, options, args):
1182         """
1183         return uhe trusted certs at this interface (get_trusted_certs)
1184         """ 
1185         trusted_certs = self.registry().get_trusted_certs()
1186         for trusted_cert in trusted_certs:
1187             gid = GID(string=trusted_cert)
1188             gid.dump()
1189             cert = Certificate(string=trusted_cert)
1190             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1191         return 
1192