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