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