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