merge trellis branch to trunk.
[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:100 (8bit, 5mbit)                  |
129 #          |                            |
130 # 1:1000 (8bit, 5mbit),        1:2000 (8bit, 1gbit),
131 # 1:1001 (8bit, 5mbit),        1:2001 (8bit, 1gbit),
132 # 1:1002 (1mbit, 5mbit),       1:2002 (1mbit, 1gbit),
133 # ...                          ...
134 # 1:1FFF (8bit, 5mbit)         1:2FFF (8bit, 1gbit)
135 #
136 default_minor = 0x1000
137 exempt_minor = 0x2000
138
139 # root_xid is for the root context. The root context is exempt from
140 # fair sharing in both the default and exempt subclasses. The root
141 # context gets 5 shares by default.
142 root_xid = 0x0000
143 root_share = 5
144
145 # default_xid is for unclassifiable packets. Packets should not be
146 # classified here very often. They can be if a slice's HTB classes are
147 # deleted before its processes are. Each slice gets 1 share by
148 # default.
149 default_xid = 0x0FFF
150 default_share = 1
151
152 # See tc_util.c and http://physics.nist.gov/cuu/Units/binary.html. Be
153 # warned that older versions of tc interpret "kbps", "mbps", "mbit",
154 # and "kbit" to mean (in this system) "kibps", "mibps", "mibit", and
155 # "kibit" and that if an older version is installed, all rates will
156 # be off by a small fraction.
157 suffixes = {
158     "":         1,
159     "bit":      1,
160     "kibit":    1024,
161     "kbit":     1000,
162     "mibit":    1024*1024,
163     "mbit":     1000000,
164     "gibit":    1024*1024*1024,
165     "gbit":     1000000000,
166     "tibit":    1024*1024*1024*1024,
167     "tbit":     1000000000000,
168     "bps":      8,
169     "kibps":    8*1024,
170     "kbps":     8000,
171     "mibps":    8*1024*1024,
172     "mbps":     8000000,
173     "gibps":    8*1024*1024*1024,
174     "gbps":     8000000000,
175     "tibps":    8*1024*1024*1024*1024,
176     "tbps":     8000000000000
177 }
178
179
180 def get_tc_rate(s):
181     """
182     Parses an integer or a tc rate string (e.g., 1.5mbit) into bits/second
183     """
184
185     if type(s) == int:
186         return s
187     m = re.match(r"([0-9.]+)(\D*)", s)
188     if m is None:
189         return -1
190     suffix = m.group(2).lower()
191     if suffixes.has_key(suffix):
192         return int(float(m.group(1)) * suffixes[suffix])
193     else:
194         return -1
195
196 def format_bytes(bytes, si = True):
197     """
198     Formats bytes into a string
199     """
200     if si:
201         kilo = 1000.
202     else:
203         # Officially, a kibibyte
204         kilo = 1024.
205
206     if bytes >= (kilo * kilo * kilo):
207         return "%.1f GB" % (bytes / (kilo * kilo * kilo))
208     elif bytes >= 1000000:
209         return "%.1f MB" % (bytes / (kilo * kilo))
210     elif bytes >= 1000:
211         return "%.1f KB" % (bytes / kilo)
212     else:
213         return "%.0f bytes" % bytes
214
215 def format_tc_rate(rate):
216     """
217     Formats a bits/second rate into a tc rate string
218     """
219
220     if rate >= 1000000000 and (rate % 1000000000) == 0:
221         return "%.0fgbit" % (rate / 1000000000.)
222     elif rate >= 1000000 and (rate % 1000000) == 0:
223         return "%.0fmbit" % (rate / 1000000.)
224     elif rate >= 1000:
225         return "%.0fkbit" % (rate / 1000.)
226     else:
227         return "%.0fbit" % rate
228
229
230 # Parse /etc/planetlab/bwcap (or equivalent)
231 def read_bwcap(bwcap_file):
232     bwcap = bwmax
233     try:
234         fp = open(bwcap_file, "r")
235         line = fp.readline().strip()
236         if line:
237             bwcap = get_tc_rate(line)
238     except:
239         pass
240     if bwcap == -1:
241         bwcap = bwmax
242     return bwcap
243
244
245 def get_bwcap(dev = dev):
246     """
247     Get the current (live) value of the node bandwidth cap
248     """
249
250     state = tc("-d class show dev %s" % dev)
251     base_re = re.compile(r"class htb 1:10 parent 1:1 .*ceil ([^ ]+) .*")
252     base_classes = filter(None, map(base_re.match, state))
253     if not base_classes:
254         return -1
255     if len(base_classes) > 1:
256         raise Exception, "unable to get current bwcap"
257     return get_tc_rate(base_classes[0].group(1))
258
259
260 def get_slice(xid):
261     """
262     Get slice name ("princeton_mlh") from slice xid (500)
263     """
264
265     if xid == root_xid:
266         return "root"
267     if xid == default_xid:
268         return "default"
269     try:
270         return pwd.getpwuid(xid).pw_name
271     except KeyError:
272         pass
273
274     return None
275
276 def get_xid(slice):
277     """
278     Get slice xid ("princeton_mlh") from slice name ("500" or "princeton_mlh")
279     """
280
281     if slice == "root":
282         return root_xid
283     if slice == "default":
284         return default_xid
285     try:
286         try:
287             return int(slice)
288         except ValueError:
289             pass
290         return pwd.getpwnam(slice).pw_uid
291     except KeyError:
292         pass
293
294     return None
295
296 def run(cmd, input = None):
297     """
298     Shortcut for running a shell command
299     """
300
301     try:
302         if verbose:
303             sys.stderr.write("Executing: " + cmd + "\n")
304         if input is None:
305             fileobj = os.popen(cmd, "r")
306             output = fileobj.readlines()
307         else:
308             fileobj = os.popen(cmd, "w")
309             fileobj.write(input)
310             output = None
311         if fileobj.close() is None:
312             return output
313     except Exception, e:
314         pass
315     return None
316
317
318 def tc(cmd):
319     """
320     Shortcut for running a tc command
321     """
322
323     return run(TC + " " + cmd)
324
325
326 def stop(dev = dev):
327     '''
328     Turn off all queing.  Stops all slice HTBS and reverts to pfifo_fast (the default).
329     '''
330     try:
331         for i in range(0,2):
332             tc("qdisc del dev %s root" % dev)
333     except: pass
334
335
336 def init(dev = dev, bwcap = bwmax):
337     """
338     (Re)initialize the bandwidth limits on this node
339     """
340
341     # Load the module used to manage exempt classes
342     run("/sbin/modprobe ip_set_iphash")
343
344     # Save current settings
345     paramslist = get(None, dev)
346
347     # Delete root qdisc 1: if it exists. This will also automatically
348     # delete any child classes.
349     for line in tc("qdisc show dev %s" % dev):
350         # Search for the root qdisc 1:
351         m = re.match(r"qdisc htb 1:", line)
352         if m is not None:
353             tc("qdisc del dev %s root handle 1:" % dev)
354             break
355
356     # Initialize HTB. The "default" clause specifies that if a packet
357     # fails classification, it should go into the class with handle
358     # 1FFF.
359     tc("qdisc add dev %s root handle 1: htb default %x" % \
360        (dev, default_minor | default_xid))
361
362     # Set up a parent class from which all subclasses borrow.
363     tc("class add dev %s parent 1: classid 1:1 htb rate %dbit" % \
364        (dev, bwmax))
365
366     # Set up a subclass that represents the node bandwidth cap. We
367     # allow each slice to borrow up to this rate, so it is also
368     # usually the "ceil" rate for each slice.
369     tc("class add dev %s parent 1:1 classid 1:10 htb rate %dbit ceil %dbit" % \
370        (dev, bwmin, bwcap))
371
372     # Set up a subclass for DRL(Distributed Rate Limiting). 
373     # DRL will directly modify that subclass implementing the site limits.
374     tc("class add dev %s parent 1:10 classid 1:100 htb rate %dbit ceil %dbit" % \
375        (dev, bwmin, bwcap))
376
377
378     # Set up a subclass that represents "exemption" from the node
379     # bandwidth cap. Once the node bandwidth cap is reached, bandwidth
380     # to exempt destinations can still be fairly shared up to bwmax.
381     tc("class add dev %s parent 1:1 classid 1:20 htb rate %dbit ceil %dbit" % \
382        (dev, bwmin, bwmax))
383
384     # Set up the root class (and tell VNET what it is). Packets sent
385     # by root end up here and are capped at the node bandwidth
386     # cap.
387     #on(root_xid, dev, share = root_share)
388     #try:
389     #    file("/proc/sys/vnet/root_class", "w").write("%d" % ((1 << 16) | default_minor | root_xid))
390     #except:
391     #    pass
392
393     # Set up the default class. Packets that fail classification end
394     # up here.
395     on(default_xid, dev, share = default_share)
396
397     # Restore old settings
398     for (xid, share,
399          minrate, maxrate,
400          minexemptrate, maxexemptrate,
401          bytes, exemptbytes) in paramslist:
402         if xid not in (root_xid, default_xid):
403             on(xid, dev, share, minrate, maxrate, minexemptrate, maxexemptrate)
404
405
406 def get(xid = None, dev = dev):
407     """
408     Get the bandwidth limits and current byte totals for a
409     particular slice xid as a tuple (xid, share, minrate, maxrate,
410     minexemptrate, maxexemptrate, bytes, exemptbytes), or all classes
411     as a list of such tuples.
412     """
413
414     if xid is None:
415         ret = []
416     else:
417         ret = None
418
419     rates = {}
420     rate = None
421
422     # ...
423     # class htb 1:1000 parent 1:10 leaf 1000: prio 0 quantum 8000 rate 8bit ceil 10000Kbit ...
424     #  Sent 6851486 bytes 49244 pkt (dropped 0, overlimits 0 requeues 0)
425     # ...
426     # class htb 1:2000 parent 1:20 leaf 2000: prio 0 quantum 8000 rate 8bit ceil 1000Mbit ...
427     #  Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) 
428     # ...
429     for line in tc("-s -d class show dev %s" % dev):
430         # Rate parameter line
431         params = re.match(r"class htb 1:([0-9a-f]+) parent 1:(10|20)", line)
432         # Statistics line
433         stats = re.match(r".* Sent ([0-9]+) bytes", line)
434         # Another class
435         ignore = re.match(r"class htb", line)
436
437         if params is not None:
438             # Which class
439             if params.group(2) == "10":
440                 min = 'min'
441                 max = 'max'
442                 bytes = 'bytes'
443             else:
444                 min = 'minexempt'
445                 max = 'maxexempt'
446                 bytes = 'exemptbytes'
447
448             # Slice ID
449             id = int(params.group(1), 16) & 0x0FFF;
450
451             if rates.has_key(id):
452                 rate = rates[id]
453             else:
454                 rate = {'id': id}
455
456             # Parse share
457             rate['share'] = 1
458             m = re.search(r"quantum (\d+)", line)
459             if m is not None:
460                 rate['share'] = int(m.group(1)) / quantum
461
462             # Parse minrate
463             rate[min] = bwmin
464             m = re.search(r"rate (\w+)", line)
465             if m is not None:
466                 rate[min] = get_tc_rate(m.group(1))
467
468             # Parse maxrate
469             rate[max] = bwmax
470             m = re.search(r"ceil (\w+)", line)
471             if m is not None:
472                 rate[max] = get_tc_rate(m.group(1))
473
474             # Which statistics to parse
475             rate['stats'] = bytes
476
477             rates[id] = rate
478
479         elif stats is not None:
480             if rate is not None:
481                 rate[rate['stats']] = int(stats.group(1))
482
483         elif ignore is not None:
484             rate = None
485
486         # Keep parsing until we get everything
487         if rate is not None and \
488            rate.has_key('min') and rate.has_key('minexempt') and \
489            rate.has_key('max') and rate.has_key('maxexempt') and \
490            rate.has_key('bytes') and rate.has_key('exemptbytes'):
491             params = (rate['id'], rate['share'],
492                       rate['min'], rate['max'],
493                       rate['minexempt'], rate['maxexempt'],
494                       rate['bytes'], rate['exemptbytes'])
495             if xid is None:
496                 # Return a list of parameters
497                 ret.append(params)
498                 rate = None
499             elif xid == rate['id']:
500                 # Return the parameters for this class
501                 ret = params
502                 break
503
504     return ret
505
506
507 def on(xid, dev = dev, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None):
508     """
509     Apply specified bandwidth limit to the specified slice xid
510     """
511
512     # Get defaults from current state if available
513     cap = get(xid, dev)
514     if cap is not None:
515         if share is None:
516             share = cap[1]
517         if minrate is None:
518             minrate = cap[2]
519         if maxrate is None:
520             maxrate = cap[3]
521         if minexemptrate is None:
522             minexemptrate = cap[4]
523         if maxexemptrate is None:
524             maxexemptrate = cap[5]
525
526     # Figure out what the current node bandwidth cap is
527     bwcap = get_bwcap()
528
529     # Set defaults
530     if share is None:
531         share = default_share
532     if minrate is None:
533         minrate = bwmin
534     else:
535         minrate = get_tc_rate(minrate)
536     if maxrate is None:
537         maxrate = bwcap
538     else:
539         maxrate = get_tc_rate(maxrate)
540     if minexemptrate is None:
541         minexemptrate = minrate
542     else:
543         minexemptrate = get_tc_rate(minexemptrate)
544     if maxexemptrate is None:
545         maxexemptrate = bwmax
546     else:
547         maxexemptrate = get_tc_rate(maxexemptrate)
548
549     # Sanity checks
550     if maxrate < bwmin:
551         maxrate = bwmin
552     if maxrate > bwcap:
553         maxrate = bwcap
554     if minrate < bwmin:
555         minrate = bwmin
556     if minrate > maxrate:
557         minrate = maxrate
558     if maxexemptrate < bwmin:
559         maxexemptrate = bwmin
560     if maxexemptrate > bwmax:
561         maxexemptrate = bwmax
562     if minexemptrate < bwmin:
563         minexemptrate = bwmin
564     if minexemptrate > maxexemptrate:
565         minexemptrate = maxexemptrate
566
567     # Set up subclasses for the slice
568     tc("class replace dev %s parent 1:100 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \
569        (dev, default_minor | xid, minrate, maxrate, share * quantum))
570
571     tc("class replace dev %s parent 1:20 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \
572        (dev, exempt_minor | xid, minexemptrate, maxexemptrate, share * quantum))
573
574     # Attach a FIFO to each subclass, which helps to throttle back
575     # processes that are sending faster than the token buckets can
576     # support.
577     tc("qdisc replace dev %s parent 1:%x handle %x pfifo" % \
578        (dev, default_minor | xid, default_minor | xid))
579
580     tc("qdisc replace dev %s parent 1:%x handle %x pfifo" % \
581        (dev, exempt_minor | xid, exempt_minor | xid))
582
583
584 def set(xid, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None, dev = dev ):
585     on(xid = xid, dev = dev, share = share,
586        minrate = minrate, maxrate = maxrate,
587        minexemptrate = minexemptrate, maxexemptrate = maxexemptrate)
588
589
590 # Remove class associated with specified slice xid. If further packets
591 # are seen from this slice, they will be classified into the default
592 # class 1:1FFF.
593 def off(xid, dev = dev):
594     """
595     Remove class associated with specified slice xid. If further
596     packets are seen from this slice, they will be classified into the
597     default class 1:1FFF.
598     """
599
600     cap = get(xid, dev)
601     if cap is not None:
602         tc("class del dev %s classid 1:%x" % (dev, default_minor | xid))
603         tc("class del dev %s classid 1:%x" % (dev, exempt_minor | xid))
604
605
606 def exempt_init(group_name, node_ips):
607     """
608     Initialize the list of destinations exempt from the node bandwidth
609     (burst) cap.
610     """
611
612     # Check of set exists
613     set = run("/sbin/ipset -S " + group_name)
614     if set == None:
615         # Create a hashed IP set of all of these destinations
616         lines = ["-N %s iphash" % group_name]
617         add_cmd = "-A %s " % group_name
618         lines += [(add_cmd + ip) for ip in node_ips]
619         lines += ["COMMIT"]
620         restore = "\n".join(lines) + "\n"
621         run("/sbin/ipset -R", restore)
622     else: # set exists
623         # Check all hosts and add missing.
624         for nodeip in node_ips:
625             if not run("/sbin/ipset -T %s %s" % (group_name, nodeip)):
626                run("/sbin/ipset -A %s %s" % (group_name, nodeip))
627
628
629 def usage():
630     bwcap_description = format_tc_rate(get_bwcap())
631         
632     print """
633 Usage:
634
635 %s [OPTION]... [COMMAND] [ARGUMENT]...
636
637 Options:
638         -d device   Network interface (default: %s)
639         -r rate         Node bandwidth cap (default: %s)
640         -q quantum      Share multiplier (default: %d bytes)
641         -n              Print rates in numeric bits per second
642         -v              Enable verbose debug messages
643         -h              This message
644
645 Commands:
646         init
647                 (Re)initialize all bandwidth parameters
648         on slice [share|-] [minrate|-] [maxrate|-] [minexemptrate|-] [maxexemptrate|-]
649                 Set bandwidth parameter(s) for the specified slice
650         off slice
651                 Remove all bandwidth parameters for the specified slice
652         get
653                 Get all bandwidth parameters for all slices
654         get slice
655                 Get bandwidth parameters for the specified slice
656 """ % (sys.argv[0], dev, bwcap_description, quantum)
657     sys.exit(1)
658     
659
660 def main():
661     global dev, quantum, verbose
662
663     # Defaults
664     numeric = False
665     bwcap = get_bwcap()
666
667     (opts, argv) = getopt.getopt(sys.argv[1:], "d:nr:q:vh")
668     for (opt, optval) in opts:
669         if opt == '-d':
670             dev = optval
671         elif opt == '-n':
672             numeric = True
673         elif opt == '-r':
674             bwcap = get_tc_rate(optval)
675         elif opt == '-q':
676             quantum = int(optval)
677         elif opt == '-v':
678             verbose += 1
679         elif opt == '-h':
680             usage()
681
682     if bwcap == -1:
683         return 0
684
685     if len(argv):
686         if argv[0] == "init" or (argv[0] == "on" and len(argv) == 1):
687             # (Re)initialize
688             init(dev, get_tc_rate(bwcap))
689
690         elif argv[0] == "get" or argv[0] == "show":
691             # Show
692             if len(argv) >= 2:
693                 # Show a particular slice
694                 xid = get_xid(argv[1])
695                 if xid is None:
696                     sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1])
697                     usage()
698                 params = get(xid, dev)
699                 if params is None:
700                     paramslist = []
701                 else:
702                     paramslist = [params]
703             else:
704                 # Show all slices
705                 paramslist = get(None, dev)
706
707             for (xid, share,
708                  minrate, maxrate,
709                  minexemptrate, maxexemptrate,
710                  bytes, exemptbytes) in paramslist:
711                 slice = get_slice(xid)
712                 if slice is None:
713                     # Orphaned (not associated with a slice) class
714                     slice = "%d?" % xid
715                 if numeric:
716                     print "%s %d %d %d %d %d %d %d" % \
717                           (slice, share,
718                            minrate, maxrate,
719                            minexemptrate, maxexemptrate,
720                            bytes, exemptbytes)
721                 else:
722                     print "%s %d %s %s %s %s %s %s" % \
723                           (slice, share,
724                            format_tc_rate(minrate), format_tc_rate(maxrate),
725                            format_tc_rate(minexemptrate), format_tc_rate(maxexemptrate),
726                            format_bytes(bytes), format_bytes(exemptbytes))
727
728         elif len(argv) >= 2:
729             # slice, ...
730             xid = get_xid(argv[1])
731             if xid is None:
732                 sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1])
733                 usage()
734
735             if argv[0] == "on" or argv[0] == "add" or argv[0] == "replace" or argv[0] == "set":
736                 # Enable cap
737                 args = []
738                 if len(argv) >= 3:
739                     # ... share, minrate, maxrate, minexemptrate, maxexemptrate
740                     casts = [int, get_tc_rate, get_tc_rate, get_tc_rate, get_tc_rate]
741                     for i, arg in enumerate(argv[2:]):
742                         if i >= len(casts):
743                             break
744                         if arg == "-":
745                             args.append(None)
746                         else:
747                             args.append(casts[i](arg))
748                 on(xid, dev, *args)
749
750             elif argv[0] == "off" or argv[0] == "del":
751                 # Disable cap
752                 off(xid, dev)
753
754             else:
755                 usage()
756
757         else:
758             usage()
759
760
761 if __name__ == '__main__':
762     main()