When calling CreateSliver, only attempt to fetch user keys from user record
[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         print records
698         records = filter_records(opts.type, records)
699         if not records:
700             print "No record of type", opts.type
701         for record in records:
702             if record['type'] in ['user']:
703                 record = UserRecord(dict=record)
704             elif record['type'] in ['slice']:
705                 record = SliceRecord(dict=record)
706             elif record['type'] in ['node']:
707                 record = NodeRecord(dict=record)
708             elif record['type'].startswith('authority'):
709                 record = AuthorityRecord(dict=record)
710             else:
711                 record = SfaRecord(dict=record)
712             if (opts.format == "text"): 
713                 record.dump()  
714             else:
715                 print record.save_to_string() 
716         if opts.file:
717             file = opts.file
718             if not file.startswith(os.sep):
719                 file = os.path.join(self.options.sfi_dir, file)
720             save_records_to_file(file, records)
721         return
722     
723     def delegate(self, opts, args):
724
725         delegee_hrn = args[0]
726         if opts.delegate_user:
727             user_cred = self.get_user_cred()
728             cred = self.delegate_cred(user_cred, delegee_hrn)
729         elif opts.delegate_slice:
730             slice_cred = self.get_slice_cred(opts.delegate_slice)
731             cred = self.delegate_cred(slice_cred, delegee_hrn)
732         else:
733             self.logger.warning("Must specify either --user or --slice <hrn>")
734             return
735         delegated_cred = Credential(string=cred)
736         object_hrn = delegated_cred.get_gid_object().get_hrn()
737         if opts.delegate_user:
738             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
739                                   + get_leaf(object_hrn) + ".cred")
740         elif opts.delegate_slice:
741             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
742                                   + get_leaf(object_hrn) + ".cred")
743
744         delegated_cred.save_to_file(dest_fn, save_parents=True)
745
746         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
747     
748     def delegate_cred(self, object_cred, hrn):
749         # the gid and hrn of the object we are delegating
750         if isinstance(object_cred, str):
751             object_cred = Credential(string=object_cred) 
752         object_gid = object_cred.get_gid_object()
753         object_hrn = object_gid.get_hrn()
754     
755         if not object_cred.get_privileges().get_all_delegate():
756             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
757             return
758
759         # the delegating user's gid
760         caller_gid = self._get_gid(self.user)
761         caller_gidfile = os.path.join(self.options.sfi_dir, self.user + ".gid")
762   
763         # the gid of the user who will be delegated to
764         delegee_gid = self._get_gid(hrn)
765         delegee_hrn = delegee_gid.get_hrn()
766         delegee_gidfile = os.path.join(self.options.sfi_dir, delegee_hrn + ".gid")
767         delegee_gid.save_to_file(filename=delegee_gidfile)
768         dcred = object_cred.delegate(delegee_gidfile, self.get_key_file(), caller_gidfile)
769         return dcred.save_to_string(save_parents=True)
770      
771     # removed named registry record
772     #   - have to first retrieve the record to be removed
773     def remove(self, opts, args):
774         auth_cred = self.get_auth_cred().save_to_string(save_parents=True)
775         if len(args)!=1:
776             self.print_help()
777             sys.exit(1)
778         hrn = args[0]
779         type = opts.type 
780         if type in ['all']:
781             type = '*'
782         return self.registry.Remove(hrn, auth_cred, type)
783     
784     # add named registry record
785     def add(self, opts, args):
786         auth_cred = self.get_auth_cred().save_to_string(save_parents=True)
787         if len(args)!=1:
788             self.print_help()
789             sys.exit(1)
790         record_filepath = args[0]
791         rec_file = self.get_record_file(record_filepath)
792         record = load_record_from_file(rec_file).as_dict()
793         return self.registry.Register(record, auth_cred)
794     
795     # update named registry entry
796     def update(self, opts, args):
797         user_cred = self.get_user_cred()
798         if len(args)!=1:
799             self.print_help()
800             sys.exit(1)
801         rec_file = self.get_record_file(args[0])
802         record = load_record_from_file(rec_file)
803         if record['type'] == "user":
804             if record.get_name() == user_cred.get_gid_object().get_hrn():
805                 cred = user_cred.save_to_string(save_parents=True)
806             else:
807                 cred = self.get_auth_cred().save_to_string(save_parents=True)
808         elif record['type'] in ["slice"]:
809             try:
810                 cred = self.get_slice_cred(record.get_name()).save_to_string(save_parents=True)
811             except xmlrpcprotocol.ServerException, e:
812                # XXX smbaker -- once we have better error return codes, update this
813                # to do something better than a string compare
814                if "Permission error" in e.args[0]:
815                    cred = self.get_auth_cred().save_to_string(save_parents=True)
816                else:
817                    raise
818         elif record.get_type() in ["authority"]:
819             cred = self.get_auth_cred().save_to_string(save_parents=True)
820         elif record.get_type() == 'node':
821             cred = self.get_auth_cred().save_to_string(save_parents=True)
822         else:
823             raise "unknown record type" + record.get_type()
824         record = record.as_dict()
825         return self.registry.Update(record, cred)
826   
827     def get_trusted_certs(self, opts, args):
828         """
829         return uhe trusted certs at this interface 
830         """ 
831         trusted_certs = self.registry.get_trusted_certs()
832         for trusted_cert in trusted_certs:
833             gid = GID(string=trusted_cert)
834             gid.dump()
835             cert = Certificate(string=trusted_cert)
836             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
837         return 
838
839     def aggregates(self, opts, args):
840         """
841         return a list of details about known aggregates
842         """
843         user_cred = self.get_user_cred().save_to_string(save_parents=True)
844         hrn = None
845         if args:
846             hrn = args[0]
847
848         result = self.registry.get_aggregates(user_cred, hrn)
849         display_list(result)
850         return 
851
852     def registries(self, opts, args):
853         """
854         return a list of details about known registries
855         """
856         user_cred = self.get_user_cred().save_to_string(save_parents=True)
857         hrn = None
858         if args:
859             hrn = args[0]
860         result = self.registry.get_registries(user_cred, hrn)
861         display_list(result)
862         return
863
864  
865     # ==================================================================
866     # Slice-related commands
867     # ==================================================================
868
869     def version(self, opts, args):
870         if opts.version_local:
871             version=version_core()
872         else:
873             if opts.version_registry:
874                 server=self.registry
875             else:
876                 server = self.get_server_from_opts(opts)
877             version=server.GetVersion()
878         for (k,v) in version.iteritems():
879             print "%-20s: %s"%(k,v)
880
881     # list instantiated slices
882     def slices(self, opts, args):
883         """
884         list instantiated slices
885         """
886         user_cred = self.get_user_cred().save_to_string(save_parents=True)
887         creds = [user_cred]
888         if opts.delegate:
889             delegated_cred = self.delegate_cred(user_cred, get_authority(self.authority))
890             creds.append(delegated_cred)  
891         server = self.get_server_from_opts(opts)
892         #results = server.ListSlices(creds, unique_call_id())
893         results = server.ListSlices(creds)
894         display_list(results)
895         return
896     
897     # show rspec for named slice
898     def resources(self, opts, args):
899         user_cred = self.get_user_cred().save_to_string(save_parents=True)
900         server = self.slicemgr
901         call_options = {}
902         server = self.get_server_from_opts(opts)
903         
904         if args:
905             cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
906             hrn = args[0]
907             call_options = {'geni_slice_urn': hrn_to_urn(hrn, 'slice')}
908         else:
909             cred = user_cred
910             hrn = None
911      
912         creds = [cred]
913         if opts.delegate:
914             delegated_cred = self.delegate_cred(cred, get_authority(self.authority))
915             creds.append(delegated_cred)
916         if opts.rspec_version:
917             server_version = self.get_cached_server_version(server)
918             if 'sfa' in server_version:
919                 # just request the version the client wants 
920                 call_options['rspec_version'] = dict(RSpecVersion(opts.rspec_version)) 
921             else:
922                 # this must be a protogeni aggregate. We should request a v2 ad rspec
923                 # regardless of what the client user requested 
924                 call_options['rspec_version'] = dict(pg_rspec_request_version)     
925         #panos add info options
926         if opts.info:
927             call_options['info'] = opts.info 
928
929         call_args = [creds, call_options]
930         if self.server_supports_call_id_arg(server):
931             call_args.append(unique_call_id())
932         result = server.ListResources(*call_args)
933         format = opts.format
934         if opts.file is None:
935             display_rspec(result, format)
936         else:
937             file = opts.file
938             if not file.startswith(os.sep):
939                 file = os.path.join(self.options.sfi_dir, file)
940             save_rspec_to_file(result, file)
941         return
942     
943     # created named slice with given rspec
944     def create(self, opts, args):
945         server = self.get_server_from_opts(opts)
946         server_version = self.get_cached_server_version(server)
947         slice_hrn = args[0]
948         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
949         user_cred = self.get_user_cred()
950         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
951         creds = [slice_cred]
952         if opts.delegate:
953             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
954             creds.append(delegated_cred)
955         rspec_file = self.get_rspec_file(args[1])
956         rspec = open(rspec_file).read()
957
958         # need to pass along user keys to the aggregate.  
959         # users = [
960         #  { urn: urn:publicid:IDN+emulab.net+user+alice
961         #    keys: [<ssh key A>, <ssh key B>] 
962         #  }]
963         users = []
964         all_keys = []
965         all_key_ids = []
966         slice_records = self.registry.Resolve(slice_urn, [user_cred.save_to_string(save_parents=True)])
967         if slice_records and 'researcher' in slice_records[0]:
968             slice_record = slice_records[0]
969             user_hrns = slice_record['researcher']
970             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
971             user_records = self.registry.Resolve(user_urns, [user_cred.save_to_string(save_parents=True)])
972             for user_record in user_records:
973                 if user_record['type'] != 'user':
974                     continue
975                 #user = {'urn': user_cred.get_gid_caller().get_urn(),'keys': []}
976                 user = {'urn': user_cred.get_gid_caller().get_urn(), #
977                         'keys': user_record['keys'],
978                         'email': user_record['email'], #  needed for MyPLC
979                         'person_id': user_record['person_id'], # needed for MyPLC
980                         'first_name': user_record['first_name'], # needed for MyPLC
981                         'last_name': user_record['last_name'], # needed for MyPLC
982                         'slice_record': slice_record, # needed for legacy refresh peer
983                         'key_ids': user_record['key_ids'] # needed for legacy refresh peer
984                 } 
985                 users.append(user)
986                 all_keys.extend(user_record['keys'])
987                 all_key_ids.extend(user_record['key_ids'])
988             # ProtoGeni Aggregates will only install the keys of the user that is issuing the
989             # request. So we will add all to the current caller's list of keys
990             if 'sfa' not in server_version:
991                 for user in users:
992                     if user['urn'] == user_cred.get_gid_caller().get_urn():
993                         user['keys'] = all_keys  
994
995         call_args = [slice_urn, creds, rspec, users]
996         if self.server_supports_call_id_arg(server):
997             call_args.append(unique_call_id())
998              
999         result = server.CreateSliver(*call_args)
1000         print result
1001         return result
1002
1003     # get a ticket for the specified slice
1004     def get_ticket(self, opts, args):
1005         slice_hrn, rspec_path = args[0], args[1]
1006         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1007         user_cred = self.get_user_cred()
1008         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1009         creds = [slice_cred]
1010         if opts.delegate:
1011             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1012             creds.append(delegated_cred)
1013         rspec_file = self.get_rspec_file(rspec_path) 
1014         rspec = open(rspec_file).read()
1015         server = self.get_server_from_opts(opts)
1016         ticket_string = server.GetTicket(slice_urn, creds, rspec, [])
1017         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1018         self.logger.info("writing ticket to %s"%file)
1019         ticket = SfaTicket(string=ticket_string)
1020         ticket.save_to_file(filename=file, save_parents=True)
1021
1022     def redeem_ticket(self, opts, args):
1023         ticket_file = args[0]
1024         
1025         # get slice hrn from the ticket
1026         # use this to get the right slice credential 
1027         ticket = SfaTicket(filename=ticket_file)
1028         ticket.decode()
1029         slice_hrn = ticket.gidObject.get_hrn()
1030         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1031         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1032         user_cred = self.get_user_cred()
1033         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1034         
1035         # get a list of node hostnames from the RSpec 
1036         tree = etree.parse(StringIO(ticket.rspec))
1037         root = tree.getroot()
1038         hostnames = root.xpath("./network/site/node/hostname/text()")
1039         
1040         # create an xmlrpc connection to the component manager at each of these
1041         # components and gall redeem_ticket
1042         connections = {}
1043         for hostname in hostnames:
1044             try:
1045                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1046                 server = self.get_server(hostname, CM_PORT, self.key_file, \
1047                                          self.cert_file, self.options.debug)
1048                 server.RedeemTicket(ticket.save_to_string(save_parents=True), slice_cred)
1049                 self.logger.info("Success")
1050             except socket.gaierror:
1051                 self.logger.error("redeem_ticket failed: Component Manager not accepting requests")
1052             except Exception, e:
1053                 self.logger.log_exc(e.message)
1054         return
1055  
1056     # delete named slice
1057     def delete(self, opts, args):
1058         slice_hrn = args[0]
1059         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1060         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1061         creds = [slice_cred]
1062         if opts.delegate:
1063             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1064             creds.append(delegated_cred)
1065         server = self.get_server_from_opts(opts)
1066
1067         call_args = [slice_urn, creds]
1068         if self.server_supports_call_id_arg(server):
1069             call_args.append(unique_call_id())
1070         return server.DeleteSliver(*call_args) 
1071   
1072     # start named slice
1073     def start(self, opts, args):
1074         slice_hrn = args[0]
1075         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1076         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1077         creds = [slice_cred]
1078         if opts.delegate:
1079             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1080             creds.append(delegated_cred)
1081         server = self.get_server_from_opts(opts)
1082         return server.Start(slice_urn, creds)
1083     
1084     # stop named slice
1085     def stop(self, opts, args):
1086         slice_hrn = args[0]
1087         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1088         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1089         creds = [slice_cred]
1090         if opts.delegate:
1091             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1092             creds.append(delegated_cred)
1093         server = self.get_server_from_opts(opts)
1094         return server.Stop(slice_urn, creds)
1095     
1096     # reset named slice
1097     def reset(self, opts, args):
1098         slice_hrn = args[0]
1099         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1100         server = self.get_server_from_opts(opts)
1101         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1102         creds = [slice_cred]
1103         if opts.delegate:
1104             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1105             creds.append(delegated_cred)
1106         return server.reset_slice(creds, slice_urn)
1107
1108     def renew(self, opts, args):
1109         slice_hrn = args[0]
1110         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1111         server = self.get_server_from_opts(opts)
1112         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1113         creds = [slice_cred]
1114         if opts.delegate:
1115             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1116             creds.append(delegated_cred)
1117         time = args[1]
1118         
1119         call_args = [slice_urn, creds, time]
1120         if self.server_supports_call_id_arg(server):
1121             call_args.append(unique_call_id())
1122         return server.RenewSliver(*call_args)
1123
1124
1125     def status(self, opts, args):
1126         slice_hrn = args[0]
1127         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1128         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1129         creds = [slice_cred]
1130         if opts.delegate:
1131             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1132             creds.append(delegated_cred)
1133         server = self.get_server_from_opts(opts)
1134         call_args = [slice_urn, creds]
1135         if self.server_supports_call_id_arg(server):
1136             call_args.append(unique_call_id())
1137         print server.SliverStatus(*call_args)
1138
1139
1140     def shutdown(self, opts, args):
1141         slice_hrn = args[0]
1142         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1143         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1144         creds = [slice_cred]
1145         if opts.delegate:
1146             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1147             creds.append(delegated_cred)
1148         server = self.get_server_from_opts(opts)
1149         return server.Shutdown(slice_urn, creds)         
1150     
1151     def print_help (self):
1152         self.sfi_parser.print_help()
1153         self.cmd_parser.print_help()
1154
1155     #
1156     # Main: parse arguments and dispatch to command
1157     #
1158     def main(self):
1159         self.sfi_parser = self.create_parser()
1160         (options, args) = self.sfi_parser.parse_args()
1161         self.options = options
1162
1163         self.logger.setLevelFromOptVerbose(self.options.verbose)
1164         if options.hashrequest:
1165             self.hashrequest = True
1166  
1167         if len(args) <= 0:
1168             self.logger.critical("No command given. Use -h for help.")
1169             return -1
1170     
1171         command = args[0]
1172         self.cmd_parser = self.create_cmd_parser(command)
1173         (cmd_opts, cmd_args) = self.cmd_parser.parse_args(args[1:])
1174
1175         self.set_servers()
1176         self.logger.info("Command=%s" % command)
1177         if command in ("resources"):
1178             self.logger.debug("resources cmd_opts %s" % cmd_opts.format)
1179         elif command in ("list", "show", "remove"):
1180             self.logger.debug("cmd_opts.type %s" % cmd_opts.type)
1181         self.logger.debug('cmd_args %s' % cmd_args)
1182
1183         try:
1184             self.dispatch(command, cmd_opts, cmd_args)
1185         except KeyError:
1186             self.logger.critical ("Unknown command %s"%command)
1187             raise
1188             sys.exit(1)
1189     
1190         return
1191     
1192 if __name__ == "__main__":
1193     Sfi().main()