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