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