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