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     rec_record = Record(dict=record_dict)
151     str = record.save_to_string()
152     f=codecs.open(filename, encoding='utf-8',mode="w")
153     f.write(str)
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
919         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
920         value = ReturnValue.get_value(result)
921         if self.options.raw:
922             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
923         if options.file is not None:
924             save_rspec_to_file (value, options.file)
925         if (self.options.raw is None) and (options.file is None):
926             print value
927
928         return value
929
930     def delete(self, options, args):
931         """
932         delete named slice (DeleteSliver)
933         """
934         server = self.sliceapi()
935
936         # slice urn
937         slice_hrn = args[0]
938         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
939
940         # creds
941         slice_cred = self.slice_credential_string(slice_hrn)
942         creds = [slice_cred]
943         if options.delegate:
944             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
945             creds.append(delegated_cred)
946         
947         # options and call_id when supported
948         api_options = {}
949         api_options ['call_id'] = unique_call_id()
950         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
951         value = ReturnValue.get_value(result)
952         if self.options.raw:
953             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
954         else:
955             print value
956         return value 
957   
958     def status(self, options, args):
959         """
960         retrieve slice status (SliverStatus)
961         """
962         server = self.sliceapi()
963
964         # slice urn
965         slice_hrn = args[0]
966         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
967
968         # creds 
969         slice_cred = self.slice_credential_string(slice_hrn)
970         creds = [slice_cred]
971         if options.delegate:
972             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
973             creds.append(delegated_cred)
974
975         # options and call_id when supported
976         api_options = {}
977         api_options['call_id']=unique_call_id()
978         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
979         value = ReturnValue.get_value(result)
980         if self.options.raw:
981             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
982         else:
983             print value
984
985     def start(self, options, args):
986         """
987         start named slice (Start)
988         """
989         server = self.sliceapi()
990
991         # the slice urn
992         slice_hrn = args[0]
993         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
994         
995         # cred
996         slice_cred = self.slice_credential_string(args[0])
997         creds = [slice_cred]
998         if options.delegate:
999             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1000             creds.append(delegated_cred)
1001         # xxx Thierry - does this not need an api_options as well ?
1002         result = server.Start(slice_urn, creds)
1003         value = ReturnValue.get_value(result)
1004         if self.options.raw:
1005             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1006         else:
1007             print value
1008         return value
1009     
1010     def stop(self, options, args):
1011         """
1012         stop named slice (Stop)
1013         """
1014         server = self.sliceapi()
1015         # slice urn
1016         slice_hrn = args[0]
1017         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1018         # cred
1019         slice_cred = self.slice_credential_string(args[0])
1020         creds = [slice_cred]
1021         if options.delegate:
1022             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1023             creds.append(delegated_cred)
1024         result =  server.Stop(slice_urn, creds)
1025         value = ReturnValue.get_value(result)
1026         if self.options.raw:
1027             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1028         else:
1029             print value
1030         return value
1031     
1032     # reset named slice
1033     def reset(self, options, args):
1034         """
1035         reset named slice (reset_slice)
1036         """
1037         server = self.sliceapi()
1038         # slice urn
1039         slice_hrn = args[0]
1040         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1041         # cred
1042         slice_cred = self.slice_credential_string(args[0])
1043         creds = [slice_cred]
1044         if options.delegate:
1045             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1046             creds.append(delegated_cred)
1047         result = server.reset_slice(creds, slice_urn)
1048         value = ReturnValue.get_value(result)
1049         if self.options.raw:
1050             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1051         else:
1052             print value
1053         return value
1054
1055     def renew(self, options, args):
1056         """
1057         renew slice (RenewSliver)
1058         """
1059         server = self.sliceapi()
1060         # slice urn    
1061         slice_hrn = args[0]
1062         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1063         # creds
1064         slice_cred = self.slice_credential_string(args[0])
1065         creds = [slice_cred]
1066         if options.delegate:
1067             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1068             creds.append(delegated_cred)
1069         # time
1070         time = args[1]
1071         # options and call_id when supported
1072         api_options = {}
1073         api_options['call_id']=unique_call_id()
1074         result =  server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1075         value = ReturnValue.get_value(result)
1076         if self.options.raw:
1077             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1078         else:
1079             print value
1080         return value
1081
1082
1083     def shutdown(self, options, args):
1084         """
1085         shutdown named slice (Shutdown)
1086         """
1087         server = self.sliceapi()
1088         # slice urn
1089         slice_hrn = args[0]
1090         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1091         # creds
1092         slice_cred = self.slice_credential_string(slice_hrn)
1093         creds = [slice_cred]
1094         if options.delegate:
1095             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1096             creds.append(delegated_cred)
1097         result = server.Shutdown(slice_urn, creds)
1098         value = ReturnValue.get_value(result)
1099         if self.options.raw:
1100             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1101         else:
1102             print value
1103         return value         
1104     
1105
1106     def get_ticket(self, options, args):
1107         """
1108         get a ticket for the specified slice
1109         """
1110         server = self.sliceapi()
1111         # slice urn
1112         slice_hrn, rspec_path = args[0], args[1]
1113         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1114         # creds
1115         slice_cred = self.slice_credential_string(slice_hrn)
1116         creds = [slice_cred]
1117         if options.delegate:
1118             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1119             creds.append(delegated_cred)
1120         # rspec
1121         rspec_file = self.get_rspec_file(rspec_path) 
1122         rspec = open(rspec_file).read()
1123         # options and call_id when supported
1124         api_options = {}
1125         api_options['call_id']=unique_call_id()
1126         # get ticket at the server
1127         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1128         # save
1129         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1130         self.logger.info("writing ticket to %s"%file)
1131         ticket = SfaTicket(string=ticket_string)
1132         ticket.save_to_file(filename=file, save_parents=True)
1133
1134     def redeem_ticket(self, options, args):
1135         """
1136         Connects to nodes in a slice and redeems a ticket
1137 (slice hrn is retrieved from the ticket)
1138         """
1139         ticket_file = args[0]
1140         
1141         # get slice hrn from the ticket
1142         # use this to get the right slice credential 
1143         ticket = SfaTicket(filename=ticket_file)
1144         ticket.decode()
1145         ticket_string = ticket.save_to_string(save_parents=True)
1146
1147         slice_hrn = ticket.gidObject.get_hrn()
1148         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1149         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1150         slice_cred = self.slice_credential_string(slice_hrn)
1151         
1152         # get a list of node hostnames from the RSpec 
1153         tree = etree.parse(StringIO(ticket.rspec))
1154         root = tree.getroot()
1155         hostnames = root.xpath("./network/site/node/hostname/text()")
1156         
1157         # create an xmlrpc connection to the component manager at each of these
1158         # components and gall redeem_ticket
1159         connections = {}
1160         for hostname in hostnames:
1161             try:
1162                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1163                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1164                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1165                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1166                                            timeout=self.options.timeout, verbose=self.options.debug)
1167                 server.RedeemTicket(ticket_string, slice_cred)
1168                 self.logger.info("Success")
1169             except socket.gaierror:
1170                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1171             except Exception, e:
1172                 self.logger.log_exc(e.message)
1173         return
1174
1175     def create_gid(self, options, args):
1176         """
1177         Create a GID (CreateGid)
1178         """
1179         if len(args) < 1:
1180             self.print_help()
1181             sys.exit(1)
1182         target_hrn = args[0]
1183         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1184         if options.file:
1185             filename = options.file
1186         else:
1187             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1188         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1189         GID(string=gid).save_to_file(filename)
1190          
1191
1192     def delegate(self, options, args):
1193         """
1194         (locally) create delegate credential for use by given hrn
1195         """
1196         delegee_hrn = args[0]
1197         if options.delegate_user:
1198             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1199         elif options.delegate_slice:
1200             slice_cred = self.slice_credential_string(options.delegate_slice)
1201             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1202         else:
1203             self.logger.warning("Must specify either --user or --slice <hrn>")
1204             return
1205         delegated_cred = Credential(string=cred)
1206         object_hrn = delegated_cred.get_gid_object().get_hrn()
1207         if options.delegate_user:
1208             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1209                                   + get_leaf(object_hrn) + ".cred")
1210         elif options.delegate_slice:
1211             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1212                                   + get_leaf(object_hrn) + ".cred")
1213
1214         delegated_cred.save_to_file(dest_fn, save_parents=True)
1215
1216         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1217     
1218     def get_trusted_certs(self, options, args):
1219         """
1220         return uhe trusted certs at this interface (get_trusted_certs)
1221         """ 
1222         trusted_certs = self.registry().get_trusted_certs()
1223         for trusted_cert in trusted_certs:
1224             gid = GID(string=trusted_cert)
1225             gid.dump()
1226             cert = Certificate(string=trusted_cert)
1227             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1228         return 
1229