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