the -d option is used for locating the config file
[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.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.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        return self.registry.remove(auth_cred, opts.type, args[0])
471     
472     # add named registry record
473     def add(self,opts, args):
474        auth_cred = self.get_auth_cred()
475        rec_file = self.get_record_file(args[0])
476        record = self.load_record_from_file(rec_file)
477     
478        return self.registry.register(auth_cred, record)
479     
480     # update named registry entry
481     def update(self,opts, args):
482        user_cred = self.get_user_cred()
483        rec_file = self.get_record_file(args[0])
484        record = self.load_record_from_file(rec_file)
485        if record.get_type() == "user":
486            if record.get_name() == user_cred.get_gid_object().get_hrn():
487               cred = user_cred
488            else:
489               cred = self.get_auth_cred()
490        elif record.get_type() in ["slice"]:
491            try:
492                cred = self.get_slice_cred(record.get_name())
493            except ServerException, e:
494                # XXX smbaker -- once we have better error return codes, update this
495                # to do something better than a string compare
496                if "Permission error" in e.args[0]:
497                    cred = self.get_auth_cred()
498                else:
499                    raise
500        elif record.get_type() in ["authority"]:
501            cred = self.get_auth_cred()
502        elif record.get_type() == 'node':
503             cred = self.get_auth_cred()
504        else:
505            raise "unknown record type" + record.get_type()
506        return self.registry.update(cred, record)
507     
508     #
509     # Slice-related commands
510     #
511     
512     # list available nodes -- use 'resources' w/ no argument instead
513
514     # list instantiated slices
515     def slices(self,opts, args):
516        user_cred = self.get_user_cred()
517        results = self.slicemgr.get_slices(user_cred)
518        self.display_list(results)
519        return
520     
521     # show rspec for named slice
522     def resources(self,opts, args):
523        if args:
524            slice_cred = self.get_slice_cred(args[0])
525            result = self.slicemgr.get_resources(slice_cred, args[0])
526        else:
527            user_cred = self.get_user_cred()
528            result = self.slicemgr.get_resources(user_cred)
529        format = opts.format      
530        self.display_rspec(result, format)
531        if (opts.file is not None):
532           self.save_rspec_to_file(result, opts.file)
533        return
534     
535     # created named slice with given rspec
536     def create(self,opts, args):
537        slice_hrn = args[0]
538        slice_cred = self.get_slice_cred(slice_hrn)
539        rspec_file = self.get_rspec_file(args[1])
540        rspec=open(rspec_file).read()
541        return self.slicemgr.create_slice(slice_cred, slice_hrn, rspec)
542     
543     # delete named slice
544     def delete(self,opts, args):
545        slice_hrn = args[0]
546        slice_cred = self.get_slice_cred(slice_hrn)
547        
548        return self.slicemgr.delete_slice(slice_cred, slice_hrn)
549     
550     # start named slice
551     def start(self,opts, args):
552        slice_hrn = args[0]
553        slice_cred = self.get_slice_cred(args[0])
554        return self.slicemgr.start_slice(slice_cred, slice_hrn)
555     
556     # stop named slice
557     def stop(self,opts, args):
558        slice_hrn = args[0]
559        slice_cred = self.get_slice_cred(args[0])
560        return self.slicemgr.stop_slice(slice_cred, slice_hrn)
561     
562     # reset named slice
563     def reset(self,opts, args):
564        slice_hrn = args[0]
565        slice_cred = self.get_slice_cred(args[0])
566        return self.slicemgr.reset_slice(slice_cred, slice_hrn)
567     
568     #
569     #
570     # Display, Save, and Filter RSpecs and Records
571     #   - to be replace by EMF-generated routines
572     #
573     #
574     
575     def display_rspec(self,rspec, format = 'rspec'):
576         if format in ['dns']:
577             spec = Rspec()
578             spec.parseString(rspec)
579             hostnames = []
580             nodespecs = spec.getDictsByTagName('NodeSpec')
581             for nodespec in nodespecs:
582                 if nodespec.has_key('name') and nodespec['name']:
583                     if isinstance(nodespec['name'], ListType):
584                         hostnames.extend(nodespec['name'])
585                     elif isinstance(nodespec['name'], StringTypes):
586                         hostnames.append(nodespec['name'])
587             result = hostnames
588         elif format in ['ip']:
589             spec = Rspec()
590             spec.parseString(rspec)
591             ips = []
592             ifspecs = spec.getDictsByTagName('IfSpec')
593             for ifspec in ifspecs:
594                 if ifspec.has_key('addr') and ifspec['addr']:
595                     ips.append(ifspec['addr'])
596             result = ips 
597         else:     
598             result = rspec
599     
600         print result
601         return
602     
603     def display_list(self,results):
604         for result in results:
605             print result
606     
607     def save_rspec_to_file(self,rspec, filename):
608        if not filename.startswith(os.sep):
609            filename = self.sfi_dir + filename
610        if not filename.endswith(".rspec"):
611            filename = filename + ".rspec"
612     
613        f = open(filename, 'w')
614        f.write(rspec)
615        f.close()
616        return
617     
618     def display_records(self,recordList, dump = False):
619        ''' Print all fields in the record'''
620        for record in recordList:
621           self.display_record(record, dump)
622     
623     def display_record(self,record, dump = False):
624        if dump:
625            record.dump()
626        else:
627            info = record.getdict()
628            print "%s (%s)" % (info['hrn'], info['type'])
629        return
630     
631     def filter_records(self,type, records):
632        filtered_records = []
633        for record in records:
634            if (record.get_type() == type) or (type == "all"):
635                filtered_records.append(record)
636        return filtered_records
637     
638     def save_records_to_file(self,filename, recordList):
639        index = 0
640        for record in recordList:
641            if index>0:
642                self.save_record_to_file(filename + "." + str(index), record)
643            else:
644                self.save_record_to_file(filename, record)
645            index = index + 1
646     
647     def save_record_to_file(self,filename, record):
648        if not filename.startswith(os.sep):
649            filename = self.sfi_dir + filename
650        str = record.save_to_string()
651        file(filename, "w").write(str)
652        return
653     
654     def load_record_from_file(self,filename):
655        str = file(filename, "r").read()
656        record = GeniRecord(string=str)
657        return record
658     
659     #
660     # Main: parse arguments and dispatch to command
661     #
662     def main(self):
663     
664        parser = self.create_parser()
665        (options, args) = parser.parse_args()
666        self.options = options
667     
668        if len(args) <= 0:
669             print "No command given. Use -h for help."
670             return -1
671     
672        command = args[0]
673        (cmd_opts, cmd_args) = self.create_cmd_parser(command).parse_args(args[1:])
674        if self.options.verbose :
675           print "Registry %s, sm %s, dir %s, user %s, auth %s" % (options.registry,
676                                                                    options.sm,
677                                                                    options.sfi_dir,
678                                                                    options.user,
679                                                                    options.auth)
680           print "Command %s" %command
681           if command in ("resources"):
682              print "resources cmd_opts %s" %cmd_opts.format
683           elif command in ("list","show","remove"):
684              print "cmd_opts.type %s" %cmd_opts.type
685           print "cmd_args %s" %cmd_args
686     
687        self.set_servers()
688     
689        try:
690           self.dispatch(command, cmd_opts, cmd_args)
691        except KeyError:
692           raise 
693           print "Command not found:", command
694           sys.exit(1)
695     
696        return
697     
698 if __name__=="__main__":
699    Sfi().main()