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