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