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