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