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