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