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