dirty...dirty..dirty hack to get rid of r2q.
[util-vserver-pl.git] / python / bwlimit.py
1 #!/usr/bin/python
2
3 # Bandwidth limit module for PlanetLab nodes. The intent is to use the
4 # Hierarchical Token Bucket (HTB) queueing discipline (qdisc) to allow
5 # slices to fairly share access to available node bandwidth. We
6 # currently define three classes of "available node bandwidth":
7 #
8 # 1. Available hardware bandwidth (bwmax): The maximum rate of the
9 # hardware.
10 #
11 # 2. Available capped bandwidth (bwcap): The maximum rate allowed to
12 # non-exempt destinations. By default, equal to bwmax, but may be
13 # lowered by PIs.
14 #
15 # 3. Available uncapped ("exempt") bandwidth: The difference between
16 # bwmax and what is currently being used of bwcap, or the maximum rate
17 # allowed to destinations exempt from caps (e.g., Internet2).
18 #
19 # All three classes of bandwidth are fairly shared according to the
20 # notion of "shares". For instance, if the node is capped at 5 Mbps,
21 # there are N slices, and each slice has 1 share, then each slice
22 # should get at least 5/N Mbps of bandwidth. How HTB is implemented
23 # makes this statement a little too simplistic. What it really means
24 # is that during any single time period, only a certain number of
25 # bytes can be sent onto the wire. Each slice is guaranteed that at
26 # least some small number of its bytes will be sent. Whatever is left
27 # over from the budget, is split in proportion to the number of shares
28 # each slice has.
29 #
30 # Even if the node is not capped at a particular limit (bwcap ==
31 # bwmax), this module enforces fair share access to bwmax. Also, if
32 # the node is capped at a particular limit, rules may optionally be
33 # defined that classify certain packets into the "exempt" class. This
34 # class receives whatever bandwidth is leftover between bwcap and
35 # bwmax; slices fairly share this bandwidth as well.
36 #
37 # The root context is exempt from sharing and can send as much as it
38 # needs to.
39 #
40 # Some relevant URLs:
41 #
42 # 1. http://lartc.org/howto               for how to use tc
43 # 2. http://luxik.cdi.cz/~devik/qos/htb/  for info on HTB
44 #
45 # Andy Bavier <acb@cs.princeton.edu>
46 # Mark Huang <mlhuang@cs.princeton.edu>
47 # Copyright (C) 2006 The Trustees of Princeton University
48 #
49 # $Id: bwlimit.py,v 1.15 2007/02/07 04:21:11 mlhuang Exp $
50 #
51
52 import sys, os, re, getopt
53 from sets import Set
54 import pwd
55
56
57 # Where the tc binary lives
58 TC = "/sbin/tc"
59
60 # Default interface
61 dev = "eth0"
62
63 # Verbosity level
64 verbose = 0
65
66 # bwmin should be small enough that it can be considered negligibly
67 # slow compared to the hardware. 8 bits/second appears to be the
68 # smallest value supported by tc.
69 bwmin = 1000
70
71 # bwmax should be large enough that it can be considered at least as
72 # fast as the hardware.
73 bwmax = 1000*1000*1000
74
75 # quantum is the maximum number of bytes that can be borrowed by a
76 # share (or slice, if each slice gets 1 share) in one time period
77 # (with HZ=1000, 1 ms). If multiple slices are competing for bandwidth
78 # above their guarantees, and each is attempting to borrow up to the
79 # node bandwidth cap, quantums control how the excess bandwidth is
80 # distributed. Slices with 2 shares will borrow twice the amount in
81 # one time period as slices with 1 share, so averaged over time, they
82 # will get twice as much of the excess bandwidth. The value should be
83 # as small as possible and at least 1 MTU. By default, it would be
84 # calculated as bwmin/10, but since we use such small a value for
85 # bwmin, it's better to just set it to a value safely above 1 Ethernet
86 # MTU.
87 quantum = 1600
88
89 # cburst is the maximum number of bytes that can be burst onto the
90 # wire in one time period (with HZ=1000, 1 ms). If multiple slices
91 # have data queued for transmission, cbursts control how long each
92 # slice can have the wire for. If not specified, it is set to the
93 # smallest possible value that would enable the slice's "ceil" rate
94 # (usually the node bandwidth cap), to be reached if a slice was able
95 # to borrow enough bandwidth to do so. For now, it's unclear how or if
96 # to relate this to the notion of shares, so just let tc set the
97 # default.
98 cburst = None
99
100 # There is another parameter that controls how bandwidth is allocated
101 # between slices on nodes that is outside the scope of HTB. We enforce
102 # a 16 GByte/day total limit on each slice, which works out to about
103 # 1.5mbit. If a slice exceeds this byte limit before the day finishes,
104 # it is capped at (i.e., its "ceil" rate is set to) the smaller of the
105 # node bandwidth cap or 1.5mbit. pl_mom is in charge of enforcing this
106 # rule and executes this script to override "ceil".
107
108 # We support multiple bandwidth limits, by reserving the top nibble of
109 # the minor classid to be the "subclassid". Theoretically, we could
110 # support up to 15 subclasses, but for now, we only define two: the
111 # "default" subclass 1:10 that is capped at the node bandwidth cap (in
112 # this example, 5mbit) and the "exempt" subclass 1:20 that is capped
113 # at bwmax (i.e., not capped). The 1:1 parent class exists only to
114 # make the borrowing model work. All bandwidth above minimum
115 # guarantees is fairly shared (in this example, slice 2 is guaranteed
116 # at least 1mbit in addition to fair access to the rest), subject to
117 # the restrictions of the class hierarchy: namely, that the total
118 # bandwidth to non-exempt destinations should not exceed the node
119 # bandwidth cap.
120 #
121 #                         1:
122 #                         |
123 #                    1:1 (1gbit)
124 #           ______________|_____________
125 #          |                            |
126 #   1:10 (8bit, 5mbit)           1:20 (8bit, 1gbit)
127 #          |                            |
128 # 1:1000 (8bit, 5mbit),        1:2000 (8bit, 1gbit),
129 # 1:1001 (8bit, 5mbit),        1:2001 (8bit, 1gbit),
130 # 1:1002 (1mbit, 5mbit),       1:2002 (1mbit, 1gbit),
131 # ...                          ...
132 # 1:1FFF (8bit, 5mbit)         1:2FFF (8bit, 1gbit)
133 #
134 default_minor = 0x1000
135 exempt_minor = 0x2000
136
137 # root_xid is for the root context. The root context is exempt from
138 # fair sharing in both the default and exempt subclasses. The root
139 # context gets 5 shares by default.
140 root_xid = 0x0000
141 root_share = 5
142
143 # default_xid is for unclassifiable packets. Packets should not be
144 # classified here very often. They can be if a slice's HTB classes are
145 # deleted before its processes are. Each slice gets 1 share by
146 # default.
147 default_xid = 0x0FFF
148 default_share = 1
149
150 # See tc_util.c and http://physics.nist.gov/cuu/Units/binary.html. Be
151 # warned that older versions of tc interpret "kbps", "mbps", "mbit",
152 # and "kbit" to mean (in this system) "kibps", "mibps", "mibit", and
153 # "kibit" and that if an older version is installed, all rates will
154 # be off by a small fraction.
155 suffixes = {
156     "":         1,
157     "bit":      1,
158     "kibit":    1024,
159     "kbit":     1000,
160     "mibit":    1024*1024,
161     "mbit":     1000000,
162     "gibit":    1024*1024*1024,
163     "gbit":     1000000000,
164     "tibit":    1024*1024*1024*1024,
165     "tbit":     1000000000000,
166     "bps":      8,
167     "kibps":    8*1024,
168     "kbps":     8000,
169     "mibps":    8*1024*1024,
170     "mbps":     8000000,
171     "gibps":    8*1024*1024*1024,
172     "gbps":     8000000000,
173     "tibps":    8*1024*1024*1024*1024,
174     "tbps":     8000000000000
175 }
176
177
178 def get_tc_rate(s):
179     """
180     Parses an integer or a tc rate string (e.g., 1.5mbit) into bits/second
181     """
182
183     if type(s) == int:
184         return s
185     m = re.match(r"([0-9.]+)(\D*)", s)
186     if m is None:
187         return -1
188     suffix = m.group(2).lower()
189     if suffixes.has_key(suffix):
190         return int(float(m.group(1)) * suffixes[suffix])
191     else:
192         return -1
193
194 def format_bytes(bytes, si = True):
195     """
196     Formats bytes into a string
197     """
198     if si:
199         kilo = 1000.
200     else:
201         # Officially, a kibibyte
202         kilo = 1024.
203
204     if bytes >= (kilo * kilo * kilo):
205         return "%.1f GB" % (bytes / (kilo * kilo * kilo))
206     elif bytes >= 1000000:
207         return "%.1f MB" % (bytes / (kilo * kilo))
208     elif bytes >= 1000:
209         return "%.1f KB" % (bytes / kilo)
210     else:
211         return "%.0f bytes" % bytes
212
213 def format_tc_rate(rate):
214     """
215     Formats a bits/second rate into a tc rate string
216     """
217
218     if rate >= 1000000000 and (rate % 1000000000) == 0:
219         return "%.0fgbit" % (rate / 1000000000.)
220     elif rate >= 1000000 and (rate % 1000000) == 0:
221         return "%.0fmbit" % (rate / 1000000.)
222     elif rate >= 1000:
223         return "%.0fkbit" % (rate / 1000.)
224     else:
225         return "%.0fbit" % rate
226
227
228 # Parse /etc/planetlab/bwcap (or equivalent)
229 def read_bwcap(bwcap_file):
230     bwcap = bwmax
231     try:
232         fp = open(bwcap_file, "r")
233         line = fp.readline().strip()
234         if line:
235             bwcap = get_tc_rate(line)
236     except:
237         pass
238     if bwcap == -1:
239         bwcap = bwmax
240     return bwcap
241
242
243 def get_bwcap(dev = dev):
244     """
245     Get the current (live) value of the node bandwidth cap
246     """
247
248     state = tc("-d class show dev %s" % dev)
249     base_re = re.compile(r"class htb 1:10 parent 1:1 .*ceil ([^ ]+) .*")
250     base_classes = filter(None, map(base_re.match, state))
251     if not base_classes:
252         return -1
253     if len(base_classes) > 1:
254         raise Exception, "unable to get current bwcap"
255     return get_tc_rate(base_classes[0].group(1))
256
257
258 def get_slice(xid):
259     """
260     Get slice name ("princeton_mlh") from slice xid (500)
261     """
262
263     if xid == root_xid:
264         return "root"
265     if xid == default_xid:
266         return "default"
267     try:
268         return pwd.getpwuid(xid).pw_name
269     except KeyError:
270         pass
271
272     return None
273
274 def get_xid(slice):
275     """
276     Get slice xid ("princeton_mlh") from slice name ("500" or "princeton_mlh")
277     """
278
279     if slice == "root":
280         return root_xid
281     if slice == "default":
282         return default_xid
283     try:
284         try:
285             return int(slice)
286         except ValueError:
287             pass
288         return pwd.getpwnam(slice).pw_uid
289     except KeyError:
290         pass
291
292     return None
293
294 def run(cmd, input = None):
295     """
296     Shortcut for running a shell command
297     """
298
299     try:
300         if verbose:
301             sys.stderr.write("Executing: " + cmd + "\n")
302         if input is None:
303             fileobj = os.popen(cmd, "r")
304             output = fileobj.readlines()
305         else:
306             fileobj = os.popen(cmd, "w")
307             fileobj.write(input)
308             output = None
309         if fileobj.close() is None:
310             return output
311     except Exception, e:
312         pass
313     return None
314
315
316 def tc(cmd):
317     """
318     Shortcut for running a tc command
319     """
320
321     return run(TC + " " + cmd)
322
323
324 def stop(dev = dev):
325     '''
326     Turn off all queing.  Stops all slice HTBS and reverts to pfifo_fast (the default).
327     '''
328     try:
329         for i in range(0,2):
330             tc("qdisc del dev %s root" % dev)
331     except: pass
332
333
334 def init(dev = dev, bwcap = bwmax):
335     """
336     (Re)initialize the bandwidth limits on this node
337     """
338
339     # Load the module used to manage exempt classes
340     run("/sbin/modprobe ip_set_iphash")
341
342     # Save current settings
343     paramslist = get(None, dev)
344
345     # Delete root qdisc 1: if it exists. This will also automatically
346     # delete any child classes.
347     for line in tc("qdisc show dev %s" % dev):
348         # Search for the root qdisc 1:
349         m = re.match(r"qdisc htb 1:", line)
350         if m is not None:
351             tc("qdisc del dev %s root handle 1:" % dev)
352             break
353
354     # Initialize HTB. The "default" clause specifies that if a packet
355     # fails classification, it should go into the class with handle
356     # 1FFF.
357     tc("qdisc add dev %s root handle 1: htb default %x" % \
358        (dev, default_minor | default_xid))
359
360     # Set up a parent class from which all subclasses borrow.
361     tc("class add dev %s parent 1: classid 1:1 htb rate %dbit" % \
362        (dev, bwmax))
363
364     # Set up a subclass that represents the node bandwidth cap. We
365     # allow each slice to borrow up to this rate, so it is also
366     # usually the "ceil" rate for each slice.
367     tc("class add dev %s parent 1:1 classid 1:10 htb rate %dbit ceil %dbit" % \
368        (dev, bwmin, bwcap))
369
370     # Set up a subclass that represents "exemption" from the node
371     # bandwidth cap. Once the node bandwidth cap is reached, bandwidth
372     # to exempt destinations can still be fairly shared up to bwmax.
373     tc("class add dev %s parent 1:1 classid 1:20 htb rate %dbit ceil %dbit" % \
374        (dev, bwmin, bwmax))
375
376     # Set up the root class (and tell VNET what it is). Packets sent
377     # by root end up here and are capped at the node bandwidth
378     # cap.
379     #on(root_xid, dev, share = root_share)
380     #try:
381     #    file("/proc/sys/vnet/root_class", "w").write("%d" % ((1 << 16) | default_minor | root_xid))
382     #except:
383     #    pass
384
385     # Set up the default class. Packets that fail classification end
386     # up here.
387     on(default_xid, dev, share = default_share)
388
389     # Restore old settings
390     for (xid, share,
391          minrate, maxrate,
392          minexemptrate, maxexemptrate,
393          bytes, exemptbytes) in paramslist:
394         if xid not in (root_xid, default_xid):
395             on(xid, dev, share, minrate, maxrate, minexemptrate, maxexemptrate)
396
397
398 def get(xid = None, dev = dev):
399     """
400     Get the bandwidth limits and current byte totals for a
401     particular slice xid as a tuple (xid, share, minrate, maxrate,
402     minexemptrate, maxexemptrate, bytes, exemptbytes), or all classes
403     as a list of such tuples.
404     """
405
406     if xid is None:
407         ret = []
408     else:
409         ret = None
410
411     rates = {}
412     rate = None
413
414     # ...
415     # class htb 1:1000 parent 1:10 leaf 1000: prio 0 quantum 8000 rate 8bit ceil 10000Kbit ...
416     #  Sent 6851486 bytes 49244 pkt (dropped 0, overlimits 0 requeues 0)
417     # ...
418     # class htb 1:2000 parent 1:20 leaf 2000: prio 0 quantum 8000 rate 8bit ceil 1000Mbit ...
419     #  Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) 
420     # ...
421     for line in tc("-s -d class show dev %s" % dev):
422         # Rate parameter line
423         params = re.match(r"class htb 1:([0-9a-f]+) parent 1:(10|20)", line)
424         # Statistics line
425         stats = re.match(r".* Sent ([0-9]+) bytes", line)
426         # Another class
427         ignore = re.match(r"class htb", line)
428
429         if params is not None:
430             # Which class
431             if params.group(2) == "10":
432                 min = 'min'
433                 max = 'max'
434                 bytes = 'bytes'
435             else:
436                 min = 'minexempt'
437                 max = 'maxexempt'
438                 bytes = 'exemptbytes'
439
440             # Slice ID
441             id = int(params.group(1), 16) & 0x0FFF;
442
443             if rates.has_key(id):
444                 rate = rates[id]
445             else:
446                 rate = {'id': id}
447
448             # Parse share
449             rate['share'] = 1
450             m = re.search(r"quantum (\d+)", line)
451             if m is not None:
452                 rate['share'] = int(m.group(1)) / quantum
453
454             # Parse minrate
455             rate[min] = bwmin
456             m = re.search(r"rate (\w+)", line)
457             if m is not None:
458                 rate[min] = get_tc_rate(m.group(1))
459
460             # Parse maxrate
461             rate[max] = bwmax
462             m = re.search(r"ceil (\w+)", line)
463             if m is not None:
464                 rate[max] = get_tc_rate(m.group(1))
465
466             # Which statistics to parse
467             rate['stats'] = bytes
468
469             rates[id] = rate
470
471         elif stats is not None:
472             if rate is not None:
473                 rate[rate['stats']] = int(stats.group(1))
474
475         elif ignore is not None:
476             rate = None
477
478         # Keep parsing until we get everything
479         if rate is not None and \
480            rate.has_key('min') and rate.has_key('minexempt') and \
481            rate.has_key('max') and rate.has_key('maxexempt') and \
482            rate.has_key('bytes') and rate.has_key('exemptbytes'):
483             params = (rate['id'], rate['share'],
484                       rate['min'], rate['max'],
485                       rate['minexempt'], rate['maxexempt'],
486                       rate['bytes'], rate['exemptbytes'])
487             if xid is None:
488                 # Return a list of parameters
489                 ret.append(params)
490                 rate = None
491             elif xid == rate['id']:
492                 # Return the parameters for this class
493                 ret = params
494                 break
495
496     return ret
497
498
499 def on(xid, dev = dev, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None):
500     """
501     Apply specified bandwidth limit to the specified slice xid
502     """
503
504     # Get defaults from current state if available
505     cap = get(xid, dev)
506     if cap is not None:
507         if share is None:
508             share = cap[1]
509         if minrate is None:
510             minrate = cap[2]
511         if maxrate is None:
512             maxrate = cap[3]
513         if minexemptrate is None:
514             minexemptrate = cap[4]
515         if maxexemptrate is None:
516             maxexemptrate = cap[5]
517
518     # Figure out what the current node bandwidth cap is
519     bwcap = get_bwcap()
520
521     # Set defaults
522     if share is None:
523         share = default_share
524     if minrate is None:
525         minrate = bwmin
526     else:
527         minrate = get_tc_rate(minrate)
528     if maxrate is None:
529         maxrate = bwcap
530     else:
531         maxrate = get_tc_rate(maxrate)
532     if minexemptrate is None:
533         minexemptrate = minrate
534     else:
535         minexemptrate = get_tc_rate(minexemptrate)
536     if maxexemptrate is None:
537         maxexemptrate = bwmax
538     else:
539         maxexemptrate = get_tc_rate(maxexemptrate)
540
541     # Sanity checks
542     if maxrate < bwmin:
543         maxrate = bwmin
544     if maxrate > bwcap:
545         maxrate = bwcap
546     if minrate < bwmin:
547         minrate = bwmin
548     if minrate > maxrate:
549         minrate = maxrate
550     if maxexemptrate < bwmin:
551         maxexemptrate = bwmin
552     if maxexemptrate > bwmax:
553         maxexemptrate = bwmax
554     if minexemptrate < bwmin:
555         minexemptrate = bwmin
556     if minexemptrate > maxexemptrate:
557         minexemptrate = maxexemptrate
558
559     # Set up subclasses for the slice
560     tc("class replace dev %s parent 1:10 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \
561        (dev, default_minor | xid, minrate, maxrate, share * quantum))
562
563     tc("class replace dev %s parent 1:20 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \
564        (dev, exempt_minor | xid, minexemptrate, maxexemptrate, share * quantum))
565
566     # Attach a FIFO to each subclass, which helps to throttle back
567     # processes that are sending faster than the token buckets can
568     # support.
569     tc("qdisc replace dev %s parent 1:%x handle %x pfifo" % \
570        (dev, default_minor | xid, default_minor | xid))
571
572     tc("qdisc replace dev %s parent 1:%x handle %x pfifo" % \
573        (dev, exempt_minor | xid, exempt_minor | xid))
574
575
576 def set(xid, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None):
577     on(xid = xid, share = share,
578        minrate = minrate, maxrate = maxrate,
579        minexemptrate = minexemptrate, maxexemptrate = maxexemptrate)
580
581
582 # Remove class associated with specified slice xid. If further packets
583 # are seen from this slice, they will be classified into the default
584 # class 1:1FFF.
585 def off(xid, dev = dev):
586     """
587     Remove class associated with specified slice xid. If further
588     packets are seen from this slice, they will be classified into the
589     default class 1:1FFF.
590     """
591
592     cap = get(xid, dev)
593     if cap is not None:
594         tc("class del dev %s classid 1:%x" % (dev, default_minor | xid))
595         tc("class del dev %s classid 1:%x" % (dev, exempt_minor | xid))
596
597
598 def exempt_init(group_name, node_ips):
599     """
600     Initialize the list of destinations exempt from the node bandwidth
601     (burst) cap.
602     """
603
604     # Check of set exists
605     set = run("/sbin/ipset -S " + group_name)
606     if set == None:
607         # Create a hashed IP set of all of these destinations
608         lines = ["-N %s iphash" % group_name]
609         add_cmd = "-A %s " % group_name
610         lines += [(add_cmd + ip) for ip in node_ips]
611         lines += ["COMMIT"]
612         restore = "\n".join(lines) + "\n"
613         run("/sbin/ipset -R", restore)
614     else: # set exists
615         # Check all hosts and add missing.
616         for nodeip in node_ips:
617             if not run("/sbin/ipset -T %s %s" % (group_name, nodeip)):
618                run("/sbin/ipset -A %s %s" % (group_name, nodeip))
619
620
621 def usage():
622     bwcap_description = format_tc_rate(get_bwcap())
623         
624     print """
625 Usage:
626
627 %s [OPTION]... [COMMAND] [ARGUMENT]...
628
629 Options:
630         -d device   Network interface (default: %s)
631         -r rate         Node bandwidth cap (default: %s)
632         -q quantum      Share multiplier (default: %d bytes)
633         -n              Print rates in numeric bits per second
634         -v              Enable verbose debug messages
635         -h              This message
636
637 Commands:
638         init
639                 (Re)initialize all bandwidth parameters
640         on slice [share|-] [minrate|-] [maxrate|-] [minexemptrate|-] [maxexemptrate|-]
641                 Set bandwidth parameter(s) for the specified slice
642         off slice
643                 Remove all bandwidth parameters for the specified slice
644         get
645                 Get all bandwidth parameters for all slices
646         get slice
647                 Get bandwidth parameters for the specified slice
648 """ % (sys.argv[0], dev, bwcap_description, quantum)
649     sys.exit(1)
650     
651
652 def main():
653     global dev, quantum, verbose
654
655     # Defaults
656     numeric = False
657     bwcap = get_bwcap()
658
659     (opts, argv) = getopt.getopt(sys.argv[1:], "d:nr:q:vh")
660     for (opt, optval) in opts:
661         if opt == '-d':
662             dev = optval
663         elif opt == '-n':
664             numeric = True
665         elif opt == '-r':
666             bwcap = get_tc_rate(optval)
667         elif opt == '-q':
668             quantum = int(optval)
669         elif opt == '-v':
670             verbose += 1
671         elif opt == '-h':
672             usage()
673
674     if len(argv):
675         if argv[0] == "init" or (argv[0] == "on" and len(argv) == 1):
676             # (Re)initialize
677             init(dev, get_tc_rate(bwcap))
678
679         elif argv[0] == "get" or argv[0] == "show":
680             # Show
681             if len(argv) >= 2:
682                 # Show a particular slice
683                 xid = get_xid(argv[1])
684                 if xid is None:
685                     sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1])
686                     usage()
687                 params = get(xid, dev)
688                 if params is None:
689                     paramslist = []
690                 else:
691                     paramslist = [params]
692             else:
693                 # Show all slices
694                 paramslist = get(None, dev)
695
696             for (xid, share,
697                  minrate, maxrate,
698                  minexemptrate, maxexemptrate,
699                  bytes, exemptbytes) in paramslist:
700                 slice = get_slice(xid)
701                 if slice is None:
702                     # Orphaned (not associated with a slice) class
703                     slice = "%d?" % xid
704                 if numeric:
705                     print "%s %d %d %d %d %d %d %d" % \
706                           (slice, share,
707                            minrate, maxrate,
708                            minexemptrate, maxexemptrate,
709                            bytes, exemptbytes)
710                 else:
711                     print "%s %d %s %s %s %s %s %s" % \
712                           (slice, share,
713                            format_tc_rate(minrate), format_tc_rate(maxrate),
714                            format_tc_rate(minexemptrate), format_tc_rate(maxexemptrate),
715                            format_bytes(bytes), format_bytes(exemptbytes))
716
717         elif len(argv) >= 2:
718             # slice, ...
719             xid = get_xid(argv[1])
720             if xid is None:
721                 sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1])
722                 usage()
723
724             if argv[0] == "on" or argv[0] == "add" or argv[0] == "replace" or argv[0] == "set":
725                 # Enable cap
726                 args = []
727                 if len(argv) >= 3:
728                     # ... share, minrate, maxrate, minexemptrate, maxexemptrate
729                     casts = [int, get_tc_rate, get_tc_rate, get_tc_rate, get_tc_rate]
730                     for i, arg in enumerate(argv[2:]):
731                         if i >= len(casts):
732                             break
733                         if arg == "-":
734                             args.append(None)
735                         else:
736                             args.append(casts[i](arg))
737                 on(xid, dev, *args)
738
739             elif argv[0] == "off" or argv[0] == "del":
740                 # Disable cap
741                 off(xid, dev)
742
743             else:
744                 usage()
745
746         else:
747             usage()
748
749
750 if __name__ == '__main__':
751     main()