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