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