7b3cf050e0b66e59ad255e8bb58c723387898447
[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     tc("qdisc del dev %s root" % dev)
329
330
331 def init(dev = dev, bwcap = bwmax):
332     """
333     (Re)initialize the bandwidth limits on this node
334     """
335
336     # Load the module used to manage exempt classes
337     run("/sbin/modprobe ip_set_iphash")
338
339     # Save current settings
340     paramslist = get(None, dev)
341
342     # Delete root qdisc 1: if it exists. This will also automatically
343     # delete any child classes.
344     for line in tc("qdisc show dev %s" % dev):
345         # Search for the root qdisc 1:
346         m = re.match(r"qdisc htb 1:", line)
347         if m is not None:
348             tc("qdisc del dev %s root handle 1:" % dev)
349             break
350
351     # Initialize HTB. The "default" clause specifies that if a packet
352     # fails classification, it should go into the class with handle
353     # 1FFF.
354     tc("qdisc add dev %s root handle 1: htb default %x" % \
355        (dev, default_minor | default_xid))
356
357     # Set up a parent class from which all subclasses borrow.
358     tc("class add dev %s parent 1: classid 1:1 htb rate %dbit" % \
359        (dev, bwmax))
360
361     # Set up a subclass that represents the node bandwidth cap. We
362     # allow each slice to borrow up to this rate, so it is also
363     # usually the "ceil" rate for each slice.
364     tc("class add dev %s parent 1:1 classid 1:10 htb rate %dbit ceil %dbit" % \
365        (dev, bwmin, bwcap))
366
367     # Set up a subclass that represents "exemption" from the node
368     # bandwidth cap. Once the node bandwidth cap is reached, bandwidth
369     # to exempt destinations can still be fairly shared up to bwmax.
370     tc("class add dev %s parent 1:1 classid 1:20 htb rate %dbit ceil %dbit" % \
371        (dev, bwmin, bwmax))
372
373     # Set up the root class (and tell VNET what it is). Packets sent
374     # by root end up here and are capped at the node bandwidth
375     # cap.
376     #on(root_xid, dev, share = root_share)
377     #try:
378     #    file("/proc/sys/vnet/root_class", "w").write("%d" % ((1 << 16) | default_minor | root_xid))
379     #except:
380     #    pass
381
382     # Set up the default class. Packets that fail classification end
383     # up here.
384     on(default_xid, dev, share = default_share)
385
386     # Restore old settings
387     for (xid, share,
388          minrate, maxrate,
389          minexemptrate, maxexemptrate,
390          bytes, exemptbytes) in paramslist:
391         if xid not in (root_xid, default_xid):
392             on(xid, dev, share, minrate, maxrate, minexemptrate, maxexemptrate)
393
394
395 def get(xid = None, dev = dev):
396     """
397     Get the bandwidth limits and current byte totals for a
398     particular slice xid as a tuple (xid, share, minrate, maxrate,
399     minexemptrate, maxexemptrate, bytes, exemptbytes), or all classes
400     as a list of such tuples.
401     """
402
403     if xid is None:
404         ret = []
405     else:
406         ret = None
407
408     rates = {}
409     rate = None
410
411     # ...
412     # class htb 1:1000 parent 1:10 leaf 1000: prio 0 quantum 8000 rate 8bit ceil 10000Kbit ...
413     #  Sent 6851486 bytes 49244 pkt (dropped 0, overlimits 0 requeues 0)
414     # ...
415     # class htb 1:2000 parent 1:20 leaf 2000: prio 0 quantum 8000 rate 8bit ceil 1000Mbit ...
416     #  Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) 
417     # ...
418     for line in tc("-s -d class show dev %s" % dev):
419         # Rate parameter line
420         params = re.match(r"class htb 1:([0-9a-f]+) parent 1:(10|20)", line)
421         # Statistics line
422         stats = re.match(r".* Sent ([0-9]+) bytes", line)
423         # Another class
424         ignore = re.match(r"class htb", line)
425
426         if params is not None:
427             # Which class
428             if params.group(2) == "10":
429                 min = 'min'
430                 max = 'max'
431                 bytes = 'bytes'
432             else:
433                 min = 'minexempt'
434                 max = 'maxexempt'
435                 bytes = 'exemptbytes'
436
437             # Slice ID
438             id = int(params.group(1), 16) & 0x0FFF;
439
440             if rates.has_key(id):
441                 rate = rates[id]
442             else:
443                 rate = {'id': id}
444
445             # Parse share
446             rate['share'] = 1
447             m = re.search(r"quantum (\d+)", line)
448             if m is not None:
449                 rate['share'] = int(m.group(1)) / quantum
450
451             # Parse minrate
452             rate[min] = bwmin
453             m = re.search(r"rate (\w+)", line)
454             if m is not None:
455                 rate[min] = get_tc_rate(m.group(1))
456
457             # Parse maxrate
458             rate[max] = bwmax
459             m = re.search(r"ceil (\w+)", line)
460             if m is not None:
461                 rate[max] = get_tc_rate(m.group(1))
462
463             # Which statistics to parse
464             rate['stats'] = bytes
465
466             rates[id] = rate
467
468         elif stats is not None:
469             if rate is not None:
470                 rate[rate['stats']] = int(stats.group(1))
471
472         elif ignore is not None:
473             rate = None
474
475         # Keep parsing until we get everything
476         if rate is not None and \
477            rate.has_key('min') and rate.has_key('minexempt') and \
478            rate.has_key('max') and rate.has_key('maxexempt') and \
479            rate.has_key('bytes') and rate.has_key('exemptbytes'):
480             params = (rate['id'], rate['share'],
481                       rate['min'], rate['max'],
482                       rate['minexempt'], rate['maxexempt'],
483                       rate['bytes'], rate['exemptbytes'])
484             if xid is None:
485                 # Return a list of parameters
486                 ret.append(params)
487                 rate = None
488             elif xid == rate['id']:
489                 # Return the parameters for this class
490                 ret = params
491                 break
492
493     return ret
494
495
496 def on(xid, dev = dev, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None):
497     """
498     Apply specified bandwidth limit to the specified slice xid
499     """
500
501     # Get defaults from current state if available
502     cap = get(xid, dev)
503     if cap is not None:
504         if share is None:
505             share = cap[1]
506         if minrate is None:
507             minrate = cap[2]
508         if maxrate is None:
509             maxrate = cap[3]
510         if minexemptrate is None:
511             minexemptrate = cap[4]
512         if maxexemptrate is None:
513             maxexemptrate = cap[5]
514
515     # Figure out what the current node bandwidth cap is
516     bwcap = get_bwcap()
517
518     # Set defaults
519     if share is None:
520         share = default_share
521     if minrate is None:
522         minrate = bwmin
523     else:
524         minrate = get_tc_rate(minrate)
525     if maxrate is None:
526         maxrate = bwcap
527     else:
528         maxrate = get_tc_rate(maxrate)
529     if minexemptrate is None:
530         minexemptrate = minrate
531     else:
532         minexemptrate = get_tc_rate(minexemptrate)
533     if maxexemptrate is None:
534         maxexemptrate = bwmax
535     else:
536         maxexemptrate = get_tc_rate(maxexemptrate)
537
538     # Sanity checks
539     if maxrate < bwmin:
540         maxrate = bwmin
541     if maxrate > bwcap:
542         maxrate = bwcap
543     if minrate < bwmin:
544         minrate = bwmin
545     if minrate > maxrate:
546         minrate = maxrate
547     if maxexemptrate < bwmin:
548         maxexemptrate = bwmin
549     if maxexemptrate > bwmax:
550         maxexemptrate = bwmax
551     if minexemptrate < bwmin:
552         minexemptrate = bwmin
553     if minexemptrate > maxexemptrate:
554         minexemptrate = maxexemptrate
555
556     # Set up subclasses for the slice
557     tc("class replace dev %s parent 1:10 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \
558        (dev, default_minor | xid, minrate, maxrate, share * quantum))
559
560     tc("class replace dev %s parent 1:20 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \
561        (dev, exempt_minor | xid, minexemptrate, maxexemptrate, share * quantum))
562
563     # Attach a FIFO to each subclass, which helps to throttle back
564     # processes that are sending faster than the token buckets can
565     # support.
566     tc("qdisc replace dev %s parent 1:%x handle %x pfifo" % \
567        (dev, default_minor | xid, default_minor | xid))
568
569     tc("qdisc replace dev %s parent 1:%x handle %x pfifo" % \
570        (dev, exempt_minor | xid, exempt_minor | xid))
571
572
573 def set(xid, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None):
574     on(xid = xid, share = share,
575        minrate = minrate, maxrate = maxrate,
576        minexemptrate = minexemptrate, maxexemptrate = maxexemptrate)
577
578
579 # Remove class associated with specified slice xid. If further packets
580 # are seen from this slice, they will be classified into the default
581 # class 1:1FFF.
582 def off(xid, dev = dev):
583     """
584     Remove class associated with specified slice xid. If further
585     packets are seen from this slice, they will be classified into the
586     default class 1:1FFF.
587     """
588
589     cap = get(xid, dev)
590     if cap is not None:
591         tc("class del dev %s classid 1:%x" % (dev, default_minor | xid))
592         tc("class del dev %s classid 1:%x" % (dev, exempt_minor | xid))
593
594
595 def exempt_init(group_name, node_ips):
596     """
597     Initialize the list of destinations exempt from the node bandwidth
598     (burst) cap.
599     """
600
601     # Check of set exists
602     set = run("/sbin/ipset -S " + group_name)
603     if set == None:
604         # Create a hashed IP set of all of these destinations
605         lines = ["-N %s iphash" % group_name]
606         add_cmd = "-A %s " % group_name
607         lines += [(add_cmd + ip) for ip in node_ips]
608         lines += ["COMMIT"]
609         restore = "\n".join(lines) + "\n"
610         run("/sbin/ipset -R", restore)
611     else: # set exists
612         # Check all hosts and add missing.
613         for nodeip in node_ips:
614             if not run("/sbin/ipset -T %s %s" % (group_name, nodeip)):
615                run("/sbin/ipset -A %s %s" % (group_name, nodeip))
616
617
618 def usage():
619     bwcap_description = format_tc_rate(get_bwcap())
620         
621     print """
622 Usage:
623
624 %s [OPTION]... [COMMAND] [ARGUMENT]...
625
626 Options:
627         -d device   Network interface (default: %s)
628         -r rate         Node bandwidth cap (default: %s)
629         -q quantum      Share multiplier (default: %d bytes)
630         -n              Print rates in numeric bits per second
631         -v              Enable verbose debug messages
632         -h              This message
633
634 Commands:
635         init
636                 (Re)initialize all bandwidth parameters
637         on slice [share|-] [minrate|-] [maxrate|-] [minexemptrate|-] [maxexemptrate|-]
638                 Set bandwidth parameter(s) for the specified slice
639         off slice
640                 Remove all bandwidth parameters for the specified slice
641         get
642                 Get all bandwidth parameters for all slices
643         get slice
644                 Get bandwidth parameters for the specified slice
645 """ % (sys.argv[0], dev, bwcap_description, quantum)
646     sys.exit(1)
647     
648
649 def main():
650     global dev, quantum, verbose
651
652     # Defaults
653     numeric = False
654     bwcap = get_bwcap()
655
656     (opts, argv) = getopt.getopt(sys.argv[1:], "d:nr:q:vh")
657     for (opt, optval) in opts:
658         if opt == '-d':
659             dev = optval
660         elif opt == '-n':
661             numeric = True
662         elif opt == '-r':
663             bwcap = get_tc_rate(optval)
664         elif opt == '-q':
665             quantum = int(optval)
666         elif opt == '-v':
667             verbose += 1
668         elif opt == '-h':
669             usage()
670
671     if len(argv):
672         if argv[0] == "init" or (argv[0] == "on" and len(argv) == 1):
673             # (Re)initialize
674             init(dev, get_tc_rate(bwcap))
675
676         elif argv[0] == "get" or argv[0] == "show":
677             # Show
678             if len(argv) >= 2:
679                 # Show a particular slice
680                 xid = get_xid(argv[1])
681                 if xid is None:
682                     sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1])
683                     usage()
684                 params = get(xid, dev)
685                 if params is None:
686                     paramslist = []
687                 else:
688                     paramslist = [params]
689             else:
690                 # Show all slices
691                 paramslist = get(None, dev)
692
693             for (xid, share,
694                  minrate, maxrate,
695                  minexemptrate, maxexemptrate,
696                  bytes, exemptbytes) in paramslist:
697                 slice = get_slice(xid)
698                 if slice is None:
699                     # Orphaned (not associated with a slice) class
700                     slice = "%d?" % xid
701                 if numeric:
702                     print "%s %d %d %d %d %d %d %d" % \
703                           (slice, share,
704                            minrate, maxrate,
705                            minexemptrate, maxexemptrate,
706                            bytes, exemptbytes)
707                 else:
708                     print "%s %d %s %s %s %s %s %s" % \
709                           (slice, share,
710                            format_tc_rate(minrate), format_tc_rate(maxrate),
711                            format_tc_rate(minexemptrate), format_tc_rate(maxexemptrate),
712                            format_bytes(bytes), format_bytes(exemptbytes))
713
714         elif len(argv) >= 2:
715             # slice, ...
716             xid = get_xid(argv[1])
717             if xid is None:
718                 sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1])
719                 usage()
720
721             if argv[0] == "on" or argv[0] == "add" or argv[0] == "replace" or argv[0] == "set":
722                 # Enable cap
723                 args = []
724                 if len(argv) >= 3:
725                     # ... share, minrate, maxrate, minexemptrate, maxexemptrate
726                     casts = [int, get_tc_rate, get_tc_rate, get_tc_rate, get_tc_rate]
727                     for i, arg in enumerate(argv[2:]):
728                         if i >= len(casts):
729                             break
730                         if arg == "-":
731                             args.append(None)
732                         else:
733                             args.append(casts[i](arg))
734                 on(xid, dev, *args)
735
736             elif argv[0] == "off" or argv[0] == "del":
737                 # Disable cap
738                 off(xid, dev)
739
740             else:
741                 usage()
742
743         else:
744             usage()
745
746
747 if __name__ == '__main__':
748     main()