minor cleanup
[sfa.git] / sfa / client / sfi.py
1 #! /usr/bin/env python
2
3 # sfi -- slice-based facility interface
4
5 # xxx NOTE this will soon be reviewed to take advantage of sfaclientlib
6
7 import sys
8 sys.path.append('.')
9
10 import os, os.path
11 #import tempfile
12 import socket
13 import datetime
14 import codecs
15 import pickle
16 from lxml import etree
17 from StringIO import StringIO
18 from optparse import OptionParser
19
20 from sfa.trust.certificate import Keypair, Certificate
21 from sfa.trust.gid import GID
22 from sfa.trust.credential import Credential
23 from sfa.trust.sfaticket import SfaTicket
24
25 from sfa.util.sfalogging import sfi_logger
26 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn
27 from sfa.util.config import Config
28 from sfa.util.version import version_core
29 from sfa.util.cache import Cache
30
31 from sfa.storage.record import SfaRecord, UserRecord, SliceRecord, NodeRecord, AuthorityRecord
32
33 from sfa.rspecs.rspec import RSpec
34 from sfa.rspecs.rspec_converter import RSpecConverter
35 from sfa.rspecs.version_manager import VersionManager
36 from sfa.client.return_value import ReturnValue
37
38 import sfa.client.sfaprotocol as sfaprotocol
39 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
40
41 AGGREGATE_PORT=12346
42 CM_PORT=12346
43
44 # utility methods here
45 # display methods
46 def display_rspec(rspec, format='rspec'):
47     if format in ['dns']:
48         tree = etree.parse(StringIO(rspec))
49         root = tree.getroot()
50         result = root.xpath("./network/site/node/hostname/text()")
51     elif format in ['ip']:
52         # The IP address is not yet part of the new RSpec
53         # so this doesn't do anything yet.
54         tree = etree.parse(StringIO(rspec))
55         root = tree.getroot()
56         result = root.xpath("./network/site/node/ipv4/text()")
57     else:
58         result = rspec
59
60     print result
61     return
62
63 def display_list(results):
64     for result in results:
65         print result
66
67 def display_records(recordList, dump=False):
68     ''' Print all fields in the record'''
69     for record in recordList:
70         display_record(record, dump)
71
72 def display_record(record, dump=False):
73     if dump:
74         record.dump()
75     else:
76         info = record.getdict()
77         print "%s (%s)" % (info['hrn'], info['type'])
78     return
79
80
81 def filter_records(type, records):
82     filtered_records = []
83     for record in records:
84         if (record['type'] == type) or (type == "all"):
85             filtered_records.append(record)
86     return filtered_records
87
88
89 # save methods
90 def save_variable_to_file(var, filename, format="text"):
91     f = open(filename, "w")
92     if format == "text":
93         f.write(str(var))
94     elif format == "pickled":
95         f.write(pickle.dumps(var))
96     else:
97         # this should never happen
98         print "unknown output format", format
99
100
101 def save_rspec_to_file(rspec, filename):
102     if not filename.endswith(".rspec"):
103         filename = filename + ".rspec"
104     f = open(filename, 'w')
105     f.write(rspec)
106     f.close()
107     return
108
109 def save_records_to_file(filename, recordList, format="xml"):
110     if format == "xml":
111         index = 0
112         for record in recordList:
113             if index > 0:
114                 save_record_to_file(filename + "." + str(index), record)
115             else:
116                 save_record_to_file(filename, record)
117             index = index + 1
118     elif format == "xmllist":
119         f = open(filename, "w")
120         f.write("<recordlist>\n")
121         for record in recordList:
122             record = SfaRecord(dict=record)
123             f.write('<record hrn="' + record.get_name() + '" type="' + record.get_type() + '" />\n')
124         f.write("</recordlist>\n")
125         f.close()
126     elif format == "hrnlist":
127         f = open(filename, "w")
128         for record in recordList:
129             record = SfaRecord(dict=record)
130             f.write(record.get_name() + "\n")
131         f.close()
132     else:
133         # this should never happen
134         print "unknown output format", format
135
136 def save_record_to_file(filename, record):
137     if record['type'] in ['user']:
138         record = UserRecord(dict=record)
139     elif record['type'] in ['slice']:
140         record = SliceRecord(dict=record)
141     elif record['type'] in ['node']:
142         record = NodeRecord(dict=record)
143     elif record['type'] in ['authority', 'ma', 'sa']:
144         record = AuthorityRecord(dict=record)
145     else:
146         record = SfaRecord(dict=record)
147     str = record.save_to_string()
148     f=codecs.open(filename, encoding='utf-8',mode="w")
149     f.write(str)
150     f.close()
151     return
152
153
154 # load methods
155 def load_record_from_file(filename):
156     f=codecs.open(filename, encoding="utf-8", mode="r")
157     str = f.read()
158     f.close()
159     record = SfaRecord(string=str)
160     return record
161
162
163 import uuid
164 def unique_call_id(): return uuid.uuid4().urn
165
166 class Sfi:
167     
168     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user']
169
170     @staticmethod
171     def default_sfi_dir ():
172         if os.path.isfile("./sfi_config"): 
173             return os.getcwd()
174         else:
175             return os.path.expanduser("~/.sfi/")
176
177     # dummy to meet Sfi's expectations for its 'options' field
178     # i.e. s/t we can do setattr on
179     class DummyOptions:
180         pass
181
182     def __init__ (self,options=None):
183         if options is None: options=Sfi.DummyOptions()
184         for opt in Sfi.required_options:
185             if not hasattr(options,opt): setattr(options,opt,None)
186         if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
187         self.options = options
188         self.slicemgr = None
189         self.registry = None
190         self.user = None
191         self.authority = None
192         self.hashrequest = False
193         self.logger = sfi_logger
194         self.logger.enable_console()
195    
196     def create_cmd_parser(self, command):
197         cmdargs = {"list": "authority",
198                   "show": "name",
199                   "remove": "name",
200                   "add": "record",
201                   "update": "record",
202                   "create_gid": "[name]",
203                   "get_gid": [],  
204                   "get_trusted_certs": "cred",
205                   "slices": "",
206                   "resources": "[name]",
207                   "create": "name rspec",
208                   "get_ticket": "name rspec",
209                   "redeem_ticket": "ticket",
210                   "delete": "name",
211                   "reset": "name",
212                   "start": "name",
213                   "stop": "name",
214                   "delegate": "name",
215                   "status": "name",
216                   "renew": "name",
217                   "shutdown": "name",
218                   "version": "",  
219                  }
220
221         if command not in cmdargs:
222             msg="Invalid command\n"
223             msg+="Commands: "
224             msg += ','.join(cmdargs.keys())            
225             self.logger.critical(msg)
226             sys.exit(2)
227
228         parser = OptionParser(usage="sfi [sfi_options] %s [options] %s" \
229                                      % (command, cmdargs[command]))
230
231         # user specifies remote aggregate/sm/component                          
232         if command in ("resources", "slices", "create", "delete", "start", "stop", 
233                        "restart", "shutdown",  "get_ticket", "renew", "status"):
234             parser.add_option("-a", "--aggregate", dest="aggregate",
235                              default=None, help="aggregate host")
236             parser.add_option("-p", "--port", dest="port",
237                              default=AGGREGATE_PORT, help="aggregate port")
238             parser.add_option("-c", "--component", dest="component", default=None,
239                              help="component hrn")
240             parser.add_option("-d", "--delegate", dest="delegate", default=None, 
241                              action="store_true",
242                              help="Include a credential delegated to the user's root"+\
243                                   "authority in set of credentials for this call")
244
245         # registy filter option
246         if command in ("list", "show", "remove"):
247             parser.add_option("-t", "--type", dest="type", type="choice",
248                             help="type filter ([all]|user|slice|authority|node|aggregate)",
249                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
250                             default="all")
251         # display formats
252         if command in ("resources"):
253             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
254                               help="schema type and version of resulting RSpec")
255             parser.add_option("-f", "--format", dest="format", type="choice",
256                              help="display format ([xml]|dns|ip)", default="xml",
257                              choices=("xml", "dns", "ip"))
258             #panos: a new option to define the type of information about resources a user is interested in
259             parser.add_option("-i", "--info", dest="info",
260                                 help="optional component information", default=None)
261
262
263         # 'create' does return the new rspec, makes sense to save that too
264         if command in ("resources", "show", "list", "create_gid", 'create'):
265            parser.add_option("-o", "--output", dest="file",
266                             help="output XML to file", metavar="FILE", default=None)
267
268         if command in ("show", "list"):
269            parser.add_option("-f", "--format", dest="format", type="choice",
270                              help="display format ([text]|xml)", default="text",
271                              choices=("text", "xml"))
272
273            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
274                              help="output file format ([xml]|xmllist|hrnlist)", default="xml",
275                              choices=("xml", "xmllist", "hrnlist"))
276
277         if command in ("status", "version"):
278            parser.add_option("-o", "--output", dest="file",
279                             help="output dictionary to file", metavar="FILE", default=None)
280            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
281                              help="output file format ([text]|pickled)", default="text",
282                              choices=("text","pickled"))
283
284         if command in ("delegate"):
285            parser.add_option("-u", "--user",
286                             action="store_true", dest="delegate_user", default=False,
287                             help="delegate user credential")
288            parser.add_option("-s", "--slice", dest="delegate_slice",
289                             help="delegate slice credential", metavar="HRN", default=None)
290         
291         if command in ("version"):
292             parser.add_option("-a", "--aggregate", dest="aggregate",
293                              default=None, help="aggregate host")
294             parser.add_option("-p", "--port", dest="port",
295                              default=AGGREGATE_PORT, help="aggregate port")
296             parser.add_option("-R","--registry-version",
297                               action="store_true", dest="version_registry", default=False,
298                               help="probe registry version instead of slicemgr")
299             parser.add_option("-l","--local",
300                               action="store_true", dest="version_local", default=False,
301                               help="display version of the local client")
302
303         return parser
304
305         
306     def create_parser(self):
307
308         # Generate command line parser
309         parser = OptionParser(usage="sfi [options] command [command_options] [command_args]",
310                              description="Commands: gid,list,show,remove,add,update,nodes,slices,resources,create,delete,start,stop,reset")
311         parser.add_option("-r", "--registry", dest="registry",
312                          help="root registry", metavar="URL", default=None)
313         parser.add_option("-s", "--slicemgr", dest="sm",
314                          help="slice manager", metavar="URL", default=None)
315         parser.add_option("-d", "--dir", dest="sfi_dir",
316                          help="config & working directory - default is " + Sfi.default_sfi_dir(),
317                          metavar="PATH", default=Sfi.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_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 = sfaprotocol.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 = sfaprotocol.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 registry before _get_gid() is called 
502             self.registry = sfaprotocol.server_proxy(self.reg_url, key_file, cert_file, 
503                                                      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             records = self.registry.Resolve(hrn, user_cred.save_to_string(save_parents=True))
547             if not records:
548                 raise RecordNotFound(args[0])
549             record = records[0]
550             if type:
551                 record=None
552                 for rec in records:
553                    if type == rec['type']:
554                         record = rec 
555             if not record:
556                 raise RecordNotFound(args[0])
557             
558             gid = GID(string=record['gid'])
559             self.logger.info("Writing gid to %s"%gidfile)
560             gid.save_to_file(filename=gidfile)
561         return gid   
562                 
563      
564     def get_cached_credential(self, file):
565         """
566         Return a cached credential only if it hasn't expired.
567         """
568         if (os.path.isfile(file)):
569             credential = Credential(filename=file)
570             # make sure it isnt expired 
571             if not credential.get_expiration or \
572                datetime.datetime.today() < credential.get_expiration():
573                 return credential
574         return None 
575  
576     def get_user_cred(self):
577         file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cred")
578         return self.get_cred(file, 'user', self.user)
579
580     def get_auth_cred(self):
581         if not self.authority:
582             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
583             sys.exit(-1)
584         file = os.path.join(self.options.sfi_dir, self.authority + ".cred")
585         return self.get_cred(file, 'authority', self.authority)
586
587     def get_slice_cred(self, name):
588         file = os.path.join(self.options.sfi_dir, "slice_" + get_leaf(name) + ".cred")
589         return self.get_cred(file, 'slice', name)
590  
591     def get_cred(self, file, type, hrn):
592         # attempt to load a cached credential 
593         cred = self.get_cached_credential(file)    
594         if not cred:
595             if type in ['user']:
596                 cert_string = self.cert.save_to_string(save_parents=True)
597                 user_name = self.user.replace(self.authority + ".", '')
598                 if user_name.count(".") > 0:
599                     user_name = user_name.replace(".", '_')
600                     self.user = self.authority + "." + user_name
601                 cred_str = self.registry.GetSelfCredential(cert_string, hrn, "user")
602             else:
603                 # bootstrap slice credential from user credential
604                 user_cred = self.get_user_cred().save_to_string(save_parents=True)
605                 cred_str = self.registry.GetCredential(user_cred, hrn, type)
606             
607             if not cred_str:
608                 self.logger.critical("Failed to get %s credential" % type)
609                 sys.exit(-1)
610                 
611             cred = Credential(string=cred_str)
612             cred.save_to_file(file, save_parents=True)
613             self.logger.info("Writing %s credential to %s" %(type, file))
614
615         return cred
616  
617     
618     def get_rspec_file(self, rspec):
619        if (os.path.isabs(rspec)):
620           file = rspec
621        else:
622           file = os.path.join(self.options.sfi_dir, rspec)
623        if (os.path.isfile(file)):
624           return file
625        else:
626           self.logger.critical("No such rspec file %s"%rspec)
627           sys.exit(1)
628     
629     def get_record_file(self, record):
630        if (os.path.isabs(record)):
631           file = record
632        else:
633           file = os.path.join(self.options.sfi_dir, record)
634        if (os.path.isfile(file)):
635           return file
636        else:
637           self.logger.critical("No such registry record file %s"%record)
638           sys.exit(1)
639     
640     # xxx opts undefined
641     def get_component_proxy_from_hrn(self, hrn):
642         # direct connection to the nodes component manager interface
643         user_cred = self.get_user_cred().save_to_string(save_parents=True)
644         records = self.registry.Resolve(hrn, user_cred)
645         records = filter_records('node', records)
646         if not records:
647             self.logger.warning("No such component:%r"% opts.component)
648         record = records[0]
649   
650         return self.server_proxy(record['hostname'], CM_PORT, self.key_file, self.cert_file)
651  
652     def server_proxy(self, host, port, keyfile, certfile):
653         """
654         Return an instance of an xmlrpc server connection    
655         """
656         # port is appended onto the domain, before the path. Should look like:
657         # http://domain:port/path
658         host_parts = host.split('/')
659         host_parts[0] = host_parts[0] + ":" + str(port)
660         url =  "http://%s" %  "/".join(host_parts)    
661         return sfaprotocol.server_proxy(url, keyfile, certfile, timeout=self.options.timeout, 
662                                         verbose=self.options.debug)
663
664     # xxx opts could be retrieved in self.options
665     def server_proxy_from_opts(self, opts):
666         """
667         Return instance of an xmlrpc connection to a slice manager, aggregate
668         or component server depending on the specified opts
669         """
670         server = self.slicemgr
671         # direct connection to an aggregate
672         if hasattr(opts, 'aggregate') and opts.aggregate:
673             server = self.server_proxy(opts.aggregate, opts.port, self.key_file, self.cert_file)
674         # direct connection to the nodes component manager interface
675         if hasattr(opts, 'component') and opts.component:
676             server = self.get_component_proxy_from_hrn(opts.component)    
677  
678         return server
679     #==========================================================================
680     # Following functions implement the commands
681     #
682     # Registry-related commands
683     #==========================================================================
684   
685     def create_gid(self, opts, args):
686         if len(args) < 1:
687             self.print_help()
688             sys.exit(1)
689         target_hrn = args[0]
690         user_cred = self.get_user_cred().save_to_string(save_parents=True)
691         gid = self.registry.CreateGid(user_cred, target_hrn, self.cert.save_to_string())
692         if opts.file:
693             filename = opts.file
694         else:
695             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
696         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
697         GID(string=gid).save_to_file(filename)
698          
699      
700     # list entires in named authority registry
701     def list(self, opts, args):
702         if len(args)!= 1:
703             self.print_help()
704             sys.exit(1)
705         hrn = args[0]
706         user_cred = self.get_user_cred().save_to_string(save_parents=True)
707         try:
708             list = self.registry.List(hrn, user_cred)
709         except IndexError:
710             raise Exception, "Not enough parameters for the 'list' command"
711
712         # filter on person, slice, site, node, etc.
713         # THis really should be in the self.filter_records funct def comment...
714         list = filter_records(opts.type, list)
715         for record in list:
716             print "%s (%s)" % (record['hrn'], record['type'])
717         if opts.file:
718             save_records_to_file(opts.file, list, opts.fileformat)
719         return
720     
721     # show named registry record
722     def show(self, opts, args):
723         if len(args)!= 1:
724             self.print_help()
725             sys.exit(1)
726         hrn = args[0]
727         user_cred = self.get_user_cred().save_to_string(save_parents=True)
728         records = self.registry.Resolve(hrn, user_cred)
729         records = filter_records(opts.type, records)
730         if not records:
731             self.logger.error("No record of type %s"% opts.type)
732         for record in records:
733             if record['type'] in ['user']:
734                 record = UserRecord(dict=record)
735             elif record['type'] in ['slice']:
736                 record = SliceRecord(dict=record)
737             elif record['type'] in ['node']:
738                 record = NodeRecord(dict=record)
739             elif record['type'].startswith('authority'):
740                 record = AuthorityRecord(dict=record)
741             else:
742                 record = SfaRecord(dict=record)
743             if (opts.format == "text"): 
744                 record.dump()  
745             else:
746                 print record.save_to_string() 
747         if opts.file:
748             save_records_to_file(opts.file, records, opts.fileformat)
749         return
750     
751     def delegate(self, opts, args):
752
753         delegee_hrn = args[0]
754         if opts.delegate_user:
755             user_cred = self.get_user_cred()
756             cred = self.delegate_cred(user_cred, delegee_hrn)
757         elif opts.delegate_slice:
758             slice_cred = self.get_slice_cred(opts.delegate_slice)
759             cred = self.delegate_cred(slice_cred, delegee_hrn)
760         else:
761             self.logger.warning("Must specify either --user or --slice <hrn>")
762             return
763         delegated_cred = Credential(string=cred)
764         object_hrn = delegated_cred.get_gid_object().get_hrn()
765         if opts.delegate_user:
766             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
767                                   + get_leaf(object_hrn) + ".cred")
768         elif opts.delegate_slice:
769             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
770                                   + get_leaf(object_hrn) + ".cred")
771
772         delegated_cred.save_to_file(dest_fn, save_parents=True)
773
774         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
775     
776     def delegate_cred(self, object_cred, hrn):
777         # the gid and hrn of the object we are delegating
778         if isinstance(object_cred, str):
779             object_cred = Credential(string=object_cred) 
780         object_gid = object_cred.get_gid_object()
781         object_hrn = object_gid.get_hrn()
782     
783         if not object_cred.get_privileges().get_all_delegate():
784             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
785             return
786
787         # the delegating user's gid
788         caller_gid = self._get_gid(self.user)
789         caller_gidfile = os.path.join(self.options.sfi_dir, self.user + ".gid")
790   
791         # the gid of the user who will be delegated to
792         delegee_gid = self._get_gid(hrn)
793         delegee_hrn = delegee_gid.get_hrn()
794         delegee_gidfile = os.path.join(self.options.sfi_dir, delegee_hrn + ".gid")
795         delegee_gid.save_to_file(filename=delegee_gidfile)
796         dcred = object_cred.delegate(delegee_gidfile, self.get_key_file(), caller_gidfile)
797         return dcred.save_to_string(save_parents=True)
798      
799     # removed named registry record
800     #   - have to first retrieve the record to be removed
801     def remove(self, opts, args):
802         auth_cred = self.get_auth_cred().save_to_string(save_parents=True)
803         if len(args)!=1:
804             self.print_help()
805             sys.exit(1)
806         hrn = args[0]
807         type = opts.type 
808         if type in ['all']:
809             type = '*'
810         return self.registry.Remove(hrn, auth_cred, type)
811     
812     # add named registry record
813     def add(self, opts, args):
814         auth_cred = self.get_auth_cred().save_to_string(save_parents=True)
815         if len(args)!=1:
816             self.print_help()
817             sys.exit(1)
818         record_filepath = args[0]
819         rec_file = self.get_record_file(record_filepath)
820         record = load_record_from_file(rec_file).as_dict()
821         return self.registry.Register(record, auth_cred)
822     
823     # update named registry entry
824     def update(self, opts, args):
825         user_cred = self.get_user_cred()
826         if len(args)!=1:
827             self.print_help()
828             sys.exit(1)
829         rec_file = self.get_record_file(args[0])
830         record = load_record_from_file(rec_file)
831         if record['type'] == "user":
832             if record.get_name() == user_cred.get_gid_object().get_hrn():
833                 cred = user_cred.save_to_string(save_parents=True)
834             else:
835                 cred = self.get_auth_cred().save_to_string(save_parents=True)
836         elif record['type'] in ["slice"]:
837             try:
838                 cred = self.get_slice_cred(record.get_name()).save_to_string(save_parents=True)
839             except sfaprotocol.ServerException, e:
840                # XXX smbaker -- once we have better error return codes, update this
841                # to do something better than a string compare
842                if "Permission error" in e.args[0]:
843                    cred = self.get_auth_cred().save_to_string(save_parents=True)
844                else:
845                    raise
846         elif record.get_type() in ["authority"]:
847             cred = self.get_auth_cred().save_to_string(save_parents=True)
848         elif record.get_type() == 'node':
849             cred = self.get_auth_cred().save_to_string(save_parents=True)
850         else:
851             raise "unknown record type" + record.get_type()
852         record = record.as_dict()
853         return self.registry.Update(record, cred)
854   
855     def get_trusted_certs(self, opts, args):
856         """
857         return uhe trusted certs at this interface 
858         """ 
859         trusted_certs = self.registry.get_trusted_certs()
860         for trusted_cert in trusted_certs:
861             gid = GID(string=trusted_cert)
862             gid.dump()
863             cert = Certificate(string=trusted_cert)
864             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
865         return 
866
867     # ==================================================================
868     # Slice-related commands
869     # ==================================================================
870
871     def version(self, opts, args):
872         if opts.version_local:
873             version=version_core()
874         else:
875             if opts.version_registry:
876                 server=self.registry
877             else:
878                 server = self.server_proxy_from_opts(opts)
879             result = server.GetVersion()
880             version = ReturnValue.get_value(result)
881         for (k,v) in version.iteritems():
882             print "%-20s: %s"%(k,v)
883         if opts.file:
884             save_variable_to_file(version, opts.file, opts.fileformat)
885
886     # list instantiated slices
887     def slices(self, opts, args):
888         """
889         list instantiated slices
890         """
891         user_cred = self.get_user_cred().save_to_string(save_parents=True)
892         creds = [user_cred]
893         if opts.delegate:
894             delegated_cred = self.delegate_cred(user_cred, get_authority(self.authority))
895             creds.append(delegated_cred)  
896         server = self.server_proxy_from_opts(opts)
897         call_args = [creds]
898         if self.server_supports_options_arg(server):
899             options = {'call_id': unique_call_id()}
900             call_args.append(options)
901         result = server.ListSlices(*call_args)
902         value = ReturnValue.get_value(result)
903         display_list(value)
904         return
905     
906     # show rspec for named slice
907     def resources(self, opts, args):
908         user_cred = self.get_user_cred().save_to_string(save_parents=True)
909         server = self.server_proxy_from_opts(opts)
910    
911         options = {'call_id': unique_call_id()}
912         #panos add info options
913         if opts.info:
914             options['info'] = opts.info
915         
916         if args:
917             cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
918             hrn = args[0]
919             options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
920         else:
921             cred = user_cred
922      
923         creds = [cred]
924         if opts.delegate:
925             delegated_cred = self.delegate_cred(cred, get_authority(self.authority))
926             creds.append(delegated_cred)
927         if opts.rspec_version:
928             version_manager = VersionManager()
929             server_version = self.get_cached_server_version(server)
930             if 'sfa' in server_version:
931                 # just request the version the client wants 
932                 options['geni_rspec_version'] = version_manager.get_version(opts.rspec_version).to_dict()
933             else:
934                 # this must be a protogeni aggregate. We should request a v2 ad rspec
935                 # regardless of what the client user requested 
936                 options['geni_rspec_version'] = version_manager.get_version('ProtoGENI 2').to_dict()     
937         else:
938             options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
939  
940         call_args = [creds, options]
941         result = server.ListResources(*call_args)
942         value = ReturnValue.get_value(result)
943         if opts.file is None:
944             display_rspec(value, opts.format)
945         else:
946             save_rspec_to_file(value, opts.file)
947         return
948
949     # created named slice with given rspec
950     def create(self, opts, args):
951         server = self.server_proxy_from_opts(opts)
952         server_version = self.get_cached_server_version(server)
953         slice_hrn = args[0]
954         slice_urn = hrn_to_urn(slice_hrn, 'slice')
955         user_cred = self.get_user_cred()
956         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
957         delegated_cred = None
958         if server_version.get('interface') == 'slicemgr':
959             # delegate our cred to the slice manager
960             # do not delegate cred to slicemgr...not working at the moment
961             pass
962             #if server_version.get('hrn'):
963             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
964             #elif server_version.get('urn'):
965             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
966                  
967         rspec_file = self.get_rspec_file(args[1])
968         rspec = open(rspec_file).read()
969
970         # need to pass along user keys to the aggregate.
971         # users = [
972         #  { urn: urn:publicid:IDN+emulab.net+user+alice
973         #    keys: [<ssh key A>, <ssh key B>]
974         #  }]
975         users = []
976         slice_records = self.registry.Resolve(slice_urn, [user_cred.save_to_string(save_parents=True)])
977         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
978             slice_record = slice_records[0]
979             user_hrns = slice_record['researcher']
980             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
981             user_records = self.registry.Resolve(user_urns, [user_cred.save_to_string(save_parents=True)])
982
983             if 'sfa' not in server_version:
984                 users = pg_users_arg(user_records)
985                 rspec = RSpec(rspec)
986                 rspec.filter({'component_manager_id': server_version['urn']})
987                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
988                 creds = [slice_cred]
989             else:
990                 users = sfa_users_arg(user_records, slice_record)
991                 creds = [slice_cred]
992                 if delegated_cred:
993                     creds.append(delegated_cred)
994         call_args = [slice_urn, creds, rspec, users]
995         if self.server_supports_options_arg(server):
996             options = {'call_id': unique_call_id()}
997             call_args.append(options)
998         result = server.CreateSliver(*call_args)
999         value = ReturnValue.get_value(result)
1000         if opts.file is None:
1001             print value
1002         else:
1003             save_rspec_to_file (value, opts.file)
1004         return value
1005
1006     # get a ticket for the specified slice
1007     def get_ticket(self, opts, args):
1008         slice_hrn, rspec_path = args[0], args[1]
1009         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1010         user_cred = self.get_user_cred()
1011         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1012         creds = [slice_cred]
1013         if opts.delegate:
1014             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1015             creds.append(delegated_cred)
1016         rspec_file = self.get_rspec_file(rspec_path) 
1017         rspec = open(rspec_file).read()
1018         server = self.server_proxy_from_opts(opts)
1019         ticket_string = server.GetTicket(slice_urn, creds, rspec, [])
1020         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1021         self.logger.info("writing ticket to %s"%file)
1022         ticket = SfaTicket(string=ticket_string)
1023         ticket.save_to_file(filename=file, save_parents=True)
1024
1025     def redeem_ticket(self, opts, args):
1026         ticket_file = args[0]
1027         
1028         # get slice hrn from the ticket
1029         # use this to get the right slice credential 
1030         ticket = SfaTicket(filename=ticket_file)
1031         ticket.decode()
1032         slice_hrn = ticket.gidObject.get_hrn()
1033         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1034         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1035         user_cred = self.get_user_cred()
1036         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1037         
1038         # get a list of node hostnames from the RSpec 
1039         tree = etree.parse(StringIO(ticket.rspec))
1040         root = tree.getroot()
1041         hostnames = root.xpath("./network/site/node/hostname/text()")
1042         
1043         # create an xmlrpc connection to the component manager at each of these
1044         # components and gall redeem_ticket
1045         connections = {}
1046         for hostname in hostnames:
1047             try:
1048                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1049                 server = self.server_proxy(hostname, CM_PORT, self.key_file, \
1050                                          self.cert_file, self.options.debug)
1051                 server.RedeemTicket(ticket.save_to_string(save_parents=True), slice_cred)
1052                 self.logger.info("Success")
1053             except socket.gaierror:
1054                 self.logger.error("redeem_ticket failed: Component Manager not accepting requests")
1055             except Exception, e:
1056                 self.logger.log_exc(e.message)
1057         return
1058  
1059     # delete named slice
1060     def delete(self, opts, args):
1061         slice_hrn = args[0]
1062         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1063         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1064         creds = [slice_cred]
1065         if opts.delegate:
1066             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1067             creds.append(delegated_cred)
1068         server = self.server_proxy_from_opts(opts)
1069         call_args = [slice_urn, creds]
1070         if self.server_supports_options_arg(server):
1071             options = {'call_id': unique_call_id()}
1072             call_args.append(options)
1073         return server.DeleteSliver(*call_args) 
1074   
1075     # start named slice
1076     def start(self, opts, args):
1077         slice_hrn = args[0]
1078         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1079         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1080         creds = [slice_cred]
1081         if opts.delegate:
1082             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1083             creds.append(delegated_cred)
1084         server = self.server_proxy_from_opts(opts)
1085         return server.Start(slice_urn, creds)
1086     
1087     # stop named slice
1088     def stop(self, opts, args):
1089         slice_hrn = args[0]
1090         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1091         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1092         creds = [slice_cred]
1093         if opts.delegate:
1094             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1095             creds.append(delegated_cred)
1096         server = self.server_proxy_from_opts(opts)
1097         return server.Stop(slice_urn, creds)
1098     
1099     # reset named slice
1100     def reset(self, opts, args):
1101         slice_hrn = args[0]
1102         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1103         server = self.server_proxy_from_opts(opts)
1104         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1105         creds = [slice_cred]
1106         if opts.delegate:
1107             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1108             creds.append(delegated_cred)
1109         return server.reset_slice(creds, slice_urn)
1110
1111     def renew(self, opts, args):
1112         slice_hrn = args[0]
1113         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1114         server = self.server_proxy_from_opts(opts)
1115         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1116         creds = [slice_cred]
1117         if opts.delegate:
1118             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1119             creds.append(delegated_cred)
1120         time = args[1]
1121         
1122         call_args = [slice_urn, creds, time]
1123         if self.server_supports_options_arg(server):
1124             options = {'call_id': unique_call_id()}
1125             call_args.append(options)
1126         result =  server.RenewSliver(*call_args)
1127         value = ReturnValue.get_value(result)
1128         return value
1129
1130
1131     def status(self, opts, args):
1132         slice_hrn = args[0]
1133         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1134         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1135         creds = [slice_cred]
1136         if opts.delegate:
1137             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1138             creds.append(delegated_cred)
1139         server = self.server_proxy_from_opts(opts)
1140         call_args = [slice_urn, creds]
1141         if self.server_supports_options_arg(server):
1142             options = {'call_id': unique_call_id()}
1143             call_args.append(options)
1144         result = server.SliverStatus(*call_args)
1145         value = ReturnValue.get_value(result)
1146         print value
1147         if opts.file:
1148             save_variable_to_file(value, opts.file, opts.fileformat)
1149
1150
1151     def shutdown(self, opts, args):
1152         slice_hrn = args[0]
1153         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1154         slice_cred = self.get_slice_cred(slice_hrn).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         server = self.server_proxy_from_opts(opts)
1160         return server.Shutdown(slice_urn, creds)         
1161     
1162     def print_help (self):
1163         self.sfi_parser.print_help()
1164         self.cmd_parser.print_help()
1165
1166     #
1167     # Main: parse arguments and dispatch to command
1168     #
1169     def dispatch(self, command, cmd_opts, cmd_args):
1170         return getattr(self, command)(cmd_opts, cmd_args)
1171
1172     def main(self):
1173         self.sfi_parser = self.create_parser()
1174         (options, args) = self.sfi_parser.parse_args()
1175         self.options = options
1176
1177         self.logger.setLevelFromOptVerbose(self.options.verbose)
1178         if options.hashrequest:
1179             self.hashrequest = True
1180  
1181         if len(args) <= 0:
1182             self.logger.critical("No command given. Use -h for help.")
1183             return -1
1184     
1185         command = args[0]
1186         self.cmd_parser = self.create_cmd_parser(command)
1187         (cmd_opts, cmd_args) = self.cmd_parser.parse_args(args[1:])
1188
1189         self.set_servers()
1190         self.logger.info("Command=%s" % command)
1191         if command in ("resources"):
1192             self.logger.debug("resources cmd_opts %s" % cmd_opts.format)
1193         elif command in ("list", "show", "remove"):
1194             self.logger.debug("cmd_opts.type %s" % cmd_opts.type)
1195         self.logger.debug('cmd_args %s' % cmd_args)
1196
1197         try:
1198             self.dispatch(command, cmd_opts, cmd_args)
1199         except KeyError:
1200             self.logger.critical ("Unknown command %s"%command)
1201             raise
1202             sys.exit(1)
1203     
1204         return
1205     
1206 if __name__ == "__main__":
1207     Sfi().main()