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