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