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