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