fix bug in remove
[sfa.git] / sfa / client / sfi.py
1 #! /usr/bin/env python
2
3 # sfi -- slice-based facility interface
4
5 import sys
6 import os, os.path
7 import tempfile
8 import traceback
9 from types import StringTypes, ListType
10 from optparse import OptionParser
11
12 from sfa.trust.certificate import Keypair, Certificate
13 from sfa.trust.credential import Credential
14
15 from sfa.util.geniclient import GeniClient
16 from sfa.util.record import *
17 from sfa.util.rspec import Rspec
18 from sfa.util.xmlrpcprotocol import ServerException
19 from sfa.util.config import Config
20
21 class Sfi:
22     
23     slicemgr = None
24     registry = None
25     user = None
26     authority = None
27     options = None
28     
29     #
30     # Establish Connection to SliceMgr and Registry Servers
31     #
32     def set_servers(self):
33        config_file = self.options.sfi_dir + os.sep + "sfi_config"
34        try:
35           config = Config (config_file)
36        except:
37           print "Failed to read configuration file",config_file
38           print "Make sure to remove the export clauses and to add quotes"
39           if not self.options.verbose:
40              print "Re-run with -v for more details"
41           else:
42              traceback.print_exc()
43           sys.exit(1)
44     
45        errors=0
46        # Set SliceMgr URL
47        if (self.options.sm is not None):
48           sm_url = self.options.sm
49        elif hasattr(config,"SFI_SM"):
50           sm_url = config.SFI_SM
51        else:
52           print "You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s"%config_file
53           errors +=1 
54     
55        # Set Registry URL
56        if (self.options.registry is not None):
57           reg_url = self.options.registry
58        elif hasattr(config,"SFI_REGISTRY"):
59           reg_url = config.SFI_REGISTRY
60        else:
61           print "You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s"%config_file
62           errors +=1 
63     
64        # Set user HRN
65        if (self.options.user is not None):
66           self.user = self.options.user
67        elif hasattr(config,"SFI_USER"):
68           self.user = config.SFI_USER
69        else:
70           print "You need to set e.g. SFI_USER='plc.princeton.username' in %s"%config_file
71           errors +=1 
72     
73        # Set authority HRN
74        if (self.options.auth is not None):
75           self.authority = self.options.auth
76        elif hasattr(config,"SFI_AUTH"):
77           self.authority = config.SFI_AUTH
78        else:
79           print "You need to set e.g. SFI_AUTH='plc.princeton' in %s"%config_file
80           errors +=1 
81     
82        if errors:
83           sys.exit(1)
84     
85        if self.options.verbose :
86           print "Contacting Slice Manager at:", sm_url
87           print "Contacting Registry at:", reg_url
88     
89        # Get key and certificate
90        key_file = self.get_key_file()
91        cert_file = self.get_cert_file(key_file)
92     
93        # Establish connection to server(s)
94        self.slicemgr = GeniClient(sm_url, key_file, cert_file, self.options.protocol)
95        self.registry = GeniClient(reg_url, key_file, cert_file, self.options.protocol)
96        return
97     
98     #
99     # Get various credential and spec files
100     #
101     # Establishes limiting conventions
102     #   - conflates MAs and SAs
103     #   - assumes last token in slice name is unique
104     #
105     # Bootstraps credentials
106     #   - bootstrap user credential from self-signed certificate
107     #   - bootstrap authority credential from user credential
108     #   - bootstrap slice credential from user credential
109     #
110     
111     def get_leaf(self,name):
112        parts = name.split(".")
113        return parts[-1]
114     
115     def get_key_file(self):
116        file = os.path.join(self.options.sfi_dir, self.get_leaf(self.user) + ".pkey")
117        if (os.path.isfile(file)):
118           return file
119        else:
120           print "Key file", file, "does not exist"
121           sys.exit(-1)
122        return
123     
124     def get_cert_file(self,key_file):
125     
126        file = os.path.join(self.options.sfi_dir, self.get_leaf(self.user) + ".cert")
127        if (os.path.isfile(file)):
128           return file
129        else:
130           k = Keypair(filename = key_file)
131           cert = Certificate(subject=self.user)
132           cert.set_pubkey(k)
133           cert.set_issuer(k, self.user)
134           cert.sign()
135           if self.options.verbose :
136              print "Writing self-signed certificate to", file
137           cert.save_to_file(file)
138           return file
139     
140     def get_user_cred(self):
141        file = os.path.join(self.options.sfi_dir, self.get_leaf(self.user) + ".cred")
142        if (os.path.isfile(file)):
143           user_cred = Credential(filename=file)
144           return user_cred
145        else:
146           # bootstrap user credential
147           user_cred = self.registry.get_credential(None, "user", self.user)
148           if user_cred:
149              user_cred.save_to_file(file, save_parents=True)
150              if self.options.verbose:
151                 print "Writing user credential to", file
152              return user_cred
153           else:
154              print "Failed to get user credential"
155              sys.exit(-1)
156     
157     def get_auth_cred(self):
158     
159        if not self.authority:
160           print "no authority specified. Use -a or set SF_AUTH"
161           sys.exit(-1)
162     
163        file = os.path.join(self.options.sfi_dir, self.get_leaf("authority") +".cred")
164        if (os.path.isfile(file)):
165           auth_cred = Credential(filename=file)
166           return auth_cred
167        else:
168           # bootstrap authority credential from user credential
169           user_cred = self.get_user_cred()
170           auth_cred = self.registry.get_credential(user_cred, "authority", self.authority)
171           if auth_cred:
172              auth_cred.save_to_file(file, save_parents=True)
173              if self.options.verbose:
174                 print "Writing authority credential to", file
175              return auth_cred
176           else:
177              print "Failed to get authority credential"
178              sys.exit(-1)
179     
180     def get_slice_cred(self,name):
181        file = os.path.join(self.options.sfi_dir, "slice_" + self.get_leaf(name) + ".cred")
182        if (os.path.isfile(file)):
183           slice_cred = Credential(filename=file)
184           return slice_cred
185        else:
186           # bootstrap slice credential from user credential
187           user_cred = self.get_user_cred()
188           slice_cred = self.registry.get_credential(user_cred, "slice", name)
189           if slice_cred:
190              slice_cred.save_to_file(file, save_parents=True)
191              if self.options.verbose:
192                 print "Writing slice credential to", file
193              return slice_cred
194           else:
195              print "Failed to get slice credential"
196              sys.exit(-1)
197     
198     def delegate_cred(self,cred, hrn, type = 'authority'):
199         # the gid and hrn of the object we are delegating
200         object_gid = cred.get_gid_object()
201         object_hrn = object_gid.get_hrn()
202         cred.set_delegate(True)
203         if not cred.get_delegate():
204             raise Exception, "Error: Object credential %(object_hrn)s does not have delegate bit set" % locals()
205            
206     
207         records = self.registry.resolve(cred, hrn)
208         records = self.filter_records(type, records)
209         
210         if not records:
211             raise Exception, "Error: Didn't find a %(type)s record for %(hrn)s" % locals()
212     
213         # the gid of the user who will be delegated too
214         delegee_gid = records[0].get_gid_object()
215         delegee_hrn = delegee_gid.get_hrn()
216         
217         # the key and hrn of the user who will be delegating
218         user_key = Keypair(filename = self.get_key_file())
219         user_hrn = cred.get_gid_caller().get_hrn()
220     
221         dcred = Credential(subject=object_hrn + " delegated to " + delegee_hrn)
222         dcred.set_gid_caller(delegee_gid)
223         dcred.set_gid_object(object_gid)
224         dcred.set_privileges(cred.get_privileges())
225         dcred.set_delegate(True)
226         dcred.set_pubkey(object_gid.get_pubkey())
227         dcred.set_issuer(user_key, user_hrn)
228         dcred.set_parent(cred)
229         dcred.encode()
230         dcred.sign()
231     
232         return dcred
233     
234     def get_rspec_file(self,rspec):
235        if (os.path.isabs(rspec)):
236           file = rspec
237        else:
238           file = os.path.join(self.options.sfi_dir, rspec)
239        if (os.path.isfile(file)):
240           return file
241        else:
242           print "No such rspec file", rspec
243           sys.exit(1)
244     
245     def get_record_file(self,record):
246        if (os.path.isabs(record)):
247           file = record
248        else:
249           file = os.path.join(self.options.sfi_dir, record)
250        if (os.path.isfile(file)):
251           return file
252        else:
253           print "No such registry record file", record
254           sys.exit(1)
255     
256     def load_publickey_string(self,fn):
257        f = file(fn,"r")
258        key_string = f.read()
259     
260        # if the filename is a private key file, then extract the public key
261        if "PRIVATE KEY" in key_string:
262            outfn = tempfile.mktemp()
263            cmd = "openssl rsa -in " + fn + " -pubout -outform PEM -out " + outfn
264            os.system(cmd)
265            f = file(outfn, "r")
266            key_string = f.read()
267            os.remove(outfn)
268     
269        return key_string
270     #
271     # Generate sub-command parser
272     #
273     def create_cmd_parser(self,command, additional_cmdargs = None):
274        cmdargs = {"list": "name",
275                   "show": "name",
276                   "remove": "name",
277                   "add": "record",
278                   "update": "record",
279                   "slices": "",
280                   "resources": "[name]",
281                   "create": "name rspec",
282                   "delete": "name",
283                   "reset": "name",
284                   "start": "name",
285                   "stop": "name",
286                   "delegate": "name"
287                  }
288     
289        if additional_cmdargs:
290           cmdargs.update(additional_cmdargs)
291     
292        if command not in cmdargs:
293           print "Invalid command\n"
294           print "Commands: ",
295           for key in cmdargs.keys():
296               print key+",",
297           print ""
298           sys.exit(2)
299     
300        parser = OptionParser(usage="sfi [sfi_options] %s [options] %s" \
301           % (command, cmdargs[command]))
302
303        if command in ("resources"):
304            parser.add_option("-f", "--format", dest="format",type="choice",
305                              help="display format ([xml]|dns|ip)",default="xml",
306                              choices=("xml","dns","ip"))
307
308        if command in ("list", "show", "remove"):
309           parser.add_option("-t", "--type", dest="type",type="choice",
310                             help="type filter ([all]|user|slice|sa|ma|node|aggregate)",
311                             choices=("all","user","slice","sa","ma","node","aggregate"),
312                             default="all")
313
314        if command in ("resources", "show", "list"):
315           parser.add_option("-o", "--output", dest="file",
316                             help="output XML to file", metavar="FILE", default=None)
317
318        if command in ("show", "list"):
319            parser.add_option("-f", "--format", dest="format", type="choice", 
320                              help="display format ([text]|xml)",default="text", 
321                              choices=("text","xml")) 
322
323        if command in ("delegate"):
324           parser.add_option("-u", "--user",
325                             action="store_true", dest="delegate_user", default=False,
326                             help="delegate user credential")
327           parser.add_option("-s", "--slice", dest="delegate_slice",
328                             help="delegate slice credential", metavar="HRN", default=None)
329        return parser
330     
331     def create_parser(self):
332
333        # Generate command line parser
334        parser = OptionParser(usage="sfi [options] command [command_options] [command_args]",
335                              description="Commands: list,show,remove,add,update,nodes,slices,resources,create,delete,start,stop,reset")
336        parser.add_option("-r", "--registry", dest="registry",
337                          help="root registry", metavar="URL", default=None)
338        parser.add_option("-s", "--slicemgr", dest="sm",
339                          help="slice manager", metavar="URL", default=None)
340        default_sfi_dir=os.path.expanduser("~/.sfi/")
341        parser.add_option("-d", "--dir", dest="sfi_dir",
342                          help="config & working directory - default is " + default_sfi_dir,
343                          metavar="PATH", default = default_sfi_dir)
344        parser.add_option("-u", "--user", dest="user",
345                          help="user name", metavar="HRN", default=None)
346        parser.add_option("-a", "--auth", dest="auth",
347                          help="authority name", metavar="HRN", default=None)
348        parser.add_option("-v", "--verbose",
349                          action="store_true", dest="verbose", default=False,
350                          help="verbose mode")
351        parser.add_option("-p", "--protocol",
352                          dest="protocol", default="xmlrpc",
353                          help="RPC protocol (xmlrpc or soap)")
354        parser.disable_interspersed_args()
355     
356        return parser
357     
358     def dispatch(self,command, cmd_opts, cmd_args):
359        getattr(self,command)(cmd_opts, cmd_args)
360     
361     #
362     # Following functions implement the commands
363     #
364     # Registry-related commands
365     #
366     
367     # list entires in named authority registry
368     def list(self,opts, args):
369        user_cred = self.get_user_cred()
370        try:
371           list = self.registry.list(user_cred, args[0])
372        except IndexError:
373           raise Exception, "Not enough parameters for the 'list' command"
374           
375        # filter on person, slice, site, node, etc.  
376        # THis really should be in the self.filter_records funct def comment...
377        list = self.filter_records(opts.type, list)
378        for record in list:
379            print "%s (%s)" % (record['hrn'], record['type'])     
380        if opts.file:
381            self.save_records_to_file(opts.file, list)
382        return
383     
384     # show named registry record
385     def show(self,opts, args):
386        user_cred = self.get_user_cred()
387        records = self.registry.resolve(user_cred, args[0])
388        records = self.filter_records(opts.type, records)
389        if not records:
390           print "No record of type", opts.type
391        for record in records:
392            if record['type'] in ['user']:
393                record = UserRecord(dict = record)
394            elif record['type'] in ['slice']:
395                record = SliceRecord(dict = record)
396            elif record['type'] in ['node']:
397                record = NodeRecord(dict = record)
398            elif record['type'] in ['authority', 'ma', 'sa']:
399                record = AuthorityRecord(dict = record)
400            else:
401                record = GeniRecord(dict = record)
402            if (opts.format=="text"): 
403                record.dump()  
404            else: 
405                print record.save_to_string() 
406        
407        if opts.file:
408            self.save_records_to_file(opts.file, records)
409        return
410     
411     def delegate(self,opts, args):
412        user_cred = self.get_user_cred()
413        if opts.delegate_user:
414            object_cred = user_cred
415        elif opts.delegate_slice:
416            object_cred = self.get_slice_cred(opts.delegate_slice)
417        else:
418            print "Must specify either --user or --slice <hrn>"
419            return
420     
421        # the gid and hrn of the object we are delegating
422        object_gid = object_cred.get_gid_object()
423        object_hrn = object_gid.get_hrn()
424     
425        if not object_cred.get_delegate():
426            print "Error: Object credential", object_hrn, "does not have delegate bit set"
427            return
428     
429        records = self.registry.resolve(user_cred, args[0])
430        records = self.filter_records("user", records)
431     
432        if not records:
433            print "Error: Didn't find a user record for", args[0]
434            return
435     
436        # the gid of the user who will be delegated too
437        delegee_gid = records[0].get_gid_object()
438        delegee_hrn = delegee_gid.get_hrn()
439     
440        # the key and hrn of the user who will be delegating
441        user_key = Keypair(filename = self.get_key_file())
442        user_hrn = user_cred.get_gid_caller().get_hrn()
443     
444        dcred = Credential(subject=object_hrn + " delegated to " + delegee_hrn)
445        dcred.set_gid_caller(delegee_gid)
446        dcred.set_gid_object(object_gid)
447        dcred.set_privileges(object_cred.get_privileges())
448        dcred.set_delegate(True)
449        dcred.set_pubkey(object_gid.get_pubkey())
450        dcred.set_issuer(user_key, user_hrn)
451        dcred.set_parent(object_cred)
452        dcred.encode()
453        dcred.sign()
454     
455        if opts.delegate_user:
456            dest_fn = os.path.join(self.options.sfi_dir, self.get_leaf(delegee_hrn) + "_" 
457                                   + self.get_leaf(object_hrn) + ".cred")
458        elif opts.delegate_slice:
459            dest_fn = os.path_join(self.options.sfi_dir, self.get_leaf(delegee_hrn) + "_slice_" 
460                                   + self.get_leaf(object_hrn) + ".cred")
461     
462        dcred.save_to_file(dest_fn, save_parents = True)
463     
464        print "delegated credential for", object_hrn, "to", delegee_hrn, "and wrote to", dest_fn
465     
466     # removed named registry record
467     #   - have to first retrieve the record to be removed
468     def remove(self,opts, args):
469        auth_cred = self.get_auth_cred()
470        type = opts.type 
471        if type in ['all']:
472            type = '*'                   
473        return self.registry.remove(auth_cred, type, args[0])
474     
475     # add named registry record
476     def add(self,opts, args):
477        auth_cred = self.get_auth_cred()
478        rec_file = self.get_record_file(args[0])
479        record = self.load_record_from_file(rec_file)
480     
481        return self.registry.register(auth_cred, record)
482     
483     # update named registry entry
484     def update(self,opts, args):
485        user_cred = self.get_user_cred()
486        rec_file = self.get_record_file(args[0])
487        record = self.load_record_from_file(rec_file)
488        if record.get_type() == "user":
489            if record.get_name() == user_cred.get_gid_object().get_hrn():
490               cred = user_cred
491            else:
492               cred = self.get_auth_cred()
493        elif record.get_type() in ["slice"]:
494            try:
495                cred = self.get_slice_cred(record.get_name())
496            except ServerException, e:
497                # XXX smbaker -- once we have better error return codes, update this
498                # to do something better than a string compare
499                if "Permission error" in e.args[0]:
500                    cred = self.get_auth_cred()
501                else:
502                    raise
503        elif record.get_type() in ["authority"]:
504            cred = self.get_auth_cred()
505        elif record.get_type() == 'node':
506             cred = self.get_auth_cred()
507        else:
508            raise "unknown record type" + record.get_type()
509        return self.registry.update(cred, record)
510     
511     #
512     # Slice-related commands
513     #
514     
515     # list available nodes -- use 'resources' w/ no argument instead
516
517     # list instantiated slices
518     def slices(self,opts, args):
519        user_cred = self.get_user_cred()
520        results = self.slicemgr.get_slices(user_cred)
521        self.display_list(results)
522        return
523     
524     # show rspec for named slice
525     def resources(self,opts, args):
526        if args:
527            slice_cred = self.get_slice_cred(args[0])
528            result = self.slicemgr.get_resources(slice_cred, args[0])
529        else:
530            user_cred = self.get_user_cred()
531            result = self.slicemgr.get_resources(user_cred)
532        format = opts.format      
533        self.display_rspec(result, format)
534        if (opts.file is not None):
535           self.save_rspec_to_file(result, opts.file)
536        return
537     
538     # created named slice with given rspec
539     def create(self,opts, args):
540        slice_hrn = args[0]
541        slice_cred = self.get_slice_cred(slice_hrn)
542        rspec_file = self.get_rspec_file(args[1])
543        rspec=open(rspec_file).read()
544        return self.slicemgr.create_slice(slice_cred, slice_hrn, rspec)
545     
546     # delete named slice
547     def delete(self,opts, args):
548        slice_hrn = args[0]
549        slice_cred = self.get_slice_cred(slice_hrn)
550        
551        return self.slicemgr.delete_slice(slice_cred, slice_hrn)
552     
553     # start named slice
554     def start(self,opts, args):
555        slice_hrn = args[0]
556        slice_cred = self.get_slice_cred(args[0])
557        return self.slicemgr.start_slice(slice_cred, slice_hrn)
558     
559     # stop named slice
560     def stop(self,opts, args):
561        slice_hrn = args[0]
562        slice_cred = self.get_slice_cred(args[0])
563        return self.slicemgr.stop_slice(slice_cred, slice_hrn)
564     
565     # reset named slice
566     def reset(self,opts, args):
567        slice_hrn = args[0]
568        slice_cred = self.get_slice_cred(args[0])
569        return self.slicemgr.reset_slice(slice_cred, slice_hrn)
570     
571     #
572     #
573     # Display, Save, and Filter RSpecs and Records
574     #   - to be replace by EMF-generated routines
575     #
576     #
577     
578     def display_rspec(self,rspec, format = 'rspec'):
579         if format in ['dns']:
580             spec = Rspec()
581             spec.parseString(rspec)
582             hostnames = []
583             nodespecs = spec.getDictsByTagName('NodeSpec')
584             for nodespec in nodespecs:
585                 if nodespec.has_key('name') and nodespec['name']:
586                     if isinstance(nodespec['name'], ListType):
587                         hostnames.extend(nodespec['name'])
588                     elif isinstance(nodespec['name'], StringTypes):
589                         hostnames.append(nodespec['name'])
590             result = hostnames
591         elif format in ['ip']:
592             spec = Rspec()
593             spec.parseString(rspec)
594             ips = []
595             ifspecs = spec.getDictsByTagName('IfSpec')
596             for ifspec in ifspecs:
597                 if ifspec.has_key('addr') and ifspec['addr']:
598                     ips.append(ifspec['addr'])
599             result = ips 
600         else:     
601             result = rspec
602     
603         print result
604         return
605     
606     def display_list(self,results):
607         for result in results:
608             print result
609     
610     def save_rspec_to_file(self,rspec, filename):
611        if not filename.startswith(os.sep):
612            filename = self.options.sfi_dir + filename
613        if not filename.endswith(".rspec"):
614            filename = filename + ".rspec"
615     
616        f = open(filename, 'w')
617        f.write(rspec)
618        f.close()
619        return
620     
621     def display_records(self,recordList, dump = False):
622        ''' Print all fields in the record'''
623        for record in recordList:
624           self.display_record(record, dump)
625     
626     def display_record(self,record, dump = False):
627        if dump:
628            record.dump()
629        else:
630            info = record.getdict()
631            print "%s (%s)" % (info['hrn'], info['type'])
632        return
633     
634     def filter_records(self,type, records):
635        filtered_records = []
636        for record in records:
637            if (record.get_type() == type) or (type == "all"):
638                filtered_records.append(record)
639        return filtered_records
640     
641     def save_records_to_file(self,filename, recordList):
642        index = 0
643        for record in recordList:
644            if index>0:
645                self.save_record_to_file(filename + "." + str(index), record)
646            else:
647                self.save_record_to_file(filename, record)
648            index = index + 1
649     
650     def save_record_to_file(self,filename, record):
651        if not filename.startswith(os.sep):
652            filename = self.options.sfi_dir + filename
653        str = record.save_to_string()
654        file(filename, "w").write(str)
655        return
656     
657     def load_record_from_file(self,filename):
658        str = file(filename, "r").read()
659        record = GeniRecord(string=str)
660        return record
661     
662     #
663     # Main: parse arguments and dispatch to command
664     #
665     def main(self):
666     
667        parser = self.create_parser()
668        (options, args) = parser.parse_args()
669        self.options = options
670     
671        if len(args) <= 0:
672             print "No command given. Use -h for help."
673             return -1
674     
675        command = args[0]
676        (cmd_opts, cmd_args) = self.create_cmd_parser(command).parse_args(args[1:])
677        if self.options.verbose :
678           print "Registry %s, sm %s, dir %s, user %s, auth %s" % (options.registry,
679                                                                    options.sm,
680                                                                    options.sfi_dir,
681                                                                    options.user,
682                                                                    options.auth)
683           print "Command %s" %command
684           if command in ("resources"):
685              print "resources cmd_opts %s" %cmd_opts.format
686           elif command in ("list","show","remove"):
687              print "cmd_opts.type %s" %cmd_opts.type
688           print "cmd_args %s" %cmd_args
689     
690        self.set_servers()
691     
692        try:
693           self.dispatch(command, cmd_opts, cmd_args)
694        except KeyError:
695           raise 
696           print "Command not found:", command
697           sys.exit(1)
698     
699        return
700     
701 if __name__=="__main__":
702    Sfi().main()