Merge branch 'master' of ssh://git.planet-lab.org/git/sfa
[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 import os, os.path
8 import tempfile
9 import traceback
10 import socket
11 import random
12 import datetime
13 import zlib
14 import codecs
15 import pickle
16 from lxml import etree
17 from StringIO import StringIO
18 from types import StringTypes, ListType
19 from optparse import OptionParser
20 from sfa.client.client_helper import pg_users_arg, sfa_users_arg, sfa_to_pg_users_arg 
21 from sfa.util.sfalogging import sfi_logger
22 from sfa.trust.certificate import Keypair, Certificate
23 from sfa.trust.gid import GID
24 from sfa.trust.credential import Credential
25 from sfa.util.sfaticket import SfaTicket
26 from sfa.util.record import SfaRecord, UserRecord, SliceRecord, NodeRecord, AuthorityRecord
27 from sfa.rspecs.rspec import RSpec
28 from sfa.rspecs.rspec_converter import RSpecConverter
29 from sfa.util.xrn import Xrn, get_leaf, get_authority, hrn_to_urn
30 import sfa.util.xmlrpcprotocol as xmlrpcprotocol
31 from sfa.util.config import Config
32 from sfa.util.version import version_core
33 from sfa.util.cache import Cache
34 from sfa.rspecs.version_manager import VersionManager
35
36 AGGREGATE_PORT=12346
37 CM_PORT=12346
38
39 # utility methods here
40 # display methods
41 def display_rspec(rspec, format='rspec'):
42     if format in ['dns']:
43         tree = etree.parse(StringIO(rspec))
44         root = tree.getroot()
45         result = root.xpath("./network/site/node/hostname/text()")
46     elif format in ['ip']:
47         # The IP address is not yet part of the new RSpec
48         # so this doesn't do anything yet.
49         tree = etree.parse(StringIO(rspec))
50         root = tree.getroot()
51         result = root.xpath("./network/site/node/ipv4/text()")
52     else:
53         result = rspec
54
55     print result
56     return
57
58 def display_list(results):
59     for result in results:
60         print result
61
62 def display_records(recordList, dump=False):
63     ''' Print all fields in the record'''
64     for record in recordList:
65         display_record(record, dump)
66
67 def display_record(record, dump=False):
68     if dump:
69         record.dump()
70     else:
71         info = record.getdict()
72         print "%s (%s)" % (info['hrn'], info['type'])
73     return
74
75
76 def filter_records(type, records):
77     filtered_records = []
78     for record in records:
79         if (record['type'] == type) or (type == "all"):
80             filtered_records.append(record)
81     return filtered_records
82
83
84 # save methods
85 def save_variable_to_file(var, filename, format="text"):
86     f = open(filename, "w")
87     if format == "text":
88         f.write(str(var))
89     elif format == "pickled":
90         f.write(pickle.dumps(var))
91     else:
92         # this should never happen
93         print "unknown output format", format
94
95
96 def save_rspec_to_file(rspec, filename):
97     if not filename.endswith(".rspec"):
98         filename = filename + ".rspec"
99     f = open(filename, 'w')
100     f.write(rspec)
101     f.close()
102     return
103
104 def save_records_to_file(filename, recordList, format="xml"):
105     if format == "xml":
106         index = 0
107         for record in recordList:
108             if index > 0:
109                 save_record_to_file(filename + "." + str(index), record)
110             else:
111                 save_record_to_file(filename, record)
112             index = index + 1
113     elif format == "xmllist":
114         f = open(filename, "w")
115         f.write("<recordlist>\n")
116         for record in recordList:
117             record = SfaRecord(dict=record)
118             f.write('<record hrn="' + record.get_name() + '" type="' + record.get_type() + '" />\n')
119         f.write("</recordlist>\n");
120         f.close()
121     elif format == "hrnlist":
122         f = open(filename, "w")
123         for record in recordList:
124             record = SfaRecord(dict=record)
125             f.write(record.get_name() + "\n")
126         f.close()
127     else:
128         # this should never happen
129         print "unknown output format", format
130
131 def save_record_to_file(filename, record):
132     if record['type'] in ['user']:
133         record = UserRecord(dict=record)
134     elif record['type'] in ['slice']:
135         record = SliceRecord(dict=record)
136     elif record['type'] in ['node']:
137         record = NodeRecord(dict=record)
138     elif record['type'] in ['authority', 'ma', 'sa']:
139         record = AuthorityRecord(dict=record)
140     else:
141         record = SfaRecord(dict=record)
142     str = record.save_to_string()
143     f=codecs.open(filename, encoding='utf-8',mode="w")
144     f.write(str)
145     f.close()
146     return
147
148
149 # load methods
150 def load_record_from_file(filename):
151     f=codecs.open(filename, encoding="utf-8", mode="r")
152     str = f.read()
153     f.close()
154     record = SfaRecord(string=str)
155     return record
156
157
158 import uuid
159 def unique_call_id(): return uuid.uuid4().urn
160
161 class Sfi:
162     
163     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user']
164
165     # dummy to meet Sfi's expectations for its 'options' field
166     # i.e. s/t we can do setattr on
167     class DummyOptions:
168         pass
169
170     def __init__ (self,options=None):
171         if options is None: options=Sfi.DummyOptions()
172         for opt in Sfi.required_options:
173             if not hasattr(options,opt): setattr(options,opt,None)
174         if not hasattr(options,'sfi_dir'): options.sfi_dir=os.path.expanduser("~/.sfi/")
175         # xxx oops, this is dangerous, sounds like ww sometimes have discrepency
176         # would be safer to remove self.sfi_dir altogether
177         self.sfi_dir = options.sfi_dir
178         self.options = options
179         self.slicemgr = None
180         self.registry = None
181         self.user = None
182         self.authority = None
183         self.hashrequest = False
184         self.logger = sfi_logger
185         self.logger.enable_console()
186    
187     def create_cmd_parser(self, command, additional_cmdargs=None):
188         cmdargs = {"list": "authority",
189                   "show": "name",
190                   "remove": "name",
191                   "add": "record",
192                   "update": "record",
193                   "aggregates": "[name]",
194                   "registries": "[name]",
195                   "create_gid": "[name]",
196                   "get_gid": [],  
197                   "get_trusted_certs": "cred",
198                   "slices": "",
199                   "resources": "[name]",
200                   "create": "name rspec",
201                   "get_ticket": "name rspec",
202                   "redeem_ticket": "ticket",
203                   "delete": "name",
204                   "reset": "name",
205                   "start": "name",
206                   "stop": "name",
207                   "delegate": "name",
208                   "status": "name",
209                   "renew": "name",
210                   "shutdown": "name",
211                   "version": "",  
212                  }
213
214         if additional_cmdargs:
215             cmdargs.update(additional_cmdargs)
216
217         if command not in cmdargs:
218             msg="Invalid command\n"
219             msg+="Commands: "
220             msg += ','.join(cmdargs.keys())            
221             self.logger.critical(msg)
222             sys.exit(2)
223
224         parser = OptionParser(usage="sfi [sfi_options] %s [options] %s" \
225                                      % (command, cmdargs[command]))
226
227         # user specifies remote aggregate/sm/component                          
228         if command in ("resources", "slices", "create", "delete", "start", "stop", 
229                        "restart", "shutdown",  "get_ticket", "renew", "status"):
230             parser.add_option("-a", "--aggregate", dest="aggregate",
231                              default=None, help="aggregate host")
232             parser.add_option("-p", "--port", dest="port",
233                              default=AGGREGATE_PORT, help="aggregate port")
234             parser.add_option("-c", "--component", dest="component", default=None,
235                              help="component hrn")
236             parser.add_option("-d", "--delegate", dest="delegate", default=None, 
237                              action="store_true",
238                              help="Include a credential delegated to the user's root"+\
239                                   "authority in set of credentials for this call")  
240         
241         # registy filter option    
242         if command in ("list", "show", "remove"):
243             parser.add_option("-t", "--type", dest="type", type="choice",
244                             help="type filter ([all]|user|slice|authority|node|aggregate)",
245                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
246                             default="all")
247         # display formats
248         if command in ("resources"):
249             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
250                               help="schema type and version of resulting RSpec")
251             parser.add_option("-f", "--format", dest="format", type="choice",
252                              help="display format ([xml]|dns|ip)", default="xml",
253                              choices=("xml", "dns", "ip"))
254             #panos: a new option to define the type of information about resources a user is interested in
255             parser.add_option("-i", "--info", dest="info",
256                                 help="optional component information", default=None)
257
258
259         # 'create' does return the new rspec, makes sense to save that too
260         if command in ("resources", "show", "list", "create_gid", 'create'):
261            parser.add_option("-o", "--output", dest="file",
262                             help="output XML to file", metavar="FILE", default=None)
263
264         if command in ("show", "list"):
265            parser.add_option("-f", "--format", dest="format", type="choice",
266                              help="display format ([text]|xml)", default="text",
267                              choices=("text", "xml"))
268
269            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
270                              help="output file format ([xml]|xmllist|hrnlist)", default="xml",
271                              choices=("xml", "xmllist", "hrnlist"))
272
273         if command in ("status"):
274            parser.add_option("-o", "--output", dest="file",
275                             help="output dictionary to file", metavar="FILE", default=None)
276            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
277                              help="output file format ([text]|pickled)", default="text",
278                              choices=("text","pickled"))
279
280         if command in ("delegate"):
281            parser.add_option("-u", "--user",
282                             action="store_true", dest="delegate_user", default=False,
283                             help="delegate user credential")
284            parser.add_option("-s", "--slice", dest="delegate_slice",
285                             help="delegate slice credential", metavar="HRN", default=None)
286         
287         if command in ("version"):
288             parser.add_option("-a", "--aggregate", dest="aggregate",
289                              default=None, help="aggregate host")
290             parser.add_option("-p", "--port", dest="port",
291                              default=AGGREGATE_PORT, help="aggregate port")
292             parser.add_option("-R","--registry-version",
293                               action="store_true", dest="version_registry", default=False,
294                               help="probe registry version instead of slicemgr")
295             parser.add_option("-l","--local",
296                               action="store_true", dest="version_local", default=False,
297                               help="display version of the local client")
298
299         return parser
300
301         
302     def create_parser(self):
303
304         # Generate command line parser
305         parser = OptionParser(usage="sfi [options] command [command_options] [command_args]",
306                              description="Commands: gid,list,show,remove,add,update,nodes,slices,resources,create,delete,start,stop,reset")
307         parser.add_option("-r", "--registry", dest="registry",
308                          help="root registry", metavar="URL", default=None)
309         parser.add_option("-s", "--slicemgr", dest="sm",
310                          help="slice manager", metavar="URL", default=None)
311         default_sfi_dir = os.path.expanduser("~/.sfi/")
312         parser.add_option("-d", "--dir", dest="sfi_dir",
313                          help="config & working directory - default is " + default_sfi_dir,
314                          metavar="PATH", default=default_sfi_dir)
315         parser.add_option("-u", "--user", dest="user",
316                          help="user name", metavar="HRN", default=None)
317         parser.add_option("-a", "--auth", dest="auth",
318                          help="authority name", metavar="HRN", default=None)
319         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
320                          help="verbose mode - cumulative")
321         parser.add_option("-D", "--debug",
322                           action="store_true", dest="debug", default=False,
323                           help="Debug (xml-rpc) protocol messages")
324         parser.add_option("-p", "--protocol", dest="protocol", default="xmlrpc",
325                          help="RPC protocol (xmlrpc or soap)")
326         parser.add_option("-k", "--hashrequest",
327                          action="store_true", dest="hashrequest", default=False,
328                          help="Create a hash of the request that will be authenticated on the server")
329         parser.add_option("-t", "--timeout", dest="timeout", default=None,
330                          help="Amout of time tom wait before timing out the request")
331         parser.disable_interspersed_args()
332
333         return parser
334         
335
336     def read_config(self):
337        config_file = os.path.join(self.options.sfi_dir,"sfi_config")
338        try:
339           config = Config (config_file)
340        except:
341           self.logger.critical("Failed to read configuration file %s"%config_file)
342           self.logger.info("Make sure to remove the export clauses and to add quotes")
343           if self.options.verbose==0:
344               self.logger.info("Re-run with -v for more details")
345           else:
346               self.logger.log_exc("Could not read config file %s"%config_file)
347           sys.exit(1)
348     
349        errors = 0
350        # Set SliceMgr URL
351        if (self.options.sm is not None):
352           self.sm_url = self.options.sm
353        elif hasattr(config, "SFI_SM"):
354           self.sm_url = config.SFI_SM
355        else:
356           self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
357           errors += 1 
358     
359        # Set Registry URL
360        if (self.options.registry is not None):
361           self.reg_url = self.options.registry
362        elif hasattr(config, "SFI_REGISTRY"):
363           self.reg_url = config.SFI_REGISTRY
364        else:
365           self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
366           errors += 1 
367           
368
369        # Set user HRN
370        if (self.options.user is not None):
371           self.user = self.options.user
372        elif hasattr(config, "SFI_USER"):
373           self.user = config.SFI_USER
374        else:
375           self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
376           errors += 1 
377     
378        # Set authority HRN
379        if (self.options.auth is not None):
380           self.authority = self.options.auth
381        elif hasattr(config, "SFI_AUTH"):
382           self.authority = config.SFI_AUTH
383        else:
384           self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
385           errors += 1 
386     
387        if errors:
388           sys.exit(1)
389
390
391     #
392     # Establish Connection to SliceMgr and Registry Servers
393     #
394     def set_servers(self):
395
396        self.read_config() 
397        # Get key and certificate
398        key_file = self.get_key_file()
399        cert_file = self.get_cert_file(key_file)
400        self.key = Keypair(filename=key_file) 
401        self.key_file = key_file
402        self.cert_file = cert_file
403        self.cert = GID(filename=cert_file)
404        self.logger.info("Contacting Registry at: %s"%self.reg_url)
405        self.registry = xmlrpcprotocol.get_server(self.reg_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)  
406        self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
407        self.slicemgr = xmlrpcprotocol.get_server(self.sm_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)
408        return
409
410     def get_cached_server_version(self, server):
411         # check local cache first
412         cache = None
413         version = None 
414         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
415         cache_key = server.url + "-version"
416         try:
417             cache = Cache(cache_file)
418         except IOError:
419             cache = Cache()
420             self.logger.info("Local cache not found at: %s" % cache_file)
421
422         if cache:
423             version = cache.get(cache_key)
424             
425         if not version: 
426             version = server.GetVersion()
427             # cache version for 24 hours
428             cache.add(cache_key, version, ttl= 60*60*24)
429             self.logger.info("Updating cache file %s" % cache_file)
430             cache.save_to_file(cache_file)
431
432
433         return version   
434         
435
436     def server_supports_call_id_arg(self, server):
437         """
438         Returns true if server support the optional call_id arg, false otherwise. 
439         """
440         server_version = self.get_cached_server_version(server)
441         if 'sfa' in server_version and 'code_tag' in server_version:
442             code_tag = server_version['code_tag']
443             code_tag_parts = code_tag.split("-")
444             
445             version_parts = code_tag_parts[0].split(".")
446             major, minor = version_parts[0], version_parts[1]
447             rev = code_tag_parts[1]
448             if int(major) > 1:
449                 if int(minor) > 0 or int(rev) > 20:
450                     return True
451         return False                
452              
453     #
454     # Get various credential and spec files
455     #
456     # Establishes limiting conventions
457     #   - conflates MAs and SAs
458     #   - assumes last token in slice name is unique
459     #
460     # Bootstraps credentials
461     #   - bootstrap user credential from self-signed certificate
462     #   - bootstrap authority credential from user credential
463     #   - bootstrap slice credential from user credential
464     #
465     
466     
467     def get_key_file(self):
468        file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".pkey")
469        if (os.path.isfile(file)):
470           return file
471        else:
472           self.logger.error("Key file %s does not exist"%file)
473           sys.exit(-1)
474        return
475     
476     def get_cert_file(self, key_file):
477     
478         cert_file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cert")
479         if (os.path.isfile(cert_file)):
480             # we'd perfer to use Registry issued certs instead of self signed certs. 
481             # if this is a Registry cert (GID) then we are done 
482             gid = GID(filename=cert_file)
483             if gid.get_urn():
484                 return cert_file
485
486         # generate self signed certificate
487         k = Keypair(filename=key_file)
488         cert = Certificate(subject=self.user)
489         cert.set_pubkey(k)
490         cert.set_issuer(k, self.user)
491         cert.sign()
492         self.logger.info("Writing self-signed certificate to %s"%cert_file)
493         cert.save_to_file(cert_file)
494         self.cert = cert
495         # try to get registry issued cert
496         try:
497             self.logger.info("Getting Registry issued cert")
498             self.read_config()
499             # *hack.  need to set registyr before _get_gid() is called 
500             self.registry = xmlrpcprotocol.get_server(self.reg_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)
501             gid = self._get_gid(type='user')
502             self.registry = None 
503             self.logger.info("Writing certificate to %s"%cert_file)
504             gid.save_to_file(cert_file)
505         except:
506             self.logger.info("Failed to download Registry issued cert")
507
508         return cert_file
509
510     def get_cached_gid(self, file):
511         """
512         Return a cached gid    
513         """
514         gid = None 
515         if (os.path.isfile(file)):
516             gid = GID(filename=file)
517         return gid
518
519     # xxx opts unused
520     def get_gid(self, opts, args):
521         """
522         Get the specify gid and save it to file
523         """
524         hrn = None
525         if args:
526             hrn = args[0]
527         gid = self._get_gid(hrn)
528         self.logger.debug("Sfi.get_gid-> %s",gid.save_to_string(save_parents=True))
529         return gid
530
531     def _get_gid(self, hrn=None, type=None):
532         """
533         git_gid helper. Retrive the gid from the registry and save it to file.
534         """
535         
536         if not hrn:
537             hrn = self.user
538  
539         gidfile = os.path.join(self.options.sfi_dir, hrn + ".gid")
540         print gidfile
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_server_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.get_server(record['hostname'], CM_PORT, self.key_file, self.cert_file)
664  
665     def get_server(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.get_server(url, keyfile, certfile, timeout=self.options.timeout, verbose=self.options.debug)
675
676     # xxx opts could be retrieved in self.options
677     def get_server_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.get_server(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_server_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.get_server_from_opts(opts)
920             version=server.GetVersion()
921         for (k,v) in version.iteritems():
922             print "%-20s: %s"%(k,v)
923
924     # list instantiated slices
925     def slices(self, opts, args):
926         """
927         list instantiated slices
928         """
929         user_cred = self.get_user_cred().save_to_string(save_parents=True)
930         creds = [user_cred]
931         if opts.delegate:
932             delegated_cred = self.delegate_cred(user_cred, get_authority(self.authority))
933             creds.append(delegated_cred)  
934         server = self.get_server_from_opts(opts)
935         #results = server.ListSlices(creds, unique_call_id())
936         results = server.ListSlices(creds)
937         display_list(results)
938         return
939     
940     # show rspec for named slice
941     def resources(self, opts, args):
942         user_cred = self.get_user_cred().save_to_string(save_parents=True)
943         server = self.slicemgr
944         call_options = {}
945         server = self.get_server_from_opts(opts)
946         
947         if args:
948             cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
949             hrn = args[0]
950             call_options = {'geni_slice_urn': hrn_to_urn(hrn, 'slice')}
951         else:
952             cred = user_cred
953             hrn = None
954      
955         creds = [cred]
956         if opts.delegate:
957             delegated_cred = self.delegate_cred(cred, get_authority(self.authority))
958             creds.append(delegated_cred)
959         if opts.rspec_version:
960             version_manager = VersionManager()
961             server_version = self.get_cached_server_version(server)
962             if 'sfa' in server_version:
963                 # just request the version the client wants 
964                 call_options['rspec_version'] = version_manager.get_version(opts.rspec_version).to_dict()
965             else:
966                 # this must be a protogeni aggregate. We should request a v2 ad rspec
967                 # regardless of what the client user requested 
968                 call_options['rspec_version'] = version_manager.get_version('ProtoGENI 2').to_dict()     
969         #panos add info options
970         if opts.info:
971             call_options['info'] = opts.info 
972
973         call_args = [creds, call_options]
974         if self.server_supports_call_id_arg(server):
975             call_args.append(unique_call_id())
976         result = server.ListResources(*call_args)
977         if opts.file is None:
978             display_rspec(result, opts.format)
979         else:
980             save_rspec_to_file(result, opts.file)
981         return
982     
983     # created named slice with given rspec
984     def create(self, opts, args):
985         server = self.get_server_from_opts(opts)
986         server_version = self.get_cached_server_version(server)
987         slice_hrn = args[0]
988         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
989         user_cred = self.get_user_cred()
990         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
991         creds = [slice_cred]
992         if opts.delegate:
993             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
994             creds.append(delegated_cred)
995         rspec_file = self.get_rspec_file(args[1])
996         rspec = open(rspec_file).read()
997
998         # need to pass along user keys to the aggregate.  
999         # users = [
1000         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1001         #    keys: [<ssh key A>, <ssh key B>] 
1002         #  }]
1003         users = []
1004         slice_records = self.registry.Resolve(slice_urn, [user_cred.save_to_string(save_parents=True)])
1005         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1006             slice_record = slice_records[0]
1007             user_hrns = slice_record['researcher']
1008             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1009             user_records = self.registry.Resolve(user_urns, [user_cred.save_to_string(save_parents=True)])
1010             
1011             if 'sfa' not in server_version:
1012                 users = pg_users_arg(user_records)
1013                 rspec = RSpec(rspec)
1014                 rspec.filter({'component_manager_id': server_version['urn']})
1015                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1016             else:
1017                 users = sfa_users_arg(user_records, slice_record)
1018         call_args = [slice_urn, creds, rspec, users]
1019         if self.server_supports_call_id_arg(server):
1020             call_args.append(unique_call_id())
1021             
1022         result = server.CreateSliver(*call_args)
1023         if opts.file is None:
1024             print result
1025         else:
1026             save_rspec_to_file (result, opts.file)
1027         return result
1028
1029     # get a ticket for the specified slice
1030     def get_ticket(self, opts, args):
1031         slice_hrn, rspec_path = args[0], args[1]
1032         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1033         user_cred = self.get_user_cred()
1034         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1035         creds = [slice_cred]
1036         if opts.delegate:
1037             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1038             creds.append(delegated_cred)
1039         rspec_file = self.get_rspec_file(rspec_path) 
1040         rspec = open(rspec_file).read()
1041         server = self.get_server_from_opts(opts)
1042         ticket_string = server.GetTicket(slice_urn, creds, rspec, [])
1043         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1044         self.logger.info("writing ticket to %s"%file)
1045         ticket = SfaTicket(string=ticket_string)
1046         ticket.save_to_file(filename=file, save_parents=True)
1047
1048     def redeem_ticket(self, opts, args):
1049         ticket_file = args[0]
1050         
1051         # get slice hrn from the ticket
1052         # use this to get the right slice credential 
1053         ticket = SfaTicket(filename=ticket_file)
1054         ticket.decode()
1055         slice_hrn = ticket.gidObject.get_hrn()
1056         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1057         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1058         user_cred = self.get_user_cred()
1059         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1060         
1061         # get a list of node hostnames from the RSpec 
1062         tree = etree.parse(StringIO(ticket.rspec))
1063         root = tree.getroot()
1064         hostnames = root.xpath("./network/site/node/hostname/text()")
1065         
1066         # create an xmlrpc connection to the component manager at each of these
1067         # components and gall redeem_ticket
1068         connections = {}
1069         for hostname in hostnames:
1070             try:
1071                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1072                 server = self.get_server(hostname, CM_PORT, self.key_file, \
1073                                          self.cert_file, self.options.debug)
1074                 server.RedeemTicket(ticket.save_to_string(save_parents=True), slice_cred)
1075                 self.logger.info("Success")
1076             except socket.gaierror:
1077                 self.logger.error("redeem_ticket failed: Component Manager not accepting requests")
1078             except Exception, e:
1079                 self.logger.log_exc(e.message)
1080         return
1081  
1082     # delete named slice
1083     def delete(self, opts, args):
1084         slice_hrn = args[0]
1085         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1086         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1087         creds = [slice_cred]
1088         if opts.delegate:
1089             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1090             creds.append(delegated_cred)
1091         server = self.get_server_from_opts(opts)
1092
1093         call_args = [slice_urn, creds]
1094         if self.server_supports_call_id_arg(server):
1095             call_args.append(unique_call_id())
1096         return server.DeleteSliver(*call_args) 
1097   
1098     # start named slice
1099     def start(self, opts, args):
1100         slice_hrn = args[0]
1101         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1102         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1103         creds = [slice_cred]
1104         if opts.delegate:
1105             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1106             creds.append(delegated_cred)
1107         server = self.get_server_from_opts(opts)
1108         return server.Start(slice_urn, creds)
1109     
1110     # stop named slice
1111     def stop(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.get_server_from_opts(opts)
1120         return server.Stop(slice_urn, creds)
1121     
1122     # reset named slice
1123     def reset(self, opts, args):
1124         slice_hrn = args[0]
1125         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1126         server = self.get_server_from_opts(opts)
1127         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1128         creds = [slice_cred]
1129         if opts.delegate:
1130             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1131             creds.append(delegated_cred)
1132         return server.reset_slice(creds, slice_urn)
1133
1134     def renew(self, opts, args):
1135         slice_hrn = args[0]
1136         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1137         server = self.get_server_from_opts(opts)
1138         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1139         creds = [slice_cred]
1140         if opts.delegate:
1141             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1142             creds.append(delegated_cred)
1143         time = args[1]
1144         
1145         call_args = [slice_urn, creds, time]
1146         if self.server_supports_call_id_arg(server):
1147             call_args.append(unique_call_id())
1148         return server.RenewSliver(*call_args)
1149
1150
1151     def status(self, opts, args):
1152         slice_hrn = args[0]
1153         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1154         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1155         creds = [slice_cred]
1156         if opts.delegate:
1157             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1158             creds.append(delegated_cred)
1159         server = self.get_server_from_opts(opts)
1160         call_args = [slice_urn, creds]
1161         if self.server_supports_call_id_arg(server):
1162             call_args.append(unique_call_id())
1163         result = server.SliverStatus(*call_args)
1164         print result
1165         if opts.file:
1166             save_variable_to_file(result, opts.file, opts.fileformat)
1167
1168
1169     def shutdown(self, opts, args):
1170         slice_hrn = args[0]
1171         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1172         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1173         creds = [slice_cred]
1174         if opts.delegate:
1175             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1176             creds.append(delegated_cred)
1177         server = self.get_server_from_opts(opts)
1178         return server.Shutdown(slice_urn, creds)         
1179     
1180     def print_help (self):
1181         self.sfi_parser.print_help()
1182         self.cmd_parser.print_help()
1183
1184     #
1185     # Main: parse arguments and dispatch to command
1186     #
1187     def main(self):
1188         self.sfi_parser = self.create_parser()
1189         (options, args) = self.sfi_parser.parse_args()
1190         self.options = options
1191
1192         self.logger.setLevelFromOptVerbose(self.options.verbose)
1193         if options.hashrequest:
1194             self.hashrequest = True
1195  
1196         if len(args) <= 0:
1197             self.logger.critical("No command given. Use -h for help.")
1198             return -1
1199     
1200         command = args[0]
1201         self.cmd_parser = self.create_cmd_parser(command)
1202         (cmd_opts, cmd_args) = self.cmd_parser.parse_args(args[1:])
1203
1204         self.set_servers()
1205         self.logger.info("Command=%s" % command)
1206         if command in ("resources"):
1207             self.logger.debug("resources cmd_opts %s" % cmd_opts.format)
1208         elif command in ("list", "show", "remove"):
1209             self.logger.debug("cmd_opts.type %s" % cmd_opts.type)
1210         self.logger.debug('cmd_args %s' % cmd_args)
1211
1212         try:
1213             self.dispatch(command, cmd_opts, cmd_args)
1214         except KeyError:
1215             self.logger.critical ("Unknown command %s"%command)
1216             raise
1217             sys.exit(1)
1218     
1219         return
1220     
1221 if __name__ == "__main__":
1222     Sfi().main()