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