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