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