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