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