cosmetic
[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.sfaprotocol as sfaprotocol
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_file = key_file
404        self.cert_file = cert_file
405        self.cert = GID(filename=cert_file)
406        self.logger.info("Contacting Registry at: %s"%self.reg_url)
407        self.registry = sfaprotocol.server_proxy(self.reg_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)  
408        self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
409        self.slicemgr = sfaprotocol.server_proxy(self.sm_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)
410        return
411
412     def get_cached_server_version(self, server):
413         # check local cache first
414         cache = None
415         version = None 
416         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
417         cache_key = server.url + "-version"
418         try:
419             cache = Cache(cache_file)
420         except IOError:
421             cache = Cache()
422             self.logger.info("Local cache not found at: %s" % cache_file)
423
424         if cache:
425             version = cache.get(cache_key)
426
427         if not version: 
428             result = server.GetVersion()
429             version= ReturnValue.get_value(result)
430             # cache version for 24 hours
431             cache.add(cache_key, version, ttl= 60*60*24)
432             self.logger.info("Updating cache file %s" % cache_file)
433             cache.save_to_file(cache_file)
434
435         return version   
436         
437
438     def server_supports_options_arg(self, server):
439         """
440         Returns true if server support the optional call_id arg, false otherwise. 
441         """
442         server_version = self.get_cached_server_version(server)
443         if 'sfa' in server_version and 'code_tag' in server_version:
444             code_tag = server_version['code_tag']
445             code_tag_parts = code_tag.split("-")
446             
447             version_parts = code_tag_parts[0].split(".")
448             major, minor = version_parts[0], version_parts[1]
449             rev = code_tag_parts[1]
450             if int(major) >= 1:
451                 if int(minor) >= 2:
452                     return True
453         return False                
454         
455     #
456     # Get various credential and spec files
457     #
458     # Establishes limiting conventions
459     #   - conflates MAs and SAs
460     #   - assumes last token in slice name is unique
461     #
462     # Bootstraps credentials
463     #   - bootstrap user credential from self-signed certificate
464     #   - bootstrap authority credential from user credential
465     #   - bootstrap slice credential from user credential
466     #
467     
468     
469     def get_key_file(self):
470        file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".pkey")
471        if (os.path.isfile(file)):
472           return file
473        else:
474           self.logger.error("Key file %s does not exist"%file)
475           sys.exit(-1)
476        return
477     
478     def get_cert_file(self, key_file):
479     
480         cert_file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cert")
481         if (os.path.isfile(cert_file)):
482             # we'd perfer to use Registry issued certs instead of self signed certs. 
483             # if this is a Registry cert (GID) then we are done 
484             gid = GID(filename=cert_file)
485             if gid.get_urn():
486                 return cert_file
487
488         # generate self signed certificate
489         k = Keypair(filename=key_file)
490         cert = Certificate(subject=self.user)
491         cert.set_pubkey(k)
492         cert.set_issuer(k, self.user)
493         cert.sign()
494         self.logger.info("Writing self-signed certificate to %s"%cert_file)
495         cert.save_to_file(cert_file)
496         self.cert = cert
497         # try to get registry issued cert
498         try:
499             self.logger.info("Getting Registry issued cert")
500             self.read_config()
501             # *hack.  need to set registyr before _get_gid() is called 
502             self.registry = sfaprotocol.server_proxy(self.reg_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)
503             gid = self._get_gid(type='user')
504             self.registry = None 
505             self.logger.info("Writing certificate to %s"%cert_file)
506             gid.save_to_file(cert_file)
507         except:
508             self.logger.info("Failed to download Registry issued cert")
509
510         return cert_file
511
512     def get_cached_gid(self, file):
513         """
514         Return a cached gid    
515         """
516         gid = None 
517         if (os.path.isfile(file)):
518             gid = GID(filename=file)
519         return gid
520
521     # xxx opts unused
522     def get_gid(self, opts, args):
523         """
524         Get the specify gid and save it to file
525         """
526         hrn = None
527         if args:
528             hrn = args[0]
529         gid = self._get_gid(hrn)
530         self.logger.debug("Sfi.get_gid-> %s" % gid.save_to_string(save_parents=True))
531         return gid
532
533     def _get_gid(self, hrn=None, type=None):
534         """
535         git_gid helper. Retrive the gid from the registry and save it to file.
536         """
537         
538         if not hrn:
539             hrn = self.user
540  
541         gidfile = os.path.join(self.options.sfi_dir, hrn + ".gid")
542         gid = self.get_cached_gid(gidfile)
543         if not gid:
544             user_cred = self.get_user_cred()
545             records = self.registry.Resolve(hrn, user_cred.save_to_string(save_parents=True))
546             if not records:
547                 raise RecordNotFound(args[0])
548             record = records[0]
549             if type:
550                 record=None
551                 for rec in records:
552                    if type == rec['type']:
553                         record = rec 
554             if not record:
555                 raise RecordNotFound(args[0])
556             
557             gid = GID(string=record['gid'])
558             self.logger.info("Writing gid to %s"%gidfile)
559             gid.save_to_file(filename=gidfile)
560         return gid   
561                 
562      
563     def get_cached_credential(self, file):
564         """
565         Return a cached credential only if it hasn't expired.
566         """
567         if (os.path.isfile(file)):
568             credential = Credential(filename=file)
569             # make sure it isnt expired 
570             if not credential.get_expiration or \
571                datetime.datetime.today() < credential.get_expiration():
572                 return credential
573         return None 
574  
575     def get_user_cred(self):
576         file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cred")
577         return self.get_cred(file, 'user', self.user)
578
579     def get_auth_cred(self):
580         if not self.authority:
581             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
582             sys.exit(-1)
583         file = os.path.join(self.options.sfi_dir, self.authority + ".cred")
584         return self.get_cred(file, 'authority', self.authority)
585
586     def get_slice_cred(self, name):
587         file = os.path.join(self.options.sfi_dir, "slice_" + get_leaf(name) + ".cred")
588         return self.get_cred(file, 'slice', name)
589  
590     def get_cred(self, file, type, hrn):
591         # attempt to load a cached credential 
592         cred = self.get_cached_credential(file)    
593         if not cred:
594             if type in ['user']:
595                 cert_string = self.cert.save_to_string(save_parents=True)
596                 user_name = self.user.replace(self.authority + ".", '')
597                 if user_name.count(".") > 0:
598                     user_name = user_name.replace(".", '_')
599                     self.user = self.authority + "." + user_name
600                 cred_str = self.registry.GetSelfCredential(cert_string, hrn, "user")
601             else:
602                 # bootstrap slice credential from user credential
603                 user_cred = self.get_user_cred().save_to_string(save_parents=True)
604                 cred_str = self.registry.GetCredential(user_cred, hrn, type)
605             
606             if not cred_str:
607                 self.logger.critical("Failed to get %s credential" % type)
608                 sys.exit(-1)
609                 
610             cred = Credential(string=cred_str)
611             cred.save_to_file(file, save_parents=True)
612             self.logger.info("Writing %s credential to %s" %(type, file))
613
614         return cred
615  
616     
617     def get_rspec_file(self, rspec):
618        if (os.path.isabs(rspec)):
619           file = rspec
620        else:
621           file = os.path.join(self.options.sfi_dir, rspec)
622        if (os.path.isfile(file)):
623           return file
624        else:
625           self.logger.critical("No such rspec file %s"%rspec)
626           sys.exit(1)
627     
628     def get_record_file(self, record):
629        if (os.path.isabs(record)):
630           file = record
631        else:
632           file = os.path.join(self.options.sfi_dir, record)
633        if (os.path.isfile(file)):
634           return file
635        else:
636           self.logger.critical("No such registry record file %s"%record)
637           sys.exit(1)
638     
639     def load_publickey_string(self, fn):
640        f = file(fn, "r")
641        key_string = f.read()
642     
643        # if the filename is a private key file, then extract the public key
644        if "PRIVATE KEY" in key_string:
645            outfn = tempfile.mktemp()
646            cmd = "openssl rsa -in " + fn + " -pubout -outform PEM -out " + outfn
647            os.system(cmd)
648            f = file(outfn, "r")
649            key_string = f.read()
650            os.remove(outfn)
651     
652        return key_string
653
654     # xxx opts undefined
655     def get_component_proxy_from_hrn(self, hrn):
656         # direct connection to the nodes component manager interface
657         user_cred = self.get_user_cred().save_to_string(save_parents=True)
658         records = self.registry.Resolve(hrn, user_cred)
659         records = filter_records('node', records)
660         if not records:
661             self.logger.warning("No such component:%r"% opts.component)
662         record = records[0]
663   
664         return self.server_proxy(record['hostname'], CM_PORT, self.key_file, self.cert_file)
665  
666     def server_proxy(self, host, port, keyfile, certfile):
667         """
668         Return an instance of an xmlrpc server connection    
669         """
670         # port is appended onto the domain, before the path. Should look like:
671         # http://domain:port/path
672         host_parts = host.split('/')
673         host_parts[0] = host_parts[0] + ":" + str(port)
674         url =  "http://%s" %  "/".join(host_parts)    
675         return sfaprotocol.server_proxy(url, keyfile, certfile, timeout=self.options.timeout, verbose=self.options.debug)
676
677     # xxx opts could be retrieved in self.options
678     def server_proxy_from_opts(self, opts):
679         """
680         Return instance of an xmlrpc connection to a slice manager, aggregate
681         or component server depending on the specified opts
682         """
683         server = self.slicemgr
684         # direct connection to an aggregate
685         if hasattr(opts, 'aggregate') and opts.aggregate:
686             server = self.server_proxy(opts.aggregate, opts.port, self.key_file, self.cert_file)
687         # direct connection to the nodes component manager interface
688         if hasattr(opts, 'component') and opts.component:
689             server = self.get_component_proxy_from_hrn(opts.component)    
690  
691         return server
692     #==========================================================================
693     # Following functions implement the commands
694     #
695     # Registry-related commands
696     #==========================================================================
697   
698     def dispatch(self, command, cmd_opts, cmd_args):
699         return getattr(self, command)(cmd_opts, cmd_args)
700
701     def create_gid(self, opts, args):
702         if len(args) < 1:
703             self.print_help()
704             sys.exit(1)
705         target_hrn = args[0]
706         user_cred = self.get_user_cred().save_to_string(save_parents=True)
707         gid = self.registry.CreateGid(user_cred, target_hrn, self.cert.save_to_string())
708         if opts.file:
709             filename = opts.file
710         else:
711             filename = os.sep.join([self.sfi_dir, '%s.gid' % target_hrn])
712         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
713         GID(string=gid).save_to_file(filename)
714          
715      
716     # list entires in named authority registry
717     def list(self, opts, args):
718         if len(args)!= 1:
719             self.print_help()
720             sys.exit(1)
721         hrn = args[0]
722         user_cred = self.get_user_cred().save_to_string(save_parents=True)
723         try:
724             list = self.registry.List(hrn, user_cred)
725         except IndexError:
726             raise Exception, "Not enough parameters for the 'list' command"
727
728         # filter on person, slice, site, node, etc.
729         # THis really should be in the self.filter_records funct def comment...
730         list = filter_records(opts.type, list)
731         for record in list:
732             print "%s (%s)" % (record['hrn'], record['type'])
733         if opts.file:
734             save_records_to_file(opts.file, list, opts.fileformat)
735         return
736     
737     # show named registry record
738     def show(self, opts, args):
739         if len(args)!= 1:
740             self.print_help()
741             sys.exit(1)
742         hrn = args[0]
743         user_cred = self.get_user_cred().save_to_string(save_parents=True)
744         records = self.registry.Resolve(hrn, user_cred)
745         records = filter_records(opts.type, records)
746         if not records:
747             print "No record of type", opts.type
748         for record in records:
749             if record['type'] in ['user']:
750                 record = UserRecord(dict=record)
751             elif record['type'] in ['slice']:
752                 record = SliceRecord(dict=record)
753             elif record['type'] in ['node']:
754                 record = NodeRecord(dict=record)
755             elif record['type'].startswith('authority'):
756                 record = AuthorityRecord(dict=record)
757             else:
758                 record = SfaRecord(dict=record)
759             if (opts.format == "text"): 
760                 record.dump()  
761             else:
762                 print record.save_to_string() 
763         if opts.file:
764             save_records_to_file(opts.file, records, opts.fileformat)
765         return
766     
767     def delegate(self, opts, args):
768
769         delegee_hrn = args[0]
770         if opts.delegate_user:
771             user_cred = self.get_user_cred()
772             cred = self.delegate_cred(user_cred, delegee_hrn)
773         elif opts.delegate_slice:
774             slice_cred = self.get_slice_cred(opts.delegate_slice)
775             cred = self.delegate_cred(slice_cred, delegee_hrn)
776         else:
777             self.logger.warning("Must specify either --user or --slice <hrn>")
778             return
779         delegated_cred = Credential(string=cred)
780         object_hrn = delegated_cred.get_gid_object().get_hrn()
781         if opts.delegate_user:
782             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
783                                   + get_leaf(object_hrn) + ".cred")
784         elif opts.delegate_slice:
785             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
786                                   + get_leaf(object_hrn) + ".cred")
787
788         delegated_cred.save_to_file(dest_fn, save_parents=True)
789
790         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
791     
792     def delegate_cred(self, object_cred, hrn):
793         # the gid and hrn of the object we are delegating
794         if isinstance(object_cred, str):
795             object_cred = Credential(string=object_cred) 
796         object_gid = object_cred.get_gid_object()
797         object_hrn = object_gid.get_hrn()
798     
799         if not object_cred.get_privileges().get_all_delegate():
800             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
801             return
802
803         # the delegating user's gid
804         caller_gid = self._get_gid(self.user)
805         caller_gidfile = os.path.join(self.options.sfi_dir, self.user + ".gid")
806   
807         # the gid of the user who will be delegated to
808         delegee_gid = self._get_gid(hrn)
809         delegee_hrn = delegee_gid.get_hrn()
810         delegee_gidfile = os.path.join(self.options.sfi_dir, delegee_hrn + ".gid")
811         delegee_gid.save_to_file(filename=delegee_gidfile)
812         dcred = object_cred.delegate(delegee_gidfile, self.get_key_file(), caller_gidfile)
813         return dcred.save_to_string(save_parents=True)
814      
815     # removed named registry record
816     #   - have to first retrieve the record to be removed
817     def remove(self, opts, args):
818         auth_cred = self.get_auth_cred().save_to_string(save_parents=True)
819         if len(args)!=1:
820             self.print_help()
821             sys.exit(1)
822         hrn = args[0]
823         type = opts.type 
824         if type in ['all']:
825             type = '*'
826         return self.registry.Remove(hrn, auth_cred, type)
827     
828     # add named registry record
829     def add(self, opts, args):
830         auth_cred = self.get_auth_cred().save_to_string(save_parents=True)
831         if len(args)!=1:
832             self.print_help()
833             sys.exit(1)
834         record_filepath = args[0]
835         rec_file = self.get_record_file(record_filepath)
836         record = load_record_from_file(rec_file).as_dict()
837         return self.registry.Register(record, auth_cred)
838     
839     # update named registry entry
840     def update(self, opts, args):
841         user_cred = self.get_user_cred()
842         if len(args)!=1:
843             self.print_help()
844             sys.exit(1)
845         rec_file = self.get_record_file(args[0])
846         record = load_record_from_file(rec_file)
847         if record['type'] == "user":
848             if record.get_name() == user_cred.get_gid_object().get_hrn():
849                 cred = user_cred.save_to_string(save_parents=True)
850             else:
851                 cred = self.get_auth_cred().save_to_string(save_parents=True)
852         elif record['type'] in ["slice"]:
853             try:
854                 cred = self.get_slice_cred(record.get_name()).save_to_string(save_parents=True)
855             except sfaprotocol.ServerException, e:
856                # XXX smbaker -- once we have better error return codes, update this
857                # to do something better than a string compare
858                if "Permission error" in e.args[0]:
859                    cred = self.get_auth_cred().save_to_string(save_parents=True)
860                else:
861                    raise
862         elif record.get_type() in ["authority"]:
863             cred = self.get_auth_cred().save_to_string(save_parents=True)
864         elif record.get_type() == 'node':
865             cred = self.get_auth_cred().save_to_string(save_parents=True)
866         else:
867             raise "unknown record type" + record.get_type()
868         record = record.as_dict()
869         return self.registry.Update(record, cred)
870   
871     def get_trusted_certs(self, opts, args):
872         """
873         return uhe trusted certs at this interface 
874         """ 
875         trusted_certs = self.registry.get_trusted_certs()
876         for trusted_cert in trusted_certs:
877             gid = GID(string=trusted_cert)
878             gid.dump()
879             cert = Certificate(string=trusted_cert)
880             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
881         return 
882
883     def aggregates(self, opts, args):
884         """
885         return a list of details about known aggregates
886         """
887         user_cred = self.get_user_cred().save_to_string(save_parents=True)
888         hrn = None
889         if args:
890             hrn = args[0]
891
892         result = self.registry.get_aggregates(user_cred, hrn)
893         display_list(result)
894         return 
895
896     def registries(self, opts, args):
897         """
898         return a list of details about known registries
899         """
900         user_cred = self.get_user_cred().save_to_string(save_parents=True)
901         hrn = None
902         if args:
903             hrn = args[0]
904         result = self.registry.get_registries(user_cred, hrn)
905         display_list(result)
906         return
907
908  
909     # ==================================================================
910     # Slice-related commands
911     # ==================================================================
912
913     def version(self, opts, args):
914         if opts.version_local:
915             version=version_core()
916         else:
917             if opts.version_registry:
918                 server=self.registry
919             else:
920                 server = self.server_proxy_from_opts(opts)
921             result = server.GetVersion()
922             version = ReturnValue.get_value(result)
923         for (k,v) in version.iteritems():
924             print "%-20s: %s"%(k,v)
925         if opts.file:
926             save_variable_to_file(version, opts.file, opts.fileformat)
927
928     # list instantiated slices
929     def slices(self, opts, args):
930         """
931         list instantiated slices
932         """
933         user_cred = self.get_user_cred().save_to_string(save_parents=True)
934         creds = [user_cred]
935         if opts.delegate:
936             delegated_cred = self.delegate_cred(user_cred, get_authority(self.authority))
937             creds.append(delegated_cred)  
938         server = self.server_proxy_from_opts(opts)
939         call_args = [creds]
940         if self.server_supports_options_arg(server):
941             options = {'call_id': unique_call_id()}
942             call_args.append(options)
943         result = server.ListSlices(*call_args)
944         value = ReturnValue.get_value(result)
945         display_list(value)
946         return
947     
948     # show rspec for named slice
949     def resources(self, opts, args):
950         user_cred = self.get_user_cred().save_to_string(save_parents=True)
951         server = self.server_proxy_from_opts(opts)
952    
953         options = {'call_id': unique_call_id()}
954         #panos add info options
955         if opts.info:
956             options['info'] = opts.info
957         
958         if args:
959             cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
960             hrn = args[0]
961             options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
962         else:
963             cred = user_cred
964      
965         creds = [cred]
966         if opts.delegate:
967             delegated_cred = self.delegate_cred(cred, get_authority(self.authority))
968             creds.append(delegated_cred)
969         if opts.rspec_version:
970             version_manager = VersionManager()
971             server_version = self.get_cached_server_version(server)
972             if 'sfa' in server_version:
973                 # just request the version the client wants 
974                 options['geni_rspec_version'] = version_manager.get_version(opts.rspec_version).to_dict()
975             else:
976                 # this must be a protogeni aggregate. We should request a v2 ad rspec
977                 # regardless of what the client user requested 
978                 options['geni_rspec_version'] = version_manager.get_version('ProtoGENI 2').to_dict()     
979         else:
980             options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
981  
982         call_args = [creds, options]
983         result = server.ListResources(*call_args)
984         value = ReturnValue.get_value(result)
985         if opts.file is None:
986             display_rspec(value, opts.format)
987         else:
988             save_rspec_to_file(value, opts.file)
989         return
990
991     # created named slice with given rspec
992     def create(self, opts, args):
993         server = self.server_proxy_from_opts(opts)
994         server_version = self.get_cached_server_version(server)
995         slice_hrn = args[0]
996         slice_urn = hrn_to_urn(slice_hrn, 'slice')
997         user_cred = self.get_user_cred()
998         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
999
1000         if hasattr(opts, 'aggregate') and opts.aggregate:
1001             delegated_cred = None
1002         else:
1003             # delegate the cred to the callers root authority
1004             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority)+'.slicemanager')
1005             #delegated_cred = self.delegate_cred(slice_cred, get_authority(slice_hrn))
1006             #creds.append(delegated_cred)
1007
1008         rspec_file = self.get_rspec_file(args[1])
1009         rspec = open(rspec_file).read()
1010
1011         # need to pass along user keys to the aggregate.
1012         # users = [
1013         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1014         #    keys: [<ssh key A>, <ssh key B>]
1015         #  }]
1016         users = []
1017         slice_records = self.registry.Resolve(slice_urn, [user_cred.save_to_string(save_parents=True)])
1018         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1019             slice_record = slice_records[0]
1020             user_hrns = slice_record['researcher']
1021             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1022             user_records = self.registry.Resolve(user_urns, [user_cred.save_to_string(save_parents=True)])
1023
1024             if 'sfa' not in server_version:
1025                 users = pg_users_arg(user_records)
1026                 rspec = RSpec(rspec)
1027                 rspec.filter({'component_manager_id': server_version['urn']})
1028                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1029                 creds = [slice_cred]
1030             else:
1031                 users = sfa_users_arg(user_records, slice_record)
1032                 creds = [slice_cred]
1033                 if delegated_cred:
1034                     creds.append(delegated_cred)
1035         call_args = [slice_urn, creds, rspec, users]
1036         if self.server_supports_options_arg(server):
1037             options = {'call_id': unique_call_id()}
1038             call_args.append(options)
1039         result = server.CreateSliver(*call_args)
1040         value = ReturnValue.get_value(result)
1041         if opts.file is None:
1042             print value
1043         else:
1044             save_rspec_to_file (value, opts.file)
1045         return value
1046
1047     # get a ticket for the specified slice
1048     def get_ticket(self, opts, args):
1049         slice_hrn, rspec_path = args[0], args[1]
1050         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1051         user_cred = self.get_user_cred()
1052         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1053         creds = [slice_cred]
1054         if opts.delegate:
1055             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1056             creds.append(delegated_cred)
1057         rspec_file = self.get_rspec_file(rspec_path) 
1058         rspec = open(rspec_file).read()
1059         server = self.server_proxy_from_opts(opts)
1060         ticket_string = server.GetTicket(slice_urn, creds, rspec, [])
1061         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1062         self.logger.info("writing ticket to %s"%file)
1063         ticket = SfaTicket(string=ticket_string)
1064         ticket.save_to_file(filename=file, save_parents=True)
1065
1066     def redeem_ticket(self, opts, args):
1067         ticket_file = args[0]
1068         
1069         # get slice hrn from the ticket
1070         # use this to get the right slice credential 
1071         ticket = SfaTicket(filename=ticket_file)
1072         ticket.decode()
1073         slice_hrn = ticket.gidObject.get_hrn()
1074         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1075         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1076         user_cred = self.get_user_cred()
1077         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1078         
1079         # get a list of node hostnames from the RSpec 
1080         tree = etree.parse(StringIO(ticket.rspec))
1081         root = tree.getroot()
1082         hostnames = root.xpath("./network/site/node/hostname/text()")
1083         
1084         # create an xmlrpc connection to the component manager at each of these
1085         # components and gall redeem_ticket
1086         connections = {}
1087         for hostname in hostnames:
1088             try:
1089                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1090                 server = self.server_proxy(hostname, CM_PORT, self.key_file, \
1091                                          self.cert_file, self.options.debug)
1092                 server.RedeemTicket(ticket.save_to_string(save_parents=True), slice_cred)
1093                 self.logger.info("Success")
1094             except socket.gaierror:
1095                 self.logger.error("redeem_ticket failed: Component Manager not accepting requests")
1096             except Exception, e:
1097                 self.logger.log_exc(e.message)
1098         return
1099  
1100     # delete named slice
1101     def delete(self, opts, args):
1102         slice_hrn = args[0]
1103         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1104         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1105         creds = [slice_cred]
1106         if opts.delegate:
1107             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1108             creds.append(delegated_cred)
1109         server = self.server_proxy_from_opts(opts)
1110         call_args = [slice_urn, creds]
1111         if self.server_supports_options_arg(server):
1112             options = {'call_id': unique_call_id()}
1113             call_args.append(options)
1114         return server.DeleteSliver(*call_args) 
1115   
1116     # start named slice
1117     def start(self, opts, args):
1118         slice_hrn = args[0]
1119         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1120         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1121         creds = [slice_cred]
1122         if opts.delegate:
1123             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1124             creds.append(delegated_cred)
1125         server = self.server_proxy_from_opts(opts)
1126         return server.Start(slice_urn, creds)
1127     
1128     # stop named slice
1129     def stop(self, opts, args):
1130         slice_hrn = args[0]
1131         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1132         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1133         creds = [slice_cred]
1134         if opts.delegate:
1135             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1136             creds.append(delegated_cred)
1137         server = self.server_proxy_from_opts(opts)
1138         return server.Stop(slice_urn, creds)
1139     
1140     # reset named slice
1141     def reset(self, opts, args):
1142         slice_hrn = args[0]
1143         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1144         server = self.server_proxy_from_opts(opts)
1145         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1146         creds = [slice_cred]
1147         if opts.delegate:
1148             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1149             creds.append(delegated_cred)
1150         return server.reset_slice(creds, slice_urn)
1151
1152     def renew(self, opts, args):
1153         slice_hrn = args[0]
1154         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1155         server = self.server_proxy_from_opts(opts)
1156         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1157         creds = [slice_cred]
1158         if opts.delegate:
1159             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1160             creds.append(delegated_cred)
1161         time = args[1]
1162         
1163         call_args = [slice_urn, creds, time]
1164         if self.server_supports_options_arg(server):
1165             options = {'call_id': unique_call_id()}
1166             call_args.append(options)
1167         result =  server.RenewSliver(*call_args)
1168         value = ReturnValue.get_value(result)
1169         return value
1170
1171
1172     def status(self, opts, args):
1173         slice_hrn = args[0]
1174         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1175         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1176         creds = [slice_cred]
1177         if opts.delegate:
1178             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1179             creds.append(delegated_cred)
1180         server = self.server_proxy_from_opts(opts)
1181         call_args = [slice_urn, creds]
1182         if self.server_supports_options_arg(server):
1183             options = {'call_id': unique_call_id()}
1184             call_args.append(options)
1185         result = server.SliverStatus(*call_args)
1186         value = ReturnValue.get_value(result)
1187         print value
1188         if opts.file:
1189             save_variable_to_file(value, opts.file, opts.fileformat)
1190
1191
1192     def shutdown(self, opts, args):
1193         slice_hrn = args[0]
1194         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1195         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1196         creds = [slice_cred]
1197         if opts.delegate:
1198             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1199             creds.append(delegated_cred)
1200         server = self.server_proxy_from_opts(opts)
1201         return server.Shutdown(slice_urn, creds)         
1202     
1203     def print_help (self):
1204         self.sfi_parser.print_help()
1205         self.cmd_parser.print_help()
1206
1207     #
1208     # Main: parse arguments and dispatch to command
1209     #
1210     def main(self):
1211         self.sfi_parser = self.create_parser()
1212         (options, args) = self.sfi_parser.parse_args()
1213         self.options = options
1214
1215         self.logger.setLevelFromOptVerbose(self.options.verbose)
1216         if options.hashrequest:
1217             self.hashrequest = True
1218  
1219         if len(args) <= 0:
1220             self.logger.critical("No command given. Use -h for help.")
1221             return -1
1222     
1223         command = args[0]
1224         self.cmd_parser = self.create_cmd_parser(command)
1225         (cmd_opts, cmd_args) = self.cmd_parser.parse_args(args[1:])
1226
1227         self.set_servers()
1228         self.logger.info("Command=%s" % command)
1229         if command in ("resources"):
1230             self.logger.debug("resources cmd_opts %s" % cmd_opts.format)
1231         elif command in ("list", "show", "remove"):
1232             self.logger.debug("cmd_opts.type %s" % cmd_opts.type)
1233         self.logger.debug('cmd_args %s' % cmd_args)
1234
1235         try:
1236             self.dispatch(command, cmd_opts, cmd_args)
1237         except KeyError:
1238             self.logger.critical ("Unknown command %s"%command)
1239             raise
1240             sys.exit(1)
1241     
1242         return
1243     
1244 if __name__ == "__main__":
1245     Sfi().main()