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