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