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