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