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