845b2b134092b2dda292cb5ac90518111a79c178
[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         else:
982             options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
983  
984         call_args = [creds, options]
985         result = server.ListResources(*call_args)
986         value = ReturnValue.get_value(result)
987         if opts.file is None:
988             display_rspec(value, opts.format)
989         else:
990             save_rspec_to_file(value, opts.file)
991         return
992
993     # created named slice with given rspec
994     def create(self, opts, args):
995         server = self.server_proxy_from_opts(opts)
996         server_version = self.get_cached_server_version(server)
997         slice_hrn = args[0]
998         slice_urn = hrn_to_urn(slice_hrn, 'slice')
999         user_cred = self.get_user_cred()
1000         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1001
1002         if hasattr(opts, 'aggregate') and opts.aggregate:
1003             delegated_cred = None
1004         else:
1005             # delegate the cred to the callers root authority
1006             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority)+'.slicemanager')
1007             #delegated_cred = self.delegate_cred(slice_cred, get_authority(slice_hrn))
1008             #creds.append(delegated_cred)
1009
1010         rspec_file = self.get_rspec_file(args[1])
1011         rspec = open(rspec_file).read()
1012
1013         # need to pass along user keys to the aggregate.
1014         # users = [
1015         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1016         #    keys: [<ssh key A>, <ssh key B>]
1017         #  }]
1018         users = []
1019         slice_records = self.registry.Resolve(slice_urn, [user_cred.save_to_string(save_parents=True)])
1020         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1021             slice_record = slice_records[0]
1022             user_hrns = slice_record['researcher']
1023             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1024             user_records = self.registry.Resolve(user_urns, [user_cred.save_to_string(save_parents=True)])
1025
1026             if 'sfa' not in server_version:
1027                 users = pg_users_arg(user_records)
1028                 rspec = RSpec(rspec)
1029                 rspec.filter({'component_manager_id': server_version['urn']})
1030                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1031                 creds = [slice_cred]
1032             else:
1033                 users = sfa_users_arg(user_records, slice_record)
1034                 creds = [slice_cred]
1035                 if delegated_cred:
1036                     creds.append(delegated_cred)
1037         call_args = [slice_urn, creds, rspec, users]
1038         if self.server_supports_options_arg(server):
1039             options = {'call_id': unique_call_id()}
1040             call_args.append(options)
1041         result = server.CreateSliver(*call_args)
1042         value = ReturnValue.get_value(result)
1043         if opts.file is None:
1044             print value
1045         else:
1046             save_rspec_to_file (value, opts.file)
1047         return value
1048
1049     # get a ticket for the specified slice
1050     def get_ticket(self, opts, args):
1051         slice_hrn, rspec_path = args[0], args[1]
1052         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1053         user_cred = self.get_user_cred()
1054         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1055         creds = [slice_cred]
1056         if opts.delegate:
1057             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1058             creds.append(delegated_cred)
1059         rspec_file = self.get_rspec_file(rspec_path) 
1060         rspec = open(rspec_file).read()
1061         server = self.server_proxy_from_opts(opts)
1062         ticket_string = server.GetTicket(slice_urn, creds, rspec, [])
1063         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1064         self.logger.info("writing ticket to %s"%file)
1065         ticket = SfaTicket(string=ticket_string)
1066         ticket.save_to_file(filename=file, save_parents=True)
1067
1068     def redeem_ticket(self, opts, args):
1069         ticket_file = args[0]
1070         
1071         # get slice hrn from the ticket
1072         # use this to get the right slice credential 
1073         ticket = SfaTicket(filename=ticket_file)
1074         ticket.decode()
1075         slice_hrn = ticket.gidObject.get_hrn()
1076         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1077         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1078         user_cred = self.get_user_cred()
1079         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1080         
1081         # get a list of node hostnames from the RSpec 
1082         tree = etree.parse(StringIO(ticket.rspec))
1083         root = tree.getroot()
1084         hostnames = root.xpath("./network/site/node/hostname/text()")
1085         
1086         # create an xmlrpc connection to the component manager at each of these
1087         # components and gall redeem_ticket
1088         connections = {}
1089         for hostname in hostnames:
1090             try:
1091                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1092                 server = self.server_proxy(hostname, CM_PORT, self.key_file, \
1093                                          self.cert_file, self.options.debug)
1094                 server.RedeemTicket(ticket.save_to_string(save_parents=True), slice_cred)
1095                 self.logger.info("Success")
1096             except socket.gaierror:
1097                 self.logger.error("redeem_ticket failed: Component Manager not accepting requests")
1098             except Exception, e:
1099                 self.logger.log_exc(e.message)
1100         return
1101  
1102     # delete named slice
1103     def delete(self, opts, args):
1104         slice_hrn = args[0]
1105         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1106         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1107         creds = [slice_cred]
1108         if opts.delegate:
1109             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1110             creds.append(delegated_cred)
1111         server = self.server_proxy_from_opts(opts)
1112         call_args = [slice_urn, creds]
1113         if self.server_supports_options_arg(server):
1114             options = {'call_id': unique_call_id()}
1115             call_args.append(options)
1116         return server.DeleteSliver(*call_args) 
1117   
1118     # start named slice
1119     def start(self, opts, args):
1120         slice_hrn = args[0]
1121         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1122         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1123         creds = [slice_cred]
1124         if opts.delegate:
1125             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1126             creds.append(delegated_cred)
1127         server = self.server_proxy_from_opts(opts)
1128         return server.Start(slice_urn, creds)
1129     
1130     # stop named slice
1131     def stop(self, opts, args):
1132         slice_hrn = args[0]
1133         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1134         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1135         creds = [slice_cred]
1136         if opts.delegate:
1137             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1138             creds.append(delegated_cred)
1139         server = self.server_proxy_from_opts(opts)
1140         return server.Stop(slice_urn, creds)
1141     
1142     # reset named slice
1143     def reset(self, opts, args):
1144         slice_hrn = args[0]
1145         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1146         server = self.server_proxy_from_opts(opts)
1147         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1148         creds = [slice_cred]
1149         if opts.delegate:
1150             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1151             creds.append(delegated_cred)
1152         return server.reset_slice(creds, slice_urn)
1153
1154     def renew(self, opts, args):
1155         slice_hrn = args[0]
1156         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1157         server = self.server_proxy_from_opts(opts)
1158         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1159         creds = [slice_cred]
1160         if opts.delegate:
1161             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1162             creds.append(delegated_cred)
1163         time = args[1]
1164         
1165         call_args = [slice_urn, creds, time]
1166         if self.server_supports_options_arg(server):
1167             options = {'call_id': unique_call_id()}
1168             call_args.append(options)
1169         result =  server.RenewSliver(*call_args)
1170         value = ReturnValue.get_value(result)
1171         return value
1172
1173
1174     def status(self, opts, args):
1175         slice_hrn = args[0]
1176         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1177         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1178         creds = [slice_cred]
1179         if opts.delegate:
1180             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1181             creds.append(delegated_cred)
1182         server = self.server_proxy_from_opts(opts)
1183         call_args = [slice_urn, creds]
1184         if self.server_supports_options_arg(server):
1185             options = {'call_id': unique_call_id()}
1186             call_args.append(options)
1187         result = server.SliverStatus(*call_args)
1188         value = ReturnValue.get_value(result)
1189         print value
1190         if opts.file:
1191             save_variable_to_file(value, opts.file, opts.fileformat)
1192
1193
1194     def shutdown(self, opts, args):
1195         slice_hrn = args[0]
1196         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1197         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1198         creds = [slice_cred]
1199         if opts.delegate:
1200             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1201             creds.append(delegated_cred)
1202         server = self.server_proxy_from_opts(opts)
1203         return server.Shutdown(slice_urn, creds)         
1204     
1205     def print_help (self):
1206         self.sfi_parser.print_help()
1207         self.cmd_parser.print_help()
1208
1209     #
1210     # Main: parse arguments and dispatch to command
1211     #
1212     def main(self):
1213         self.sfi_parser = self.create_parser()
1214         (options, args) = self.sfi_parser.parse_args()
1215         self.options = options
1216
1217         self.logger.setLevelFromOptVerbose(self.options.verbose)
1218         if options.hashrequest:
1219             self.hashrequest = True
1220  
1221         if len(args) <= 0:
1222             self.logger.critical("No command given. Use -h for help.")
1223             return -1
1224     
1225         command = args[0]
1226         self.cmd_parser = self.create_cmd_parser(command)
1227         (cmd_opts, cmd_args) = self.cmd_parser.parse_args(args[1:])
1228
1229         self.set_servers()
1230         self.logger.info("Command=%s" % command)
1231         if command in ("resources"):
1232             self.logger.debug("resources cmd_opts %s" % cmd_opts.format)
1233         elif command in ("list", "show", "remove"):
1234             self.logger.debug("cmd_opts.type %s" % cmd_opts.type)
1235         self.logger.debug('cmd_args %s' % cmd_args)
1236
1237         try:
1238             self.dispatch(command, cmd_opts, cmd_args)
1239         except KeyError:
1240             self.logger.critical ("Unknown command %s"%command)
1241             raise
1242             sys.exit(1)
1243     
1244         return
1245     
1246 if __name__ == "__main__":
1247     Sfi().main()