set append to False in user struct when calling CreateSliver().
[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 import codecs
15 from lxml import etree
16 from StringIO import StringIO
17 from types import StringTypes, ListType
18 from optparse import OptionParser
19
20 from sfa.util.sfalogging import sfi_logger
21 from sfa.trust.certificate import Keypair, Certificate
22 from sfa.trust.gid import GID
23 from sfa.trust.credential import Credential
24 from sfa.util.sfaticket import SfaTicket
25 from sfa.util.record import SfaRecord, UserRecord, SliceRecord, NodeRecord, AuthorityRecord
26 from sfa.util.xrn import Xrn, get_leaf, get_authority, hrn_to_urn
27 import sfa.util.xmlrpcprotocol as xmlrpcprotocol
28 from sfa.util.config import Config
29 from sfa.util.version import version_core
30 from sfa.util.cache import Cache
31 from sfa.rspecs.rspec_version import RSpecVersion
32 from sfa.rspecs.pg_rspec import pg_rspec_request_version
33
34 AGGREGATE_PORT=12346
35 CM_PORT=12346
36
37 # utility methods here
38 # display methods
39 def display_rspec(rspec, format='rspec'):
40     if format in ['dns']:
41         tree = etree.parse(StringIO(rspec))
42         root = tree.getroot()
43         result = root.xpath("./network/site/node/hostname/text()")
44     elif format in ['ip']:
45         # The IP address is not yet part of the new RSpec
46         # so this doesn't do anything yet.
47         tree = etree.parse(StringIO(rspec))
48         root = tree.getroot()
49         result = root.xpath("./network/site/node/ipv4/text()")
50     else:
51         result = rspec
52
53     print result
54     return
55
56 def display_list(results):
57     for result in results:
58         print result
59
60 def display_records(recordList, dump=False):
61     ''' Print all fields in the record'''
62     for record in recordList:
63         display_record(record, dump)
64
65 def display_record(record, dump=False):
66     if dump:
67         record.dump()
68     else:
69         info = record.getdict()
70         print "%s (%s)" % (info['hrn'], info['type'])
71     return
72
73
74 def filter_records(type, records):
75     filtered_records = []
76     for record in records:
77         if (record['type'] == type) or (type == "all"):
78             filtered_records.append(record)
79     return filtered_records
80
81
82 # save methods
83 def save_rspec_to_file(rspec, filename):
84     if not filename.endswith(".rspec"):
85         filename = filename + ".rspec"
86     f = open(filename, 'w')
87     f.write(rspec)
88     f.close()
89     return
90
91 def save_records_to_file(filename, recordList):
92     index = 0
93     for record in recordList:
94         if index > 0:
95             save_record_to_file(filename + "." + str(index), record)
96         else:
97             save_record_to_file(filename, record)
98         index = index + 1
99
100 def save_record_to_file(filename, record):
101     if record['type'] in ['user']:
102         record = UserRecord(dict=record)
103     elif record['type'] in ['slice']:
104         record = SliceRecord(dict=record)
105     elif record['type'] in ['node']:
106         record = NodeRecord(dict=record)
107     elif record['type'] in ['authority', 'ma', 'sa']:
108         record = AuthorityRecord(dict=record)
109     else:
110         record = SfaRecord(dict=record)
111     str = record.save_to_string()
112     f=codecs.open(filename, encoding='utf-8',mode="w")
113     f.write(str)
114     f.close()
115     return
116
117
118 # load methods
119 def load_record_from_file(filename):
120     f=codecs.open(filename, encoding="utf-8", mode="r")
121     str = f.read()
122     f.close()
123     record = SfaRecord(string=str)
124     return record
125
126
127 import uuid
128 def unique_call_id(): return uuid.uuid4().urn
129
130 class Sfi:
131     
132     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user']
133
134     # dummy to meet Sfi's expectations for its 'options' field
135     # i.e. s/t we can do setattr on
136     class DummyOptions:
137         pass
138
139     def __init__ (self,options=None):
140         if options is None: options=Sfi.DummyOptions()
141         for opt in Sfi.required_options:
142             if not hasattr(options,opt): setattr(options,opt,None)
143         if not hasattr(options,'sfi_dir'): options.sfi_dir=os.path.expanduser("~/.sfi/")
144         # xxx oops, this is dangerous, sounds like ww sometimes have discrepency
145         # would be safer to remove self.sfi_dir altogether
146         self.sfi_dir = options.sfi_dir
147         self.options = options
148         self.slicemgr = None
149         self.registry = None
150         self.user = None
151         self.authority = None
152         self.hashrequest = False
153         self.logger = sfi_logger
154         self.logger.enable_console()
155    
156     def create_cmd_parser(self, command, additional_cmdargs=None):
157         cmdargs = {"list": "authority",
158                   "show": "name",
159                   "remove": "name",
160                   "add": "record",
161                   "update": "record",
162                   "aggregates": "[name]",
163                   "registries": "[name]",
164                   "create_gid": "[name]",
165                   "get_gid": [],  
166                   "get_trusted_certs": "cred",
167                   "slices": "",
168                   "resources": "[name]",
169                   "create": "name rspec",
170                   "get_ticket": "name rspec",
171                   "redeem_ticket": "ticket",
172                   "delete": "name",
173                   "reset": "name",
174                   "start": "name",
175                   "stop": "name",
176                   "delegate": "name",
177                   "status": "name",
178                   "renew": "name",
179                   "shutdown": "name",
180                   "version": "",  
181                  }
182
183         if additional_cmdargs:
184             cmdargs.update(additional_cmdargs)
185
186         if command not in cmdargs:
187             msg="Invalid command\n"
188             msg+="Commands: "
189             msg += ','.join(cmdargs.keys())            
190             self.logger.critical(msg)
191             sys.exit(2)
192
193         parser = OptionParser(usage="sfi [sfi_options] %s [options] %s" \
194                                      % (command, cmdargs[command]))
195
196         # user specifies remote aggregate/sm/component                          
197         if command in ("resources", "slices", "create", "delete", "start", "stop", 
198                        "restart", "shutdown",  "get_ticket", "renew", "status"):
199             parser.add_option("-a", "--aggregate", dest="aggregate",
200                              default=None, help="aggregate host")
201             parser.add_option("-p", "--port", dest="port",
202                              default=AGGREGATE_PORT, help="aggregate port")
203             parser.add_option("-c", "--component", dest="component", default=None,
204                              help="component hrn")
205             parser.add_option("-d", "--delegate", dest="delegate", default=None, 
206                              action="store_true",
207                              help="Include a credential delegated to the user's root"+\
208                                   "authority in set of credentials for this call")  
209         
210         # registy filter option    
211         if command in ("list", "show", "remove"):
212             parser.add_option("-t", "--type", dest="type", type="choice",
213                             help="type filter ([all]|user|slice|authority|node|aggregate)",
214                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
215                             default="all")
216         # display formats
217         if command in ("resources"):
218             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
219                               help="schema type and version of resulting RSpec")
220             parser.add_option("-f", "--format", dest="format", type="choice",
221                              help="display format ([xml]|dns|ip)", default="xml",
222                              choices=("xml", "dns", "ip"))
223             #panos: a new option to define the type of information about resources a user is interested in
224             parser.add_option("-i", "--info", dest="info",
225                                 help="optional component information", default=None)
226
227
228         # 'create' does return the new rspec, makes sense to save that too
229         if command in ("resources", "show", "list", "create_gid", 'create'):
230            parser.add_option("-o", "--output", dest="file",
231                             help="output XML to file", metavar="FILE", default=None)
232         
233         if command in ("show", "list"):
234            parser.add_option("-f", "--format", dest="format", type="choice",
235                              help="display format ([text]|xml)", default="text",
236                              choices=("text", "xml"))
237
238         if command in ("delegate"):
239            parser.add_option("-u", "--user",
240                             action="store_true", dest="delegate_user", default=False,
241                             help="delegate user credential")
242            parser.add_option("-s", "--slice", dest="delegate_slice",
243                             help="delegate slice credential", metavar="HRN", default=None)
244         
245         if command in ("version"):
246             parser.add_option("-a", "--aggregate", dest="aggregate",
247                              default=None, help="aggregate host")
248             parser.add_option("-p", "--port", dest="port",
249                              default=AGGREGATE_PORT, help="aggregate port")
250             parser.add_option("-R","--registry-version",
251                               action="store_true", dest="version_registry", default=False,
252                               help="probe registry version instead of slicemgr")
253             parser.add_option("-l","--local",
254                               action="store_true", dest="version_local", default=False,
255                               help="display version of the local client")
256
257         return parser
258
259         
260     def create_parser(self):
261
262         # Generate command line parser
263         parser = OptionParser(usage="sfi [options] command [command_options] [command_args]",
264                              description="Commands: gid,list,show,remove,add,update,nodes,slices,resources,create,delete,start,stop,reset")
265         parser.add_option("-r", "--registry", dest="registry",
266                          help="root registry", metavar="URL", default=None)
267         parser.add_option("-s", "--slicemgr", dest="sm",
268                          help="slice manager", metavar="URL", default=None)
269         default_sfi_dir = os.path.expanduser("~/.sfi/")
270         parser.add_option("-d", "--dir", dest="sfi_dir",
271                          help="config & working directory - default is " + default_sfi_dir,
272                          metavar="PATH", default=default_sfi_dir)
273         parser.add_option("-u", "--user", dest="user",
274                          help="user name", metavar="HRN", default=None)
275         parser.add_option("-a", "--auth", dest="auth",
276                          help="authority name", metavar="HRN", default=None)
277         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
278                          help="verbose mode - cumulative")
279         parser.add_option("-D", "--debug",
280                           action="store_true", dest="debug", default=False,
281                           help="Debug (xml-rpc) protocol messages")
282         parser.add_option("-p", "--protocol", dest="protocol", default="xmlrpc",
283                          help="RPC protocol (xmlrpc or soap)")
284         parser.add_option("-k", "--hashrequest",
285                          action="store_true", dest="hashrequest", default=False,
286                          help="Create a hash of the request that will be authenticated on the server")
287         parser.add_option("-t", "--timeout", dest="timeout", default=None,
288                          help="Amout of time tom wait before timing out the request")
289         parser.disable_interspersed_args()
290
291         return parser
292         
293
294     def read_config(self):
295        config_file = os.path.join(self.options.sfi_dir,"sfi_config")
296        try:
297           config = Config (config_file)
298        except:
299           self.logger.critical("Failed to read configuration file %s"%config_file)
300           self.logger.info("Make sure to remove the export clauses and to add quotes")
301           if self.options.verbose==0:
302               self.logger.info("Re-run with -v for more details")
303           else:
304               self.logger.log_exc("Could not read config file %s"%config_file)
305           sys.exit(1)
306     
307        errors = 0
308        # Set SliceMgr URL
309        if (self.options.sm is not None):
310           self.sm_url = self.options.sm
311        elif hasattr(config, "SFI_SM"):
312           self.sm_url = config.SFI_SM
313        else:
314           self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
315           errors += 1 
316     
317        # Set Registry URL
318        if (self.options.registry is not None):
319           self.reg_url = self.options.registry
320        elif hasattr(config, "SFI_REGISTRY"):
321           self.reg_url = config.SFI_REGISTRY
322        else:
323           self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
324           errors += 1 
325           
326
327        # Set user HRN
328        if (self.options.user is not None):
329           self.user = self.options.user
330        elif hasattr(config, "SFI_USER"):
331           self.user = config.SFI_USER
332        else:
333           self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
334           errors += 1 
335     
336        # Set authority HRN
337        if (self.options.auth is not None):
338           self.authority = self.options.auth
339        elif hasattr(config, "SFI_AUTH"):
340           self.authority = config.SFI_AUTH
341        else:
342           self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
343           errors += 1 
344     
345        if errors:
346           sys.exit(1)
347
348
349     #
350     # Establish Connection to SliceMgr and Registry Servers
351     #
352     def set_servers(self):
353
354        self.read_config() 
355        # Get key and certificate
356        key_file = self.get_key_file()
357        cert_file = self.get_cert_file(key_file)
358        self.key = Keypair(filename=key_file) 
359        self.key_file = key_file
360        self.cert_file = cert_file
361        self.cert = GID(filename=cert_file)
362        self.logger.info("Contacting Registry at: %s"%self.reg_url)
363        self.registry = xmlrpcprotocol.get_server(self.reg_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)  
364        self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
365        self.slicemgr = xmlrpcprotocol.get_server(self.sm_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)
366        return
367
368     def get_cached_server_version(self, server):
369         # check local cache first
370         cache = None
371         version = None 
372         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
373         cache_key = server.url + "-version"
374         try:
375             cache = Cache(cache_file)
376         except IOError:
377             cache = Cache()
378             self.logger.info("Local cache not found at: %s" % cache_file)
379
380         if cache:
381             version = cache.get(cache_key)
382             
383         if not version: 
384             version = server.GetVersion()
385             # cache version for 24 hours
386             cache.add(cache_key, version, ttl= 60*60*24)
387             self.logger.info("Updating cache file %s" % cache_file)
388             cache.save_to_file(cache_file)
389
390
391         return version   
392         
393
394     def server_supports_call_id_arg(self, server):
395         """
396         Returns true if server support the optional call_id arg, false otherwise. 
397         """
398         server_version = self.get_cached_server_version(server)
399         if 'sfa' in server_version:
400             code_tag = server_version['code_tag']
401             code_tag_parts = code_tag.split("-")
402             
403             version_parts = code_tag_parts[0].split(".")
404             major, minor = version_parts[0], version_parts[1]
405             rev = code_tag_parts[1]
406             if int(major) > 1:
407                 if int(minor) > 0 or int(rev) > 20:
408                     return True
409         return False                
410              
411     #
412     # Get various credential and spec files
413     #
414     # Establishes limiting conventions
415     #   - conflates MAs and SAs
416     #   - assumes last token in slice name is unique
417     #
418     # Bootstraps credentials
419     #   - bootstrap user credential from self-signed certificate
420     #   - bootstrap authority credential from user credential
421     #   - bootstrap slice credential from user credential
422     #
423     
424     
425     def get_key_file(self):
426        file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".pkey")
427        if (os.path.isfile(file)):
428           return file
429        else:
430           self.logger.error("Key file %s does not exist"%file)
431           sys.exit(-1)
432        return
433     
434     def get_cert_file(self, key_file):
435     
436         cert_file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cert")
437         if (os.path.isfile(cert_file)):
438             # we'd perfer to use Registry issued certs instead of self signed certs. 
439             # if this is a Registry cert (GID) then we are done 
440             gid = GID(filename=cert_file)
441             if gid.get_urn():
442                 return cert_file
443
444         # generate self signed certificate
445         k = Keypair(filename=key_file)
446         cert = Certificate(subject=self.user)
447         cert.set_pubkey(k)
448         cert.set_issuer(k, self.user)
449         cert.sign()
450         self.logger.info("Writing self-signed certificate to %s"%cert_file)
451         cert.save_to_file(cert_file)
452         self.cert = cert
453         # try to get registry issued cert
454         try:
455             self.logger.info("Getting Registry issued cert")
456             self.read_config()
457             # *hack.  need to set registyr before _get_gid() is called 
458             self.registry = xmlrpcprotocol.get_server(self.reg_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)
459             gid = self._get_gid(type='user')
460             self.registry = None 
461             self.logger.info("Writing certificate to %s"%cert_file)
462             gid.save_to_file(cert_file)
463         except:
464             self.logger.info("Failed to download Registry issued cert")
465
466         return cert_file
467
468     def get_cached_gid(self, file):
469         """
470         Return a cached gid    
471         """
472         gid = None 
473         if (os.path.isfile(file)):
474             gid = GID(filename=file)
475         return gid
476
477     # xxx opts unused
478     def get_gid(self, opts, args):
479         """
480         Get the specify gid and save it to file
481         """
482         hrn = None
483         if args:
484             hrn = args[0]
485         gid = self._get_gid(hrn)
486         self.logger.debug("Sfi.get_gid-> %s",gid.save_to_string(save_parents=True))
487         return gid
488
489     def _get_gid(self, hrn=None, type=None):
490         """
491         git_gid helper. Retrive the gid from the registry and save it to file.
492         """
493         
494         if not hrn:
495             hrn = self.user
496  
497         gidfile = os.path.join(self.options.sfi_dir, hrn + ".gid")
498         print gidfile
499         gid = self.get_cached_gid(gidfile)
500         if not gid:
501             user_cred = self.get_user_cred()
502             records = self.registry.Resolve(hrn, user_cred.save_to_string(save_parents=True))
503             if not records:
504                 raise RecordNotFound(args[0])
505             record = records[0]
506             if type:
507                 record=None
508                 for rec in records:
509                    if type == rec['type']:
510                         record = rec 
511             if not record:
512                 raise RecordNotFound(args[0])
513             
514             gid = GID(string=record['gid'])
515             self.logger.info("Writing gid to %s"%gidfile)
516             gid.save_to_file(filename=gidfile)
517         return gid   
518                 
519      
520     def get_cached_credential(self, file):
521         """
522         Return a cached credential only if it hasn't expired.
523         """
524         if (os.path.isfile(file)):
525             credential = Credential(filename=file)
526             # make sure it isnt expired 
527             if not credential.get_expiration or \
528                datetime.datetime.today() < credential.get_expiration():
529                 return credential
530         return None 
531  
532     def get_user_cred(self):
533         file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cred")
534         return self.get_cred(file, 'user', self.user)
535
536     def get_auth_cred(self):
537         if not self.authority:
538             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
539             sys.exit(-1)
540         file = os.path.join(self.options.sfi_dir, self.authority + ".cred")
541         return self.get_cred(file, 'authority', self.authority)
542
543     def get_slice_cred(self, name):
544         file = os.path.join(self.options.sfi_dir, "slice_" + get_leaf(name) + ".cred")
545         return self.get_cred(file, 'slice', name)
546  
547     def get_cred(self, file, type, hrn):
548         # attempt to load a cached credential 
549         cred = self.get_cached_credential(file)    
550         if not cred:
551             if type in ['user']:
552                 cert_string = self.cert.save_to_string(save_parents=True)
553                 user_name = self.user.replace(self.authority + ".", '')
554                 if user_name.count(".") > 0:
555                     user_name = user_name.replace(".", '_')
556                     self.user = self.authority + "." + user_name
557                 cred_str = self.registry.GetSelfCredential(cert_string, hrn, "user")
558             else:
559                 # bootstrap slice credential from user credential
560                 user_cred = self.get_user_cred().save_to_string(save_parents=True)
561                 cred_str = self.registry.GetCredential(user_cred, hrn, type)
562             
563             if not cred_str:
564                 self.logger.critical("Failed to get %s credential" % type)
565                 sys.exit(-1)
566                 
567             cred = Credential(string=cred_str)
568             cred.save_to_file(file, save_parents=True)
569             self.logger.info("Writing %s credential to %s" %(type, file))
570
571         return cred
572  
573     
574     def get_rspec_file(self, rspec):
575        if (os.path.isabs(rspec)):
576           file = rspec
577        else:
578           file = os.path.join(self.options.sfi_dir, rspec)
579        if (os.path.isfile(file)):
580           return file
581        else:
582           self.logger.critical("No such rspec file %s"%rspec)
583           sys.exit(1)
584     
585     def get_record_file(self, record):
586        if (os.path.isabs(record)):
587           file = record
588        else:
589           file = os.path.join(self.options.sfi_dir, record)
590        if (os.path.isfile(file)):
591           return file
592        else:
593           self.logger.critical("No such registry record file %s"%record)
594           sys.exit(1)
595     
596     def load_publickey_string(self, fn):
597        f = file(fn, "r")
598        key_string = f.read()
599     
600        # if the filename is a private key file, then extract the public key
601        if "PRIVATE KEY" in key_string:
602            outfn = tempfile.mktemp()
603            cmd = "openssl rsa -in " + fn + " -pubout -outform PEM -out " + outfn
604            os.system(cmd)
605            f = file(outfn, "r")
606            key_string = f.read()
607            os.remove(outfn)
608     
609        return key_string
610
611     # xxx opts undefined
612     def get_component_server_from_hrn(self, hrn):
613         # direct connection to the nodes component manager interface
614         user_cred = self.get_user_cred().save_to_string(save_parents=True)
615         records = self.registry.Resolve(hrn, user_cred)
616         records = filter_records('node', records)
617         if not records:
618             self.logger.warning("No such component:%r"% opts.component)
619         record = records[0]
620   
621         return self.get_server(record['hostname'], CM_PORT, self.key_file, self.cert_file)
622  
623     def get_server(self, host, port, keyfile, certfile):
624         """
625         Return an instance of an xmlrpc server connection    
626         """
627         # port is appended onto the domain, before the path. Should look like:
628         # http://domain:port/path
629         host_parts = host.split('/')
630         host_parts[0] = host_parts[0] + ":" + str(port)
631         url =  "http://%s" %  "/".join(host_parts)    
632         return xmlrpcprotocol.get_server(url, keyfile, certfile, timeout=self.options.timeout, verbose=self.options.debug)
633
634     # xxx opts could be retrieved in self.options
635     def get_server_from_opts(self, opts):
636         """
637         Return instance of an xmlrpc connection to a slice manager, aggregate
638         or component server depending on the specified opts
639         """
640         server = self.slicemgr
641         # direct connection to an aggregate
642         if hasattr(opts, 'aggregate') and opts.aggregate:
643             server = self.get_server(opts.aggregate, opts.port, self.key_file, self.cert_file)
644         # direct connection to the nodes component manager interface
645         if hasattr(opts, 'component') and opts.component:
646             server = self.get_component_server_from_hrn(opts.component)    
647  
648         return server
649     #==========================================================================
650     # Following functions implement the commands
651     #
652     # Registry-related commands
653     #==========================================================================
654   
655     def dispatch(self, command, cmd_opts, cmd_args):
656         return getattr(self, command)(cmd_opts, cmd_args)
657
658     def create_gid(self, opts, args):
659         if len(args) < 1:
660             self.print_help()
661             sys.exit(1)
662         target_hrn = args[0]
663         user_cred = self.get_user_cred().save_to_string(save_parents=True)
664         gid = self.registry.CreateGid(user_cred, target_hrn, self.cert.save_to_string())
665         if opts.file:
666             filename = opts.file
667         else:
668             filename = os.sep.join([self.sfi_dir, '%s.gid' % target_hrn])
669         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
670         GID(string=gid).save_to_file(filename)
671          
672      
673     # list entires in named authority registry
674     def list(self, opts, args):
675         if len(args)!= 1:
676             self.print_help()
677             sys.exit(1)
678         hrn = args[0]
679         user_cred = self.get_user_cred().save_to_string(save_parents=True)
680         try:
681             list = self.registry.List(hrn, user_cred)
682         except IndexError:
683             raise Exception, "Not enough parameters for the 'list' command"
684           
685         # filter on person, slice, site, node, etc.  
686         # THis really should be in the self.filter_records funct def comment...
687         list = filter_records(opts.type, list)
688         for record in list:
689             print "%s (%s)" % (record['hrn'], record['type'])     
690         if opts.file:
691             save_records_to_file(opts.file, list)
692         return
693     
694     # show named registry record
695     def show(self, opts, args):
696         if len(args)!= 1:
697             self.print_help()
698             sys.exit(1)
699         hrn = args[0]
700         user_cred = self.get_user_cred().save_to_string(save_parents=True)
701         records = self.registry.Resolve(hrn, user_cred)
702         records = filter_records(opts.type, records)
703         if not records:
704             print "No record of type", opts.type
705         for record in records:
706             if record['type'] in ['user']:
707                 record = UserRecord(dict=record)
708             elif record['type'] in ['slice']:
709                 record = SliceRecord(dict=record)
710             elif record['type'] in ['node']:
711                 record = NodeRecord(dict=record)
712             elif record['type'].startswith('authority'):
713                 record = AuthorityRecord(dict=record)
714             else:
715                 record = SfaRecord(dict=record)
716             if (opts.format == "text"): 
717                 record.dump()  
718             else:
719                 print record.save_to_string() 
720         if opts.file:
721             save_records_to_file(opts.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         if opts.file is None:
935             display_rspec(result, opts.format)
936         else:
937             save_rspec_to_file(result, opts.file)
938         return
939     
940     # created named slice with given rspec
941     def create(self, opts, args):
942         server = self.get_server_from_opts(opts)
943         server_version = self.get_cached_server_version(server)
944         slice_hrn = args[0]
945         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
946         user_cred = self.get_user_cred()
947         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
948         creds = [slice_cred]
949         if opts.delegate:
950             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
951             creds.append(delegated_cred)
952         rspec_file = self.get_rspec_file(args[1])
953         rspec = open(rspec_file).read()
954
955         # need to pass along user keys to the aggregate.  
956         # users = [
957         #  { urn: urn:publicid:IDN+emulab.net+user+alice
958         #    keys: [<ssh key A>, <ssh key B>] 
959         #  }]
960         users = []
961         all_keys = []
962         all_key_ids = []
963         slice_records = self.registry.Resolve(slice_urn, [user_cred.save_to_string(save_parents=True)])
964         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
965             slice_record = slice_records[0]
966             user_hrns = slice_record['researcher']
967             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
968             user_records = self.registry.Resolve(user_urns, [user_cred.save_to_string(save_parents=True)])
969             for user_record in user_records:
970                 if user_record['type'] != 'user':
971                     continue
972                 #user = {'urn': user_cred.get_gid_caller().get_urn(),'keys': []}
973                 user = {'urn': user_cred.get_gid_caller().get_urn(), #
974                         'keys': user_record['keys'],
975                         'email': user_record['email'], #  needed for MyPLC
976                         'person_id': user_record['person_id'], # needed for MyPLC
977                         'first_name': user_record['first_name'], # needed for MyPLC
978                         'last_name': user_record['last_name'], # needed for MyPLC
979                         'slice_record': slice_record, # needed for legacy refresh peer
980                         'key_ids': user_record['key_ids'] # needed for legacy refresh peer
981                 } 
982                 users.append(user)
983                 all_keys.extend(user_record['keys'])
984                 all_key_ids.extend(user_record['key_ids'])
985             # ProtoGeni Aggregates will only install the keys of the user that is issuing the
986             # request. So we will add all to the current caller's list of keys
987             if 'sfa' not in server_version:
988                 for user in users:
989                     if user['urn'] == user_cred.get_gid_caller().get_urn():
990                         user['keys'] = all_keys  
991
992         call_args = [slice_urn, creds, rspec, users]
993         if self.server_supports_call_id_arg(server):
994             call_args.append(unique_call_id())
995              
996         result = server.CreateSliver(*call_args)
997         if opts.file is None:
998             print result
999         else:
1000             save_rspec_to_file (result, opts.file)
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()