added 2 new methods, 'get_aggregates' and 'get_registries'
[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                   "aggregates": "[name]",
280                   "registries": "[name]",   
281                   "slices": "",
282                   "resources": "[name]",
283                   "create": "name rspec",
284                   "delete": "name",
285                   "reset": "name",
286                   "start": "name",
287                   "stop": "name",
288                   "delegate": "name"
289                  }
290     
291        if additional_cmdargs:
292           cmdargs.update(additional_cmdargs)
293     
294        if command not in cmdargs:
295           print "Invalid command\n"
296           print "Commands: ",
297           for key in cmdargs.keys():
298               print key+",",
299           print ""
300           sys.exit(2)
301     
302        parser = OptionParser(usage="sfi [sfi_options] %s [options] %s" \
303           % (command, cmdargs[command]))
304
305        if command in ("resources"):
306            parser.add_option("-f", "--format", dest="format",type="choice",
307                              help="display format ([xml]|dns|ip)",default="xml",
308                              choices=("xml","dns","ip"))
309
310        if command in ("list", "show", "remove"):
311           parser.add_option("-t", "--type", dest="type",type="choice",
312                             help="type filter ([all]|user|slice|sa|ma|node|aggregate)",
313                             choices=("all","user","slice","sa","ma","node","aggregate"),
314                             default="all")
315
316        if command in ("resources", "show", "list"):
317           parser.add_option("-o", "--output", dest="file",
318                             help="output XML to file", metavar="FILE", default=None)
319
320        if command in ("show", "list"):
321            parser.add_option("-f", "--format", dest="format", type="choice", 
322                              help="display format ([text]|xml)",default="text", 
323                              choices=("text","xml")) 
324
325        if command in ("delegate"):
326           parser.add_option("-u", "--user",
327                             action="store_true", dest="delegate_user", default=False,
328                             help="delegate user credential")
329           parser.add_option("-s", "--slice", dest="delegate_slice",
330                             help="delegate slice credential", metavar="HRN", default=None)
331        return parser
332     
333     def create_parser(self):
334
335        # Generate command line parser
336        parser = OptionParser(usage="sfi [options] command [command_options] [command_args]",
337                              description="Commands: list,show,remove,add,update,nodes,slices,resources,create,delete,start,stop,reset")
338        parser.add_option("-r", "--registry", dest="registry",
339                          help="root registry", metavar="URL", default=None)
340        parser.add_option("-s", "--slicemgr", dest="sm",
341                          help="slice manager", metavar="URL", default=None)
342        default_sfi_dir=os.path.expanduser("~/.sfi/")
343        parser.add_option("-d", "--dir", dest="sfi_dir",
344                          help="config & working directory - default is " + default_sfi_dir,
345                          metavar="PATH", default = default_sfi_dir)
346        parser.add_option("-u", "--user", dest="user",
347                          help="user name", metavar="HRN", default=None)
348        parser.add_option("-a", "--auth", dest="auth",
349                          help="authority name", metavar="HRN", default=None)
350        parser.add_option("-v", "--verbose",
351                          action="store_true", dest="verbose", default=False,
352                          help="verbose mode")
353        parser.add_option("-p", "--protocol",
354                          dest="protocol", default="xmlrpc",
355                          help="RPC protocol (xmlrpc or soap)")
356        parser.disable_interspersed_args()
357     
358        return parser
359     
360     def dispatch(self,command, cmd_opts, cmd_args):
361        getattr(self,command)(cmd_opts, cmd_args)
362     
363     #
364     # Following functions implement the commands
365     #
366     # Registry-related commands
367     #
368     
369     # list entires in named authority registry
370     def list(self,opts, args):
371        user_cred = self.get_user_cred()
372        try:
373           list = self.registry.list(user_cred, args[0])
374        except IndexError:
375           raise Exception, "Not enough parameters for the 'list' command"
376           
377        # filter on person, slice, site, node, etc.  
378        # THis really should be in the self.filter_records funct def comment...
379        list = self.filter_records(opts.type, list)
380        for record in list:
381            print "%s (%s)" % (record['hrn'], record['type'])     
382        if opts.file:
383            self.save_records_to_file(opts.file, list)
384        return
385     
386     # show named registry record
387     def show(self,opts, args):
388        user_cred = self.get_user_cred()
389        records = self.registry.resolve(user_cred, args[0])
390        records = self.filter_records(opts.type, records)
391        if not records:
392           print "No record of type", opts.type
393        for record in records:
394            if record['type'] in ['user']:
395                record = UserRecord(dict = record)
396            elif record['type'] in ['slice']:
397                record = SliceRecord(dict = record)
398            elif record['type'] in ['node']:
399                record = NodeRecord(dict = record)
400            elif record['type'] in ['authority', 'ma', 'sa']:
401                record = AuthorityRecord(dict = record)
402            else:
403                record = GeniRecord(dict = record)
404            if (opts.format=="text"): 
405                record.dump()  
406            else: 
407                print record.save_to_string() 
408        
409        if opts.file:
410            self.save_records_to_file(opts.file, records)
411        return
412     
413     def delegate(self,opts, args):
414        user_cred = self.get_user_cred()
415        if opts.delegate_user:
416            object_cred = user_cred
417        elif opts.delegate_slice:
418            object_cred = self.get_slice_cred(opts.delegate_slice)
419        else:
420            print "Must specify either --user or --slice <hrn>"
421            return
422     
423        # the gid and hrn of the object we are delegating
424        object_gid = object_cred.get_gid_object()
425        object_hrn = object_gid.get_hrn()
426     
427        if not object_cred.get_delegate():
428            print "Error: Object credential", object_hrn, "does not have delegate bit set"
429            return
430     
431        records = self.registry.resolve(user_cred, args[0])
432        records = self.filter_records("user", records)
433     
434        if not records:
435            print "Error: Didn't find a user record for", args[0]
436            return
437     
438        # the gid of the user who will be delegated too
439        delegee_gid = records[0].get_gid_object()
440        delegee_hrn = delegee_gid.get_hrn()
441     
442        # the key and hrn of the user who will be delegating
443        user_key = Keypair(filename = self.get_key_file())
444        user_hrn = user_cred.get_gid_caller().get_hrn()
445     
446        dcred = Credential(subject=object_hrn + " delegated to " + delegee_hrn)
447        dcred.set_gid_caller(delegee_gid)
448        dcred.set_gid_object(object_gid)
449        dcred.set_privileges(object_cred.get_privileges())
450        dcred.set_delegate(True)
451        dcred.set_pubkey(object_gid.get_pubkey())
452        dcred.set_issuer(user_key, user_hrn)
453        dcred.set_parent(object_cred)
454        dcred.encode()
455        dcred.sign()
456     
457        if opts.delegate_user:
458            dest_fn = os.path.join(self.options.sfi_dir, self.get_leaf(delegee_hrn) + "_" 
459                                   + self.get_leaf(object_hrn) + ".cred")
460        elif opts.delegate_slice:
461            dest_fn = os.path_join(self.options.sfi_dir, self.get_leaf(delegee_hrn) + "_slice_" 
462                                   + self.get_leaf(object_hrn) + ".cred")
463     
464        dcred.save_to_file(dest_fn, save_parents = True)
465     
466        print "delegated credential for", object_hrn, "to", delegee_hrn, "and wrote to", dest_fn
467     
468     # removed named registry record
469     #   - have to first retrieve the record to be removed
470     def remove(self,opts, args):
471        auth_cred = self.get_auth_cred()
472        type = opts.type 
473        if type in ['all']:
474            type = '*'                   
475        return self.registry.remove(auth_cred, type, args[0])
476     
477     # add named registry record
478     def add(self,opts, args):
479        auth_cred = self.get_auth_cred()
480        rec_file = self.get_record_file(args[0])
481        record = self.load_record_from_file(rec_file)
482     
483        return self.registry.register(auth_cred, record)
484     
485     # update named registry entry
486     def update(self,opts, args):
487        user_cred = self.get_user_cred()
488        rec_file = self.get_record_file(args[0])
489        record = self.load_record_from_file(rec_file)
490        if record.get_type() == "user":
491            if record.get_name() == user_cred.get_gid_object().get_hrn():
492               cred = user_cred
493            else:
494               cred = self.get_auth_cred()
495        elif record.get_type() in ["slice"]:
496            try:
497                cred = self.get_slice_cred(record.get_name())
498            except ServerException, e:
499                # XXX smbaker -- once we have better error return codes, update this
500                # to do something better than a string compare
501                if "Permission error" in e.args[0]:
502                    cred = self.get_auth_cred()
503                else:
504                    raise
505        elif record.get_type() in ["authority"]:
506            cred = self.get_auth_cred()
507        elif record.get_type() == 'node':
508             cred = self.get_auth_cred()
509        else:
510            raise "unknown record type" + record.get_type()
511        return self.registry.update(cred, record)
512    
513     
514     def aggregates(self, opts, args):
515         user_cred = self.get_user_cred()
516         hrn = None
517         if args: 
518             hrn = args[0]
519         
520         result = self.registry.get_aggregates(user_cred, hrn)
521         self.display_list(result)
522         return 
523
524     def registries(self, opts, args):
525         user_cred = self.get_user_cred()
526         hrn = None
527         if args:
528             hrn = args[0]
529         
530         result = self.registry.get_registries(user_cred, hrn)
531         self.display_list(result)
532         return
533  
534     #
535     # Slice-related commands
536     #
537     
538     # list available nodes -- use 'resources' w/ no argument instead
539
540     # list instantiated slices
541     def slices(self,opts, args):
542        user_cred = self.get_user_cred()
543        results = self.slicemgr.get_slices(user_cred)
544        self.display_list(results)
545        return
546     
547     # show rspec for named slice
548     def resources(self,opts, args):
549        if args:
550            slice_cred = self.get_slice_cred(args[0])
551            result = self.slicemgr.get_resources(slice_cred, args[0])
552        else:
553            user_cred = self.get_user_cred()
554            result = self.slicemgr.get_resources(user_cred)
555        format = opts.format      
556        self.display_rspec(result, format)
557        if (opts.file is not None):
558           self.save_rspec_to_file(result, opts.file)
559        return
560     
561     # created named slice with given rspec
562     def create(self,opts, args):
563        slice_hrn = args[0]
564        slice_cred = self.get_slice_cred(slice_hrn)
565        rspec_file = self.get_rspec_file(args[1])
566        rspec=open(rspec_file).read()
567        return self.slicemgr.create_slice(slice_cred, slice_hrn, rspec)
568     
569     # delete named slice
570     def delete(self,opts, args):
571        slice_hrn = args[0]
572        slice_cred = self.get_slice_cred(slice_hrn)
573        
574        return self.slicemgr.delete_slice(slice_cred, slice_hrn)
575     
576     # start named slice
577     def start(self,opts, args):
578        slice_hrn = args[0]
579        slice_cred = self.get_slice_cred(args[0])
580        return self.slicemgr.start_slice(slice_cred, slice_hrn)
581     
582     # stop named slice
583     def stop(self,opts, args):
584        slice_hrn = args[0]
585        slice_cred = self.get_slice_cred(args[0])
586        return self.slicemgr.stop_slice(slice_cred, slice_hrn)
587     
588     # reset named slice
589     def reset(self,opts, args):
590        slice_hrn = args[0]
591        slice_cred = self.get_slice_cred(args[0])
592        return self.slicemgr.reset_slice(slice_cred, slice_hrn)
593     
594     #
595     #
596     # Display, Save, and Filter RSpecs and Records
597     #   - to be replace by EMF-generated routines
598     #
599     #
600     
601     def display_rspec(self,rspec, format = 'rspec'):
602         if format in ['dns']:
603             spec = Rspec()
604             spec.parseString(rspec)
605             hostnames = []
606             nodespecs = spec.getDictsByTagName('NodeSpec')
607             for nodespec in nodespecs:
608                 if nodespec.has_key('name') and nodespec['name']:
609                     if isinstance(nodespec['name'], ListType):
610                         hostnames.extend(nodespec['name'])
611                     elif isinstance(nodespec['name'], StringTypes):
612                         hostnames.append(nodespec['name'])
613             result = hostnames
614         elif format in ['ip']:
615             spec = Rspec()
616             spec.parseString(rspec)
617             ips = []
618             ifspecs = spec.getDictsByTagName('IfSpec')
619             for ifspec in ifspecs:
620                 if ifspec.has_key('addr') and ifspec['addr']:
621                     ips.append(ifspec['addr'])
622             result = ips 
623         else:     
624             result = rspec
625     
626         print result
627         return
628     
629     def display_list(self,results):
630         for result in results:
631             print result
632     
633     def save_rspec_to_file(self,rspec, filename):
634        if not filename.startswith(os.sep):
635            filename = self.options.sfi_dir + filename
636        if not filename.endswith(".rspec"):
637            filename = filename + ".rspec"
638     
639        f = open(filename, 'w')
640        f.write(rspec)
641        f.close()
642        return
643     
644     def display_records(self,recordList, dump = False):
645        ''' Print all fields in the record'''
646        for record in recordList:
647           self.display_record(record, dump)
648     
649     def display_record(self,record, dump = False):
650        if dump:
651            record.dump()
652        else:
653            info = record.getdict()
654            print "%s (%s)" % (info['hrn'], info['type'])
655        return
656     
657     def filter_records(self,type, records):
658        filtered_records = []
659        for record in records:
660            if (record.get_type() == type) or (type == "all"):
661                filtered_records.append(record)
662        return filtered_records
663     
664     def save_records_to_file(self,filename, recordList):
665        index = 0
666        for record in recordList:
667            if index>0:
668                self.save_record_to_file(filename + "." + str(index), record)
669            else:
670                self.save_record_to_file(filename, record)
671            index = index + 1
672     
673     def save_record_to_file(self,filename, record):
674        if not filename.startswith(os.sep):
675            filename = self.options.sfi_dir + filename
676        str = record.save_to_string()
677        file(filename, "w").write(str)
678        return
679     
680     def load_record_from_file(self,filename):
681        str = file(filename, "r").read()
682        record = GeniRecord(string=str)
683        return record
684     
685     #
686     # Main: parse arguments and dispatch to command
687     #
688     def main(self):
689     
690        parser = self.create_parser()
691        (options, args) = parser.parse_args()
692        self.options = options
693     
694        if len(args) <= 0:
695             print "No command given. Use -h for help."
696             return -1
697     
698        command = args[0]
699        (cmd_opts, cmd_args) = self.create_cmd_parser(command).parse_args(args[1:])
700        if self.options.verbose :
701           print "Registry %s, sm %s, dir %s, user %s, auth %s" % (options.registry,
702                                                                    options.sm,
703                                                                    options.sfi_dir,
704                                                                    options.user,
705                                                                    options.auth)
706           print "Command %s" %command
707           if command in ("resources"):
708              print "resources cmd_opts %s" %cmd_opts.format
709           elif command in ("list","show","remove"):
710              print "cmd_opts.type %s" %cmd_opts.type
711           print "cmd_args %s" %cmd_args
712     
713        self.set_servers()
714     
715        try:
716           self.dispatch(command, cmd_opts, cmd_args)
717        except KeyError:
718           raise 
719           print "Command not found:", command
720           sys.exit(1)
721     
722        return
723     
724 if __name__=="__main__":
725    Sfi().main()