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