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