minor
[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     # xxx this too should be handled in bootstrap
548     def get_cached_credential(self, file):
549         """
550         Return a cached credential only if it hasn't expired.
551         """
552         if (os.path.isfile(file)):
553             credential = Credential(filename=file)
554             # make sure it isnt expired 
555             if not credential.get_expiration or \
556                datetime.datetime.today() < credential.get_expiration():
557                 return credential
558         return None 
559
560     def get_auth_cred(self):
561         if not self.authority:
562             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
563             sys.exit(-1)
564         return self.bootstrap.authority_credential_string (self.authority)
565
566     def get_slice_cred(self, name):
567         return self.bootstrap.slice_credential_string (name)
568
569     # xxx should be supported by sfaclientbootstrap as well
570     def delegate_cred(self, object_cred, hrn, type='authority'):
571         # the gid and hrn of the object we are delegating
572         if isinstance(object_cred, str):
573             object_cred = Credential(string=object_cred) 
574         object_gid = object_cred.get_gid_object()
575         object_hrn = object_gid.get_hrn()
576     
577         if not object_cred.get_privileges().get_all_delegate():
578             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
579             return
580
581         # the delegating user's gid
582         caller_gidfile = self.my_gid()
583   
584         # the gid of the user who will be delegated to
585         delegee_gid = self.bootstrap.gid(hrn,type)
586         delegee_hrn = delegee_gid.get_hrn()
587         dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
588         return dcred.save_to_string(save_parents=True)
589      
590     ######################################## miscell utilities
591     def get_rspec_file(self, rspec):
592        if (os.path.isabs(rspec)):
593           file = rspec
594        else:
595           file = os.path.join(self.options.sfi_dir, rspec)
596        if (os.path.isfile(file)):
597           return file
598        else:
599           self.logger.critical("No such rspec file %s"%rspec)
600           sys.exit(1)
601     
602     def get_record_file(self, record):
603        if (os.path.isabs(record)):
604           file = record
605        else:
606           file = os.path.join(self.options.sfi_dir, record)
607        if (os.path.isfile(file)):
608           return file
609        else:
610           self.logger.critical("No such registry record file %s"%record)
611           sys.exit(1)
612     
613     def get_component_proxy_from_hrn(self, hrn):
614         # direct connection to the nodes component manager interface
615         records = self.registry.Resolve(hrn, self.my_credential_string)
616         records = filter_records('node', records)
617         if not records:
618             self.logger.warning("No such component:%r"% hrn)
619         record = records[0]
620   
621         return self.server_proxy(record['hostname'], CM_PORT, self.private_key, self.my_gid)
622
623     def server_proxy(self, host, port, keyfile, certfile):
624         """
625         Return an instance of an xmlrpc server connection    
626         """
627         # port is appended onto the domain, before the path. Should look like:
628         # http://domain:port/path
629         host_parts = host.split('/')
630         host_parts[0] = host_parts[0] + ":" + str(port)
631         url =  "http://%s" %  "/".join(host_parts)    
632         return SfaServerProxy(url, keyfile, certfile, timeout=self.options.timeout, 
633                               verbose=self.options.debug)
634
635     # xxx opts could be retrieved in self.options
636     def server_proxy_from_opts(self, opts):
637         """
638         Return instance of an xmlrpc connection to a slice manager, aggregate
639         or component server depending on the specified opts
640         """
641         server = self.slicemgr
642         # direct connection to an aggregate
643         if hasattr(opts, 'aggregate') and opts.aggregate:
644             server = self.server_proxy(opts.aggregate, opts.port, self.private_key, self.my_gid)
645         # direct connection to the nodes component manager interface
646         if hasattr(opts, 'component') and opts.component:
647             server = self.get_component_proxy_from_hrn(opts.component)
648
649         return server
650     #==========================================================================
651     # Following functions implement the commands
652     #
653     # Registry-related commands
654     #==========================================================================
655   
656     def version(self, opts, args):
657         """
658         display an SFA server version (GetVersion) 
659 or version information about sfi itself
660         """
661         if opts.version_local:
662             version=version_core()
663         else:
664             if opts.version_registry:
665                 server=self.registry
666             else:
667                 server = self.server_proxy_from_opts(opts)
668             result = server.GetVersion()
669             version = ReturnValue.get_value(result)
670         for (k,v) in version.iteritems():
671             print "%-20s: %s"%(k,v)
672         if opts.file:
673             save_variable_to_file(version, opts.file, opts.fileformat)
674
675     def list(self, opts, args):
676         """
677         list entries in named authority registry (List)
678         """
679         if len(args)!= 1:
680             self.print_help()
681             sys.exit(1)
682         hrn = args[0]
683         try:
684             list = self.registry.List(hrn, self.my_credential_string)
685         except IndexError:
686             raise Exception, "Not enough parameters for the 'list' command"
687
688         # filter on person, slice, site, node, etc.
689         # THis really should be in the self.filter_records funct def comment...
690         list = filter_records(opts.type, list)
691         for record in list:
692             print "%s (%s)" % (record['hrn'], record['type'])
693         if opts.file:
694             save_records_to_file(opts.file, list, opts.fileformat)
695         return
696     
697     def show(self, opts, args):
698         """
699         show details about named registry record (Resolve)
700         """
701         if len(args)!= 1:
702             self.print_help()
703             sys.exit(1)
704         hrn = args[0]
705         records = self.registry.Resolve(hrn, self.my_credential_string)
706         records = filter_records(opts.type, records)
707         if not records:
708             self.logger.error("No record of type %s"% opts.type)
709         for record in records:
710             if record['type'] in ['user']:
711                 record = UserRecord(dict=record)
712             elif record['type'] in ['slice']:
713                 record = SliceRecord(dict=record)
714             elif record['type'] in ['node']:
715                 record = NodeRecord(dict=record)
716             elif record['type'].startswith('authority'):
717                 record = AuthorityRecord(dict=record)
718             else:
719                 record = SfaRecord(dict=record)
720             if (opts.format == "text"): 
721                 record.dump()  
722             else:
723                 print record.save_to_string() 
724         if opts.file:
725             save_records_to_file(opts.file, records, opts.fileformat)
726         return
727     
728     def add(self, opts, args):
729         "add record into registry from xml file (Register)"
730         auth_cred = self.get_auth_cred()
731         if len(args)!=1:
732             self.print_help()
733             sys.exit(1)
734         record_filepath = args[0]
735         rec_file = self.get_record_file(record_filepath)
736         record = load_record_from_file(rec_file).as_dict()
737         return self.registry.Register(record, auth_cred)
738     
739     def update(self, opts, args):
740         "update record into registry from xml file (Update)"
741         if len(args)!=1:
742             self.print_help()
743             sys.exit(1)
744         rec_file = self.get_record_file(args[0])
745         record = load_record_from_file(rec_file)
746         if record['type'] == "user":
747             if record.get_name() == self.user:
748                 cred = self.my_credential_string
749             else:
750                 cred = self.get_auth_cred()
751         elif record['type'] in ["slice"]:
752             try:
753                 cred = self.get_slice_cred(record.get_name())
754             except ServerException, e:
755                # XXX smbaker -- once we have better error return codes, update this
756                # to do something better than a string compare
757                if "Permission error" in e.args[0]:
758                    cred = self.get_auth_cred()
759                else:
760                    raise
761         elif record.get_type() in ["authority"]:
762             cred = self.get_auth_cred()
763         elif record.get_type() == 'node':
764             cred = self.get_auth_cred()
765         else:
766             raise "unknown record type" + record.get_type()
767         record = record.as_dict()
768         return self.registry.Update(record, cred)
769   
770     def remove(self, opts, args):
771         "remove registry record by name (Remove)"
772         auth_cred = self.get_auth_cred()
773         if len(args)!=1:
774             self.print_help()
775             sys.exit(1)
776         hrn = args[0]
777         type = opts.type 
778         if type in ['all']:
779             type = '*'
780         return self.registry.Remove(hrn, auth_cred, type)
781     
782     # ==================================================================
783     # Slice-related commands
784     # ==================================================================
785
786     def slices(self, opts, args):
787         "list instantiated slices (ListSlices) - returns urn's"
788         creds = [self.my_credential_string]
789         if opts.delegate:
790             delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
791             creds.append(delegated_cred)  
792         server = self.server_proxy_from_opts(opts)
793         api_options = {}
794         api_options ['call_id'] = unique_call_id()
795         result = server.ListSlices(creds, api_options)
796         value = ReturnValue.get_value(result)
797         display_list(value)
798         return
799     
800     # show rspec for named slice
801     def resources(self, opts, args):
802         """
803         with no arg, discover available resources,
804 or currently provisioned resources  (ListResources)
805         """
806         server = self.server_proxy_from_opts(opts)
807    
808         api_options = {}
809         api_options ['call_id'] = unique_call_id()
810         #panos add info api_options
811         if opts.info:
812             api_options['info'] = opts.info
813         
814         if args:
815             cred = self.get_slice_cred(args[0])
816             hrn = args[0]
817             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
818         else:
819             cred = self.my_credential_string
820      
821         creds = [cred]
822         if opts.delegate:
823             delegated_cred = self.delegate_cred(cred, get_authority(self.authority))
824             creds.append(delegated_cred)
825         if opts.rspec_version:
826             version_manager = VersionManager()
827             server_version = self.get_cached_server_version(server)
828             if 'sfa' in server_version:
829                 # just request the version the client wants 
830                 api_options['geni_rspec_version'] = version_manager.get_version(opts.rspec_version).to_dict()
831             else:
832                 # this must be a protogeni aggregate. We should request a v2 ad rspec
833                 # regardless of what the client user requested 
834                 api_options['geni_rspec_version'] = version_manager.get_version('ProtoGENI 2').to_dict()     
835         else:
836             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
837
838         result = server.ListResources(creds, api_options)
839         value = ReturnValue.get_value(result)
840         if opts.file is None:
841             display_rspec(value, opts.format)
842         else:
843             save_rspec_to_file(value, opts.file)
844         return
845
846     def create(self, opts, args):
847         """
848         create or update named slice with given rspec
849         """
850         server = self.server_proxy_from_opts(opts)
851         server_version = self.get_cached_server_version(server)
852         slice_hrn = args[0]
853         slice_urn = hrn_to_urn(slice_hrn, 'slice')
854         slice_cred = self.get_slice_cred(slice_hrn)
855         delegated_cred = None
856         if server_version.get('interface') == 'slicemgr':
857             # delegate our cred to the slice manager
858             # do not delegate cred to slicemgr...not working at the moment
859             pass
860             #if server_version.get('hrn'):
861             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
862             #elif server_version.get('urn'):
863             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
864                  
865         rspec_file = self.get_rspec_file(args[1])
866         rspec = open(rspec_file).read()
867
868         # need to pass along user keys to the aggregate.
869         # users = [
870         #  { urn: urn:publicid:IDN+emulab.net+user+alice
871         #    keys: [<ssh key A>, <ssh key B>]
872         #  }]
873         users = []
874         slice_records = self.registry.Resolve(slice_urn, [self.my_credential_string])
875         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
876             slice_record = slice_records[0]
877             user_hrns = slice_record['researcher']
878             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
879             user_records = self.registry.Resolve(user_urns, [self.my_credential_string])
880
881             if 'sfa' not in server_version:
882                 users = pg_users_arg(user_records)
883                 rspec = RSpec(rspec)
884                 rspec.filter({'component_manager_id': server_version['urn']})
885                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
886                 creds = [slice_cred]
887             else:
888                 users = sfa_users_arg(user_records, slice_record)
889                 creds = [slice_cred]
890                 if delegated_cred:
891                     creds.append(delegated_cred)
892         # do not append users, keys, or slice tags. Anything 
893         # not contained in this request will be removed from the slice 
894         api_options = {}
895         api_options ['append'] = False
896         api_options ['call_id'] = unique_call_id()
897         result = server.CreateSliver(slice_urn, creds, rspec, users, api_options)
898         value = ReturnValue.get_value(result)
899         if opts.file is None:
900             print value
901         else:
902             save_rspec_to_file (value, opts.file)
903         return value
904
905     def delete(self, opts, args):
906         """
907         delete named slice (DeleteSliver)
908         """
909         slice_hrn = args[0]
910         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
911         slice_cred = self.get_slice_cred(slice_hrn)
912         creds = [slice_cred]
913         if opts.delegate:
914             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
915             creds.append(delegated_cred)
916         server = self.server_proxy_from_opts(opts)
917         api_options = {}
918         api_options ['call_id'] = unique_call_id()
919         return server.DeleteSliver(slice_urn, creds, api_options) 
920   
921     def status(self, opts, args):
922         """
923         retrieve slice status (SliverStatus)
924         """
925         slice_hrn = args[0]
926         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
927         slice_cred = self.get_slice_cred(slice_hrn)
928         creds = [slice_cred]
929         if opts.delegate:
930             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
931             creds.append(delegated_cred)
932         server = self.server_proxy_from_opts(opts)
933         api_options = {}
934         api_options ['call_id'] = unique_call_id()
935         result = server.SliverStatus(slice_urn, creds, api_options)
936         value = ReturnValue.get_value(result)
937         print value
938         if opts.file:
939             save_variable_to_file(value, opts.file, opts.fileformat)
940
941     def start(self, opts, args):
942         """
943         start named slice (Start)
944         """
945         slice_hrn = args[0]
946         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
947         slice_cred = self.get_slice_cred(args[0])
948         creds = [slice_cred]
949         if opts.delegate:
950             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
951             creds.append(delegated_cred)
952         server = self.server_proxy_from_opts(opts)
953         return server.Start(slice_urn, creds)
954     
955     def stop(self, opts, args):
956         """
957         stop named slice (Stop)
958         """
959         slice_hrn = args[0]
960         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
961         slice_cred = self.get_slice_cred(args[0])
962         creds = [slice_cred]
963         if opts.delegate:
964             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
965             creds.append(delegated_cred)
966         server = self.server_proxy_from_opts(opts)
967         return server.Stop(slice_urn, creds)
968     
969     # reset named slice
970     def reset(self, opts, args):
971         """
972         reset named slice (reset_slice)
973         """
974         slice_hrn = args[0]
975         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
976         server = self.server_proxy_from_opts(opts)
977         slice_cred = self.get_slice_cred(args[0])
978         creds = [slice_cred]
979         if opts.delegate:
980             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
981             creds.append(delegated_cred)
982         return server.reset_slice(creds, slice_urn)
983
984     def renew(self, opts, args):
985         """
986         renew slice (RenewSliver)
987         """
988         slice_hrn = args[0]
989         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
990         server = self.server_proxy_from_opts(opts)
991         slice_cred = self.get_slice_cred(args[0])
992         creds = [slice_cred]
993         if opts.delegate:
994             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
995             creds.append(delegated_cred)
996         time = args[1]
997         api_options = {}
998         api_options ['call_id'] = unique_call_id()
999         result =  server.RenewSliver(slice_urn, creds, time, api_options)
1000         value = ReturnValue.get_value(result)
1001         return value
1002
1003
1004     def shutdown(self, opts, args):
1005         """
1006         shutdown named slice (Shutdown)
1007         """
1008         slice_hrn = args[0]
1009         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1010         slice_cred = self.get_slice_cred(slice_hrn)
1011         creds = [slice_cred]
1012         if opts.delegate:
1013             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1014             creds.append(delegated_cred)
1015         server = self.server_proxy_from_opts(opts)
1016         return server.Shutdown(slice_urn, creds)         
1017     
1018
1019     def get_ticket(self, opts, args):
1020         """
1021         get a ticket for the specified slice
1022         """
1023         slice_hrn, rspec_path = args[0], args[1]
1024         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1025         slice_cred = self.get_slice_cred(slice_hrn)
1026         creds = [slice_cred]
1027         if opts.delegate:
1028             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1029             creds.append(delegated_cred)
1030         rspec_file = self.get_rspec_file(rspec_path) 
1031         rspec = open(rspec_file).read()
1032         server = self.server_proxy_from_opts(opts)
1033         ticket_string = server.GetTicket(slice_urn, creds, rspec, [])
1034         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1035         self.logger.info("writing ticket to %s"%file)
1036         ticket = SfaTicket(string=ticket_string)
1037         ticket.save_to_file(filename=file, save_parents=True)
1038
1039     def redeem_ticket(self, opts, args):
1040         """
1041         Connects to nodes in a slice and redeems a ticket
1042 (slice hrn is retrieved from the ticket)
1043         """
1044         ticket_file = args[0]
1045         
1046         # get slice hrn from the ticket
1047         # use this to get the right slice credential 
1048         ticket = SfaTicket(filename=ticket_file)
1049         ticket.decode()
1050         slice_hrn = ticket.gidObject.get_hrn()
1051         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1052         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1053         slice_cred = self.get_slice_cred(slice_hrn)
1054         
1055         # get a list of node hostnames from the RSpec 
1056         tree = etree.parse(StringIO(ticket.rspec))
1057         root = tree.getroot()
1058         hostnames = root.xpath("./network/site/node/hostname/text()")
1059         
1060         # create an xmlrpc connection to the component manager at each of these
1061         # components and gall redeem_ticket
1062         connections = {}
1063         for hostname in hostnames:
1064             try:
1065                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1066                 server = self.server_proxy(hostname, CM_PORT, self.private_key, \
1067                                                self.my_gid, verbose=self.options.debug)
1068                 server.RedeemTicket(ticket.save_to_string(save_parents=True), slice_cred)
1069                 self.logger.info("Success")
1070             except socket.gaierror:
1071                 self.logger.error("redeem_ticket failed: Component Manager not accepting requests")
1072             except Exception, e:
1073                 self.logger.log_exc(e.message)
1074         return
1075
1076     def create_gid(self, opts, args):
1077         """
1078         Create a GID (CreateGid)
1079         """
1080         if len(args) < 1:
1081             self.print_help()
1082             sys.exit(1)
1083         target_hrn = args[0]
1084         gid = self.registry.CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1085         if opts.file:
1086             filename = opts.file
1087         else:
1088             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1089         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1090         GID(string=gid).save_to_file(filename)
1091          
1092
1093     def delegate(self, opts, args):
1094         """
1095         (locally) create delegate credential for use by given hrn
1096         """
1097         delegee_hrn = args[0]
1098         if opts.delegate_user:
1099             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1100         elif opts.delegate_slice:
1101             slice_cred = self.get_slice_cred(opts.delegate_slice)
1102             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1103         else:
1104             self.logger.warning("Must specify either --user or --slice <hrn>")
1105             return
1106         delegated_cred = Credential(string=cred)
1107         object_hrn = delegated_cred.get_gid_object().get_hrn()
1108         if opts.delegate_user:
1109             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1110                                   + get_leaf(object_hrn) + ".cred")
1111         elif opts.delegate_slice:
1112             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1113                                   + get_leaf(object_hrn) + ".cred")
1114
1115         delegated_cred.save_to_file(dest_fn, save_parents=True)
1116
1117         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1118     
1119     def get_trusted_certs(self, opts, args):
1120         """
1121         return uhe trusted certs at this interface (get_trusted_certs)
1122         """ 
1123         trusted_certs = self.registry.get_trusted_certs()
1124         for trusted_cert in trusted_certs:
1125             gid = GID(string=trusted_cert)
1126             gid.dump()
1127             cert = Certificate(string=trusted_cert)
1128             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1129         return 
1130