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