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