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