96a8d3b9089fed89dbc462c20d63b9f6c4a26308
[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         # do not append users, keys, or slice tags. Anything 
1031         # not contained in this request will be removed from the slice 
1032         options = {'append': False}
1033         if self.server_supports_options_arg(server):
1034             options['call_id'] = unique_call_id()
1035         call_args = [slice_urn, creds, rspec, users, options]
1036         result = server.CreateSliver(*call_args)
1037         value = ReturnValue.get_value(result)
1038         if opts.file is None:
1039             print value
1040         else:
1041             save_rspec_to_file (value, opts.file)
1042         return value
1043
1044     def delete(self, opts, args):
1045         """
1046         delete named slice (DeleteSliver)
1047         """
1048         slice_hrn = args[0]
1049         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1050         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1051         creds = [slice_cred]
1052         if opts.delegate:
1053             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1054             creds.append(delegated_cred)
1055         server = self.server_proxy_from_opts(opts)
1056         call_args = [slice_urn, creds]
1057         if self.server_supports_options_arg(server):
1058             options = {'call_id': unique_call_id()}
1059             call_args.append(options)
1060         return server.DeleteSliver(*call_args) 
1061   
1062     def status(self, opts, args):
1063         """
1064         retrieve slice status (SliverStatus)
1065         """
1066         slice_hrn = args[0]
1067         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1068         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1069         creds = [slice_cred]
1070         if opts.delegate:
1071             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1072             creds.append(delegated_cred)
1073         server = self.server_proxy_from_opts(opts)
1074         call_args = [slice_urn, creds]
1075         if self.server_supports_options_arg(server):
1076             options = {'call_id': unique_call_id()}
1077             call_args.append(options)
1078         result = server.SliverStatus(*call_args)
1079         value = ReturnValue.get_value(result)
1080         print value
1081         if opts.file:
1082             save_variable_to_file(value, opts.file, opts.fileformat)
1083
1084     def start(self, opts, args):
1085         """
1086         start named slice (Start)
1087         """
1088         slice_hrn = args[0]
1089         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1090         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1091         creds = [slice_cred]
1092         if opts.delegate:
1093             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1094             creds.append(delegated_cred)
1095         server = self.server_proxy_from_opts(opts)
1096         return server.Start(slice_urn, creds)
1097     
1098     def stop(self, opts, args):
1099         """
1100         stop named slice (Stop)
1101         """
1102         slice_hrn = args[0]
1103         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1104         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1105         creds = [slice_cred]
1106         if opts.delegate:
1107             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1108             creds.append(delegated_cred)
1109         server = self.server_proxy_from_opts(opts)
1110         return server.Stop(slice_urn, creds)
1111     
1112     # reset named slice
1113     def reset(self, opts, args):
1114         """
1115         reset named slice (reset_slice)
1116         """
1117         slice_hrn = args[0]
1118         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1119         server = self.server_proxy_from_opts(opts)
1120         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1121         creds = [slice_cred]
1122         if opts.delegate:
1123             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1124             creds.append(delegated_cred)
1125         return server.reset_slice(creds, slice_urn)
1126
1127     def renew(self, opts, args):
1128         """
1129         renew slice (RenewSliver)
1130         """
1131         slice_hrn = args[0]
1132         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1133         server = self.server_proxy_from_opts(opts)
1134         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1135         creds = [slice_cred]
1136         if opts.delegate:
1137             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1138             creds.append(delegated_cred)
1139         time = args[1]
1140         
1141         call_args = [slice_urn, creds, time]
1142         if self.server_supports_options_arg(server):
1143             options = {'call_id': unique_call_id()}
1144             call_args.append(options)
1145         result =  server.RenewSliver(*call_args)
1146         value = ReturnValue.get_value(result)
1147         return value
1148
1149
1150     def shutdown(self, opts, args):
1151         """
1152         shutdown named slice (Shutdown)
1153         """
1154         slice_hrn = args[0]
1155         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1156         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1157         creds = [slice_cred]
1158         if opts.delegate:
1159             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1160             creds.append(delegated_cred)
1161         server = self.server_proxy_from_opts(opts)
1162         return server.Shutdown(slice_urn, creds)         
1163     
1164
1165     def get_ticket(self, opts, args):
1166         """
1167         get a ticket for the specified slice
1168         """
1169         slice_hrn, rspec_path = args[0], args[1]
1170         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1171         user_cred = self.get_user_cred()
1172         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1173         creds = [slice_cred]
1174         if opts.delegate:
1175             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1176             creds.append(delegated_cred)
1177         rspec_file = self.get_rspec_file(rspec_path) 
1178         rspec = open(rspec_file).read()
1179         server = self.server_proxy_from_opts(opts)
1180         ticket_string = server.GetTicket(slice_urn, creds, rspec, [])
1181         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1182         self.logger.info("writing ticket to %s"%file)
1183         ticket = SfaTicket(string=ticket_string)
1184         ticket.save_to_file(filename=file, save_parents=True)
1185
1186     def redeem_ticket(self, opts, args):
1187         """
1188         Connects to nodes in a slice and redeems a ticket
1189 (slice hrn is retrieved from the ticket)
1190         """
1191         ticket_file = args[0]
1192         
1193         # get slice hrn from the ticket
1194         # use this to get the right slice credential 
1195         ticket = SfaTicket(filename=ticket_file)
1196         ticket.decode()
1197         slice_hrn = ticket.gidObject.get_hrn()
1198         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1199         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1200         user_cred = self.get_user_cred()
1201         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1202         
1203         # get a list of node hostnames from the RSpec 
1204         tree = etree.parse(StringIO(ticket.rspec))
1205         root = tree.getroot()
1206         hostnames = root.xpath("./network/site/node/hostname/text()")
1207         
1208         # create an xmlrpc connection to the component manager at each of these
1209         # components and gall redeem_ticket
1210         connections = {}
1211         for hostname in hostnames:
1212             try:
1213                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1214                 server = self.server_proxy(hostname, CM_PORT, self.key_file, \
1215                                                self.cert_file, self.options.debug)
1216                 server.RedeemTicket(ticket.save_to_string(save_parents=True), slice_cred)
1217                 self.logger.info("Success")
1218             except socket.gaierror:
1219                 self.logger.error("redeem_ticket failed: Component Manager not accepting requests")
1220             except Exception, e:
1221                 self.logger.log_exc(e.message)
1222         return
1223  
1224     def create_gid(self, opts, args):
1225         """
1226         Create a GID (CreateGid)
1227         """
1228         if len(args) < 1:
1229             self.print_help()
1230             sys.exit(1)
1231         target_hrn = args[0]
1232         user_cred = self.get_user_cred().save_to_string(save_parents=True)
1233         gid = self.registry.CreateGid(user_cred, target_hrn, self.cert.save_to_string())
1234         if opts.file:
1235             filename = opts.file
1236         else:
1237             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1238         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1239         GID(string=gid).save_to_file(filename)
1240          
1241
1242     def delegate(self, opts, args):
1243         """
1244         (locally) create delegate credential for use by given hrn
1245         """
1246         delegee_hrn = args[0]
1247         if opts.delegate_user:
1248             user_cred = self.get_user_cred()
1249             cred = self.delegate_cred(user_cred, delegee_hrn)
1250         elif opts.delegate_slice:
1251             slice_cred = self.get_slice_cred(opts.delegate_slice)
1252             cred = self.delegate_cred(slice_cred, delegee_hrn)
1253         else:
1254             self.logger.warning("Must specify either --user or --slice <hrn>")
1255             return
1256         delegated_cred = Credential(string=cred)
1257         object_hrn = delegated_cred.get_gid_object().get_hrn()
1258         if opts.delegate_user:
1259             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1260                                   + get_leaf(object_hrn) + ".cred")
1261         elif opts.delegate_slice:
1262             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1263                                   + get_leaf(object_hrn) + ".cred")
1264
1265         delegated_cred.save_to_file(dest_fn, save_parents=True)
1266
1267         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1268     
1269     def get_trusted_certs(self, opts, args):
1270         """
1271         return uhe trusted certs at this interface (get_trusted_certs)
1272         """ 
1273         trusted_certs = self.registry.get_trusted_certs()
1274         for trusted_cert in trusted_certs:
1275             gid = GID(string=trusted_cert)
1276             gid.dump()
1277             cert = Certificate(string=trusted_cert)
1278             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1279         return 
1280
1281 if __name__ == "__main__":
1282     Sfi().main()