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