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