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