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