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