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