cosmetic - normalize diffs
[plnode-utils.git] / bwlimit_vs.py
1 #!/usr/bin/python
2 #
3 # This file is under git as plnode-utils/bwlimit_vs.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 # 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 def set(xid, share = None, minrate = None, maxrate = None, minexemptrate = None, maxexemptrate = None, dev = dev ):
583     on(xid = xid, dev = dev, share = share,
584        minrate = minrate, maxrate = maxrate,
585        minexemptrate = minexemptrate, maxexemptrate = maxexemptrate)
586
587
588 # Remove class associated with specified slice xid. If further packets
589 # are seen from this slice, they will be classified into the default
590 # class 1:1FFF.
591 def off(xid, dev = dev):
592     """
593     Remove class associated with specified slice xid. If further
594     packets are seen from this slice, they will be classified into the
595     default class 1:1FFF.
596     """
597
598     cap = get(xid, dev)
599     if cap is not None:
600         tc("class del dev %s classid 1:%x" % (dev, default_minor | xid))
601         tc("class del dev %s classid 1:%x" % (dev, exempt_minor | xid))
602
603
604 def exempt_init(group_name, node_ips):
605     """
606     Initialize the list of destinations exempt from the node bandwidth
607     (burst) cap.
608     """
609
610     # Check of set exists
611     set = run("/sbin/ipset -S " + group_name)
612     if set == None:
613         # Create a hashed IP set of all of these destinations
614         lines = ["-N %s iphash" % group_name]
615         add_cmd = "-A %s " % group_name
616         lines += [(add_cmd + ip) for ip in node_ips]
617         lines += ["COMMIT"]
618         restore = "\n".join(lines) + "\n"
619         run("/sbin/ipset -R", restore)
620     else: # set exists
621         # Check all hosts and add missing.
622         for nodeip in node_ips:
623             if not run("/sbin/ipset -T %s %s" % (group_name, nodeip)):
624                run("/sbin/ipset -A %s %s" % (group_name, nodeip))
625
626
627 def usage():
628     bwcap_description = format_tc_rate(get_bwcap())
629         
630     print """
631 Usage:
632
633 %s [OPTION]... [COMMAND] [ARGUMENT]...
634
635 Options:
636         -d device   Network interface (default: %s)
637         -r rate         Node bandwidth cap (default: %s)
638         -q quantum      Share multiplier (default: %d bytes)
639         -n              Print rates in numeric bits per second
640         -v              Enable verbose debug messages
641         -h              This message
642
643 Commands:
644         init
645                 (Re)initialize all bandwidth parameters
646         on slice [share|-] [minrate|-] [maxrate|-] [minexemptrate|-] [maxexemptrate|-]
647                 Set bandwidth parameter(s) for the specified slice
648         off slice
649                 Remove all bandwidth parameters for the specified slice
650         get
651                 Get all bandwidth parameters for all slices
652         get slice
653                 Get bandwidth parameters for the specified slice
654 """ % (sys.argv[0], dev, bwcap_description, quantum)
655     sys.exit(1)
656     
657
658 def main():
659     global dev, quantum, verbose
660
661     # Defaults
662     numeric = False
663     bwcap = None
664
665     (opts, argv) = getopt.getopt(sys.argv[1:], "d:nr:q:vh")
666     for (opt, optval) in opts:
667         if opt == '-d':
668             dev = optval
669         elif opt == '-n':
670             numeric = True
671         elif opt == '-r':
672             bwcap = get_tc_rate(optval)
673         elif opt == '-q':
674             quantum = int(optval)
675         elif opt == '-v':
676             verbose += 1
677         elif opt == '-h':
678             usage()
679
680     if not bwcap:
681         bwcap = get_bwcap(dev)
682
683     if bwcap == -1:
684         return 0
685
686     if len(argv):
687         if argv[0] == "init" or (argv[0] == "on" and len(argv) == 1):
688             # (Re)initialize
689             init(dev, get_tc_rate(bwcap))
690
691         elif argv[0] == "get" or argv[0] == "show":
692             # Show
693             if len(argv) >= 2:
694                 # Show a particular slice
695                 xid = get_xid(argv[1])
696                 if xid is None:
697                     sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1])
698                     usage()
699                 params = get(xid, dev)
700                 if params is None:
701                     paramslist = []
702                 else:
703                     paramslist = [params]
704             else:
705                 # Show all slices
706                 paramslist = get(None, dev)
707
708             for (xid, share,
709                  minrate, maxrate,
710                  minexemptrate, maxexemptrate,
711                  bytes, exemptbytes) in paramslist:
712                 slice = get_slice(xid)
713                 if slice is None:
714                     # Orphaned (not associated with a slice) class
715                     slice = "%d?" % xid
716                 if numeric:
717                     print "%s %d %d %d %d %d %d %d" % \
718                           (slice, share,
719                            minrate, maxrate,
720                            minexemptrate, maxexemptrate,
721                            bytes, exemptbytes)
722                 else:
723                     print "%s %d %s %s %s %s %s %s" % \
724                           (slice, share,
725                            format_tc_rate(minrate), format_tc_rate(maxrate),
726                            format_tc_rate(minexemptrate), format_tc_rate(maxexemptrate),
727                            format_bytes(bytes), format_bytes(exemptbytes))
728
729         elif len(argv) >= 2:
730             # slice, ...
731             xid = get_xid(argv[1])
732             if xid is None:
733                 sys.stderr.write("Error: Invalid slice name or context '%s'\n" % argv[1])
734                 usage()
735
736             if argv[0] == "on" or argv[0] == "add" or argv[0] == "replace" or argv[0] == "set":
737                 # Enable cap
738                 args = []
739                 if len(argv) >= 3:
740                     # ... share, minrate, maxrate, minexemptrate, maxexemptrate
741                     casts = [int, get_tc_rate, get_tc_rate, get_tc_rate, get_tc_rate]
742                     for i, arg in enumerate(argv[2:]):
743                         if i >= len(casts):
744                             break
745                         if arg == "-":
746                             args.append(None)
747                         else:
748                             args.append(casts[i](arg))
749                 on(xid, dev, *args)
750
751             elif argv[0] == "off" or argv[0] == "del":
752                 # Disable cap
753                 off(xid, dev)
754
755             else:
756                 usage()
757
758         else:
759             usage()
760
761
762 if __name__ == '__main__':
763     main()