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