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