add warning
[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     # dummy to meet Sfi's expectations for its 'options' field
171     # i.e. s/t we can do setattr on
172     class DummyOptions:
173         pass
174
175     def __init__ (self,options=None):
176         if options is None: options=Sfi.DummyOptions()
177         for opt in Sfi.required_options:
178             if not hasattr(options,opt): setattr(options,opt,None)
179         if not hasattr(options,'sfi_dir'): options.sfi_dir=os.path.expanduser("~/.sfi/")
180         # xxx oops, this is dangerous, sounds like ww sometimes have discrepency
181         # would be safer to remove self.sfi_dir altogether
182         self.sfi_dir = options.sfi_dir
183         self.options = options
184         self.slicemgr = None
185         self.registry = None
186         self.user = None
187         self.authority = None
188         self.hashrequest = False
189         self.logger = sfi_logger
190         self.logger.enable_console()
191    
192     def create_cmd_parser(self, command, additional_cmdargs=None):
193         cmdargs = {"list": "authority",
194                   "show": "name",
195                   "remove": "name",
196                   "add": "record",
197                   "update": "record",
198                   "aggregates": "[name]",
199                   "registries": "[name]",
200                   "create_gid": "[name]",
201                   "get_gid": [],  
202                   "get_trusted_certs": "cred",
203                   "slices": "",
204                   "resources": "[name]",
205                   "create": "name rspec",
206                   "get_ticket": "name rspec",
207                   "redeem_ticket": "ticket",
208                   "delete": "name",
209                   "reset": "name",
210                   "start": "name",
211                   "stop": "name",
212                   "delegate": "name",
213                   "status": "name",
214                   "renew": "name",
215                   "shutdown": "name",
216                   "version": "",  
217                  }
218
219         if additional_cmdargs:
220             cmdargs.update(additional_cmdargs)
221
222         if command not in cmdargs:
223             msg="Invalid command\n"
224             msg+="Commands: "
225             msg += ','.join(cmdargs.keys())            
226             self.logger.critical(msg)
227             sys.exit(2)
228
229         parser = OptionParser(usage="sfi [sfi_options] %s [options] %s" \
230                                      % (command, cmdargs[command]))
231
232         # user specifies remote aggregate/sm/component                          
233         if command in ("resources", "slices", "create", "delete", "start", "stop", 
234                        "restart", "shutdown",  "get_ticket", "renew", "status"):
235             parser.add_option("-a", "--aggregate", dest="aggregate",
236                              default=None, help="aggregate host")
237             parser.add_option("-p", "--port", dest="port",
238                              default=AGGREGATE_PORT, help="aggregate port")
239             parser.add_option("-c", "--component", dest="component", default=None,
240                              help="component hrn")
241             parser.add_option("-d", "--delegate", dest="delegate", default=None, 
242                              action="store_true",
243                              help="Include a credential delegated to the user's root"+\
244                                   "authority in set of credentials for this call")
245
246         # registy filter option
247         if command in ("list", "show", "remove"):
248             parser.add_option("-t", "--type", dest="type", type="choice",
249                             help="type filter ([all]|user|slice|authority|node|aggregate)",
250                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
251                             default="all")
252         # display formats
253         if command in ("resources"):
254             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
255                               help="schema type and version of resulting RSpec")
256             parser.add_option("-f", "--format", dest="format", type="choice",
257                              help="display format ([xml]|dns|ip)", default="xml",
258                              choices=("xml", "dns", "ip"))
259             #panos: a new option to define the type of information about resources a user is interested in
260             parser.add_option("-i", "--info", dest="info",
261                                 help="optional component information", default=None)
262
263
264         # 'create' does return the new rspec, makes sense to save that too
265         if command in ("resources", "show", "list", "create_gid", 'create'):
266            parser.add_option("-o", "--output", dest="file",
267                             help="output XML to file", metavar="FILE", default=None)
268
269         if command in ("show", "list"):
270            parser.add_option("-f", "--format", dest="format", type="choice",
271                              help="display format ([text]|xml)", default="text",
272                              choices=("text", "xml"))
273
274            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
275                              help="output file format ([xml]|xmllist|hrnlist)", default="xml",
276                              choices=("xml", "xmllist", "hrnlist"))
277
278         if command in ("status", "version"):
279            parser.add_option("-o", "--output", dest="file",
280                             help="output dictionary to file", metavar="FILE", default=None)
281            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
282                              help="output file format ([text]|pickled)", default="text",
283                              choices=("text","pickled"))
284
285         if command in ("delegate"):
286            parser.add_option("-u", "--user",
287                             action="store_true", dest="delegate_user", default=False,
288                             help="delegate user credential")
289            parser.add_option("-s", "--slice", dest="delegate_slice",
290                             help="delegate slice credential", metavar="HRN", default=None)
291         
292         if command in ("version"):
293             parser.add_option("-a", "--aggregate", dest="aggregate",
294                              default=None, help="aggregate host")
295             parser.add_option("-p", "--port", dest="port",
296                              default=AGGREGATE_PORT, help="aggregate port")
297             parser.add_option("-R","--registry-version",
298                               action="store_true", dest="version_registry", default=False,
299                               help="probe registry version instead of slicemgr")
300             parser.add_option("-l","--local",
301                               action="store_true", dest="version_local", default=False,
302                               help="display version of the local client")
303
304         return parser
305
306         
307     def create_parser(self):
308
309         # Generate command line parser
310         parser = OptionParser(usage="sfi [options] command [command_options] [command_args]",
311                              description="Commands: gid,list,show,remove,add,update,nodes,slices,resources,create,delete,start,stop,reset")
312         parser.add_option("-r", "--registry", dest="registry",
313                          help="root registry", metavar="URL", default=None)
314         parser.add_option("-s", "--slicemgr", dest="sm",
315                          help="slice manager", metavar="URL", default=None)
316         default_sfi_dir = os.path.expanduser("~/.sfi/")
317         parser.add_option("-d", "--dir", dest="sfi_dir",
318                          help="config & working directory - default is " + default_sfi_dir,
319                          metavar="PATH", default=default_sfi_dir)
320         parser.add_option("-u", "--user", dest="user",
321                          help="user name", metavar="HRN", default=None)
322         parser.add_option("-a", "--auth", dest="auth",
323                          help="authority name", metavar="HRN", default=None)
324         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
325                          help="verbose mode - cumulative")
326         parser.add_option("-D", "--debug",
327                           action="store_true", dest="debug", default=False,
328                           help="Debug (xml-rpc) protocol messages")
329         parser.add_option("-p", "--protocol", dest="protocol", default="xmlrpc",
330                          help="RPC protocol (xmlrpc or soap)")
331         parser.add_option("-k", "--hashrequest",
332                          action="store_true", dest="hashrequest", default=False,
333                          help="Create a hash of the request that will be authenticated on the server")
334         parser.add_option("-t", "--timeout", dest="timeout", default=None,
335                          help="Amout of time tom wait before timing out the request")
336         parser.disable_interspersed_args()
337
338         return parser
339         
340
341     def read_config(self):
342        config_file = os.path.join(self.options.sfi_dir,"sfi_config")
343        try:
344           config = Config (config_file)
345        except:
346           self.logger.critical("Failed to read configuration file %s"%config_file)
347           self.logger.info("Make sure to remove the export clauses and to add quotes")
348           if self.options.verbose==0:
349               self.logger.info("Re-run with -v for more details")
350           else:
351               self.logger.log_exc("Could not read config file %s"%config_file)
352           sys.exit(1)
353     
354        errors = 0
355        # Set SliceMgr URL
356        if (self.options.sm is not None):
357           self.sm_url = self.options.sm
358        elif hasattr(config, "SFI_SM"):
359           self.sm_url = config.SFI_SM
360        else:
361           self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
362           errors += 1 
363     
364        # Set Registry URL
365        if (self.options.registry is not None):
366           self.reg_url = self.options.registry
367        elif hasattr(config, "SFI_REGISTRY"):
368           self.reg_url = config.SFI_REGISTRY
369        else:
370           self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
371           errors += 1 
372           
373
374        # Set user HRN
375        if (self.options.user is not None):
376           self.user = self.options.user
377        elif hasattr(config, "SFI_USER"):
378           self.user = config.SFI_USER
379        else:
380           self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
381           errors += 1 
382     
383        # Set authority HRN
384        if (self.options.auth is not None):
385           self.authority = self.options.auth
386        elif hasattr(config, "SFI_AUTH"):
387           self.authority = config.SFI_AUTH
388        else:
389           self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
390           errors += 1 
391     
392        if errors:
393           sys.exit(1)
394
395
396     #
397     # Establish Connection to SliceMgr and Registry Servers
398     #
399     def set_servers(self):
400
401        self.read_config() 
402        # Get key and certificate
403        key_file = self.get_key_file()
404        cert_file = self.get_cert_file(key_file)
405        self.key_file = key_file
406        self.cert_file = cert_file
407        self.cert = GID(filename=cert_file)
408        self.logger.info("Contacting Registry at: %s"%self.reg_url)
409        self.registry = sfaprotocol.server_proxy(self.reg_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)  
410        self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
411        self.slicemgr = sfaprotocol.server_proxy(self.sm_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)
412        return
413
414     def get_cached_server_version(self, server):
415         # check local cache first
416         cache = None
417         version = None 
418         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
419         cache_key = server.url + "-version"
420         try:
421             cache = Cache(cache_file)
422         except IOError:
423             cache = Cache()
424             self.logger.info("Local cache not found at: %s" % cache_file)
425
426         if cache:
427             version = cache.get(cache_key)
428
429         if not version: 
430             result = server.GetVersion()
431             version= ReturnValue.get_value(result)
432             # cache version for 24 hours
433             cache.add(cache_key, version, ttl= 60*60*24)
434             self.logger.info("Updating cache file %s" % cache_file)
435             cache.save_to_file(cache_file)
436
437         return version   
438         
439
440     def server_supports_options_arg(self, server):
441         """
442         Returns true if server support the optional call_id arg, false otherwise. 
443         """
444         server_version = self.get_cached_server_version(server)
445         if 'sfa' in server_version and 'code_tag' in server_version:
446             code_tag = server_version['code_tag']
447             code_tag_parts = code_tag.split("-")
448             
449             version_parts = code_tag_parts[0].split(".")
450             major, minor = version_parts[0], version_parts[1]
451             rev = code_tag_parts[1]
452             if int(major) >= 1:
453                 if int(minor) >= 2:
454                     return True
455         return False                
456         
457     #
458     # Get various credential and spec files
459     #
460     # Establishes limiting conventions
461     #   - conflates MAs and SAs
462     #   - assumes last token in slice name is unique
463     #
464     # Bootstraps credentials
465     #   - bootstrap user credential from self-signed certificate
466     #   - bootstrap authority credential from user credential
467     #   - bootstrap slice credential from user credential
468     #
469     
470     
471     def get_key_file(self):
472        file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".pkey")
473        if (os.path.isfile(file)):
474           return file
475        else:
476           self.logger.error("Key file %s does not exist"%file)
477           sys.exit(-1)
478        return
479     
480     def get_cert_file(self, key_file):
481     
482         cert_file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cert")
483         if (os.path.isfile(cert_file)):
484             # we'd perfer to use Registry issued certs instead of self signed certs. 
485             # if this is a Registry cert (GID) then we are done 
486             gid = GID(filename=cert_file)
487             if gid.get_urn():
488                 return cert_file
489
490         # generate self signed certificate
491         k = Keypair(filename=key_file)
492         cert = Certificate(subject=self.user)
493         cert.set_pubkey(k)
494         cert.set_issuer(k, self.user)
495         cert.sign()
496         self.logger.info("Writing self-signed certificate to %s"%cert_file)
497         cert.save_to_file(cert_file)
498         self.cert = cert
499         # try to get registry issued cert
500         try:
501             self.logger.info("Getting Registry issued cert")
502             self.read_config()
503             # *hack.  need to set registyr before _get_gid() is called 
504             self.registry = sfaprotocol.server_proxy(self.reg_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)
505             gid = self._get_gid(type='user')
506             self.registry = None 
507             self.logger.info("Writing certificate to %s"%cert_file)
508             gid.save_to_file(cert_file)
509         except:
510             self.logger.info("Failed to download Registry issued cert")
511
512         return cert_file
513
514     def get_cached_gid(self, file):
515         """
516         Return a cached gid    
517         """
518         gid = None 
519         if (os.path.isfile(file)):
520             gid = GID(filename=file)
521         return gid
522
523     # xxx opts unused
524     def get_gid(self, opts, args):
525         """
526         Get the specify gid and save it to file
527         """
528         hrn = None
529         if args:
530             hrn = args[0]
531         gid = self._get_gid(hrn)
532         self.logger.debug("Sfi.get_gid-> %s" % gid.save_to_string(save_parents=True))
533         return gid
534
535     def _get_gid(self, hrn=None, type=None):
536         """
537         git_gid helper. Retrive the gid from the registry and save it to file.
538         """
539         
540         if not hrn:
541             hrn = self.user
542  
543         gidfile = os.path.join(self.options.sfi_dir, hrn + ".gid")
544         gid = self.get_cached_gid(gidfile)
545         if not gid:
546             user_cred = self.get_user_cred()
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 sfaprotocol.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 sfaprotocol.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.server_proxy_from_opts(opts)
954    
955         options = {'call_id': unique_call_id()}
956         #panos add info options
957         if opts.info:
958             options['info'] = opts.info
959         
960         if args:
961             cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
962             hrn = args[0]
963             options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
964         else:
965             cred = user_cred
966      
967         creds = [cred]
968         if opts.delegate:
969             delegated_cred = self.delegate_cred(cred, get_authority(self.authority))
970             creds.append(delegated_cred)
971         if opts.rspec_version:
972             version_manager = VersionManager()
973             server_version = self.get_cached_server_version(server)
974             if 'sfa' in server_version:
975                 # just request the version the client wants 
976                 options['geni_rspec_version'] = version_manager.get_version(opts.rspec_version).to_dict()
977             else:
978                 # this must be a protogeni aggregate. We should request a v2 ad rspec
979                 # regardless of what the client user requested 
980                 options['geni_rspec_version'] = version_manager.get_version('ProtoGENI 2').to_dict()     
981         else:
982             options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
983  
984         call_args = [creds, options]
985         result = server.ListResources(*call_args)
986         value = ReturnValue.get_value(result)
987         if opts.file is None:
988             display_rspec(value, opts.format)
989         else:
990             save_rspec_to_file(value, opts.file)
991         return
992
993     # created named slice with given rspec
994     def create(self, opts, args):
995         server = self.server_proxy_from_opts(opts)
996         server_version = self.get_cached_server_version(server)
997         slice_hrn = args[0]
998         slice_urn = hrn_to_urn(slice_hrn, 'slice')
999         user_cred = self.get_user_cred()
1000         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1001
1002         if hasattr(opts, 'aggregate') and opts.aggregate:
1003             delegated_cred = None
1004         else:
1005             # delegate the cred to the callers root authority
1006             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority)+'.slicemanager')
1007             #delegated_cred = self.delegate_cred(slice_cred, get_authority(slice_hrn))
1008             #creds.append(delegated_cred)
1009
1010         rspec_file = self.get_rspec_file(args[1])
1011         rspec = open(rspec_file).read()
1012
1013         # need to pass along user keys to the aggregate.
1014         # users = [
1015         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1016         #    keys: [<ssh key A>, <ssh key B>]
1017         #  }]
1018         users = []
1019         slice_records = self.registry.Resolve(slice_urn, [user_cred.save_to_string(save_parents=True)])
1020         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1021             slice_record = slice_records[0]
1022             user_hrns = slice_record['researcher']
1023             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1024             user_records = self.registry.Resolve(user_urns, [user_cred.save_to_string(save_parents=True)])
1025
1026             if 'sfa' not in server_version:
1027                 users = pg_users_arg(user_records)
1028                 rspec = RSpec(rspec)
1029                 rspec.filter({'component_manager_id': server_version['urn']})
1030                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1031                 creds = [slice_cred]
1032             else:
1033                 users = sfa_users_arg(user_records, slice_record)
1034                 creds = [slice_cred]
1035                 if delegated_cred:
1036                     creds.append(delegated_cred)
1037         call_args = [slice_urn, creds, rspec, users]
1038         if self.server_supports_options_arg(server):
1039             options = {'call_id': unique_call_id()}
1040             call_args.append(options)
1041         result = server.CreateSliver(*call_args)
1042         value = ReturnValue.get_value(result)
1043         if opts.file is None:
1044             print value
1045         else:
1046             save_rspec_to_file (value, opts.file)
1047         return value
1048
1049     # get a ticket for the specified slice
1050     def get_ticket(self, opts, args):
1051         slice_hrn, rspec_path = args[0], args[1]
1052         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1053         user_cred = self.get_user_cred()
1054         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1055         creds = [slice_cred]
1056         if opts.delegate:
1057             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1058             creds.append(delegated_cred)
1059         rspec_file = self.get_rspec_file(rspec_path) 
1060         rspec = open(rspec_file).read()
1061         server = self.server_proxy_from_opts(opts)
1062         ticket_string = server.GetTicket(slice_urn, creds, rspec, [])
1063         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1064         self.logger.info("writing ticket to %s"%file)
1065         ticket = SfaTicket(string=ticket_string)
1066         ticket.save_to_file(filename=file, save_parents=True)
1067
1068     def redeem_ticket(self, opts, args):
1069         ticket_file = args[0]
1070         
1071         # get slice hrn from the ticket
1072         # use this to get the right slice credential 
1073         ticket = SfaTicket(filename=ticket_file)
1074         ticket.decode()
1075         slice_hrn = ticket.gidObject.get_hrn()
1076         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1077         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1078         user_cred = self.get_user_cred()
1079         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1080         
1081         # get a list of node hostnames from the RSpec 
1082         tree = etree.parse(StringIO(ticket.rspec))
1083         root = tree.getroot()
1084         hostnames = root.xpath("./network/site/node/hostname/text()")
1085         
1086         # create an xmlrpc connection to the component manager at each of these
1087         # components and gall redeem_ticket
1088         connections = {}
1089         for hostname in hostnames:
1090             try:
1091                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1092                 server = self.server_proxy(hostname, CM_PORT, self.key_file, \
1093                                          self.cert_file, self.options.debug)
1094                 server.RedeemTicket(ticket.save_to_string(save_parents=True), slice_cred)
1095                 self.logger.info("Success")
1096             except socket.gaierror:
1097                 self.logger.error("redeem_ticket failed: Component Manager not accepting requests")
1098             except Exception, e:
1099                 self.logger.log_exc(e.message)
1100         return
1101  
1102     # delete named slice
1103     def delete(self, opts, args):
1104         slice_hrn = args[0]
1105         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1106         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1107         creds = [slice_cred]
1108         if opts.delegate:
1109             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1110             creds.append(delegated_cred)
1111         server = self.server_proxy_from_opts(opts)
1112         call_args = [slice_urn, creds]
1113         if self.server_supports_options_arg(server):
1114             options = {'call_id': unique_call_id()}
1115             call_args.append(options)
1116         return server.DeleteSliver(*call_args) 
1117   
1118     # start named slice
1119     def start(self, opts, args):
1120         slice_hrn = args[0]
1121         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1122         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1123         creds = [slice_cred]
1124         if opts.delegate:
1125             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1126             creds.append(delegated_cred)
1127         server = self.server_proxy_from_opts(opts)
1128         return server.Start(slice_urn, creds)
1129     
1130     # stop named slice
1131     def stop(self, opts, args):
1132         slice_hrn = args[0]
1133         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1134         slice_cred = self.get_slice_cred(args[0]).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         return server.Stop(slice_urn, creds)
1141     
1142     # reset named slice
1143     def reset(self, opts, args):
1144         slice_hrn = args[0]
1145         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1146         server = self.server_proxy_from_opts(opts)
1147         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1148         creds = [slice_cred]
1149         if opts.delegate:
1150             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1151             creds.append(delegated_cred)
1152         return server.reset_slice(creds, slice_urn)
1153
1154     def renew(self, opts, args):
1155         slice_hrn = args[0]
1156         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1157         server = self.server_proxy_from_opts(opts)
1158         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1159         creds = [slice_cred]
1160         if opts.delegate:
1161             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1162             creds.append(delegated_cred)
1163         time = args[1]
1164         
1165         call_args = [slice_urn, creds, time]
1166         if self.server_supports_options_arg(server):
1167             options = {'call_id': unique_call_id()}
1168             call_args.append(options)
1169         result =  server.RenewSliver(*call_args)
1170         value = ReturnValue.get_value(result)
1171         return value
1172
1173
1174     def status(self, opts, args):
1175         slice_hrn = args[0]
1176         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1177         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1178         creds = [slice_cred]
1179         if opts.delegate:
1180             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1181             creds.append(delegated_cred)
1182         server = self.server_proxy_from_opts(opts)
1183         call_args = [slice_urn, creds]
1184         if self.server_supports_options_arg(server):
1185             options = {'call_id': unique_call_id()}
1186             call_args.append(options)
1187         result = server.SliverStatus(*call_args)
1188         value = ReturnValue.get_value(result)
1189         print value
1190         if opts.file:
1191             save_variable_to_file(value, opts.file, opts.fileformat)
1192
1193
1194     def shutdown(self, opts, args):
1195         slice_hrn = args[0]
1196         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1197         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1198         creds = [slice_cred]
1199         if opts.delegate:
1200             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1201             creds.append(delegated_cred)
1202         server = self.server_proxy_from_opts(opts)
1203         return server.Shutdown(slice_urn, creds)         
1204     
1205     def print_help (self):
1206         self.sfi_parser.print_help()
1207         self.cmd_parser.print_help()
1208
1209     #
1210     # Main: parse arguments and dispatch to command
1211     #
1212     def main(self):
1213         self.sfi_parser = self.create_parser()
1214         (options, args) = self.sfi_parser.parse_args()
1215         self.options = options
1216
1217         self.logger.setLevelFromOptVerbose(self.options.verbose)
1218         if options.hashrequest:
1219             self.hashrequest = True
1220  
1221         if len(args) <= 0:
1222             self.logger.critical("No command given. Use -h for help.")
1223             return -1
1224     
1225         command = args[0]
1226         self.cmd_parser = self.create_cmd_parser(command)
1227         (cmd_opts, cmd_args) = self.cmd_parser.parse_args(args[1:])
1228
1229         self.set_servers()
1230         self.logger.info("Command=%s" % command)
1231         if command in ("resources"):
1232             self.logger.debug("resources cmd_opts %s" % cmd_opts.format)
1233         elif command in ("list", "show", "remove"):
1234             self.logger.debug("cmd_opts.type %s" % cmd_opts.type)
1235         self.logger.debug('cmd_args %s' % cmd_args)
1236
1237         try:
1238             self.dispatch(command, cmd_opts, cmd_args)
1239         except KeyError:
1240             self.logger.critical ("Unknown command %s"%command)
1241             raise
1242             sys.exit(1)
1243     
1244         return
1245     
1246 if __name__ == "__main__":
1247     Sfi().main()