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