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