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