Revert "Merge pull request #921 from oOraph/dev/exclude_subnets_from_traffic_shapping2"
This reverts commit ef076afac1af0b9a8446f72e3343666567bc04dc, reversing changes made to 597408952e3e7247fb0deef26a3a935c405aa0cf. Signed-off-by: h0nIg <h0nIg@users.noreply.github.com>
This commit is contained in:
parent
8ad0361964
commit
d44bbf28af
@ -17,7 +17,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@ -61,13 +60,6 @@ var _ = Describe("Basic PTP using cnitool", func() {
|
|||||||
netConfPath, err := filepath.Abs("./testdata")
|
netConfPath, err := filepath.Abs("./testdata")
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
// Flush ipam stores to avoid conflicts
|
|
||||||
err = os.RemoveAll("/tmp/chained-ptp-bandwidth-test")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
err = os.RemoveAll("/tmp/basic-ptp-test")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
env = TestEnv([]string{
|
env = TestEnv([]string{
|
||||||
"CNI_PATH=" + cniPath,
|
"CNI_PATH=" + cniPath,
|
||||||
"NETCONFPATH=" + netConfPath,
|
"NETCONFPATH=" + netConfPath,
|
||||||
@ -90,7 +82,6 @@ var _ = Describe("Basic PTP using cnitool", func() {
|
|||||||
env.runInNS(hostNS, cnitoolBin, "add", netName, contNS.LongName())
|
env.runInNS(hostNS, cnitoolBin, "add", netName, contNS.LongName())
|
||||||
|
|
||||||
addrOutput := env.runInNS(contNS, "ip", "addr")
|
addrOutput := env.runInNS(contNS, "ip", "addr")
|
||||||
|
|
||||||
Expect(addrOutput).To(ContainSubstring(expectedIPPrefix))
|
Expect(addrOutput).To(ContainSubstring(expectedIPPrefix))
|
||||||
|
|
||||||
env.runInNS(hostNS, cnitoolBin, "del", netName, contNS.LongName())
|
env.runInNS(hostNS, cnitoolBin, "del", netName, contNS.LongName())
|
||||||
@ -154,13 +145,9 @@ var _ = Describe("Basic PTP using cnitool", func() {
|
|||||||
|
|
||||||
chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS1.LongName())
|
chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS1.LongName())
|
||||||
basicBridgeEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS2.LongName())
|
basicBridgeEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS2.LongName())
|
||||||
|
|
||||||
contNS1.Del()
|
|
||||||
contNS2.Del()
|
|
||||||
hostNS.Del()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("limits traffic only on the restricted bandwidth veth device", func() {
|
Measure("limits traffic only on the restricted bandwidth veth device", func(b Benchmarker) {
|
||||||
ipRegexp := regexp.MustCompile(`10\.1[12]\.2\.\d{1,3}`)
|
ipRegexp := regexp.MustCompile(`10\.1[12]\.2\.\d{1,3}`)
|
||||||
|
|
||||||
By(fmt.Sprintf("adding %s to %s\n\n", "chained-bridge-bandwidth", contNS1.ShortName()))
|
By(fmt.Sprintf("adding %s to %s\n\n", "chained-bridge-bandwidth", contNS1.ShortName()))
|
||||||
@ -181,23 +168,21 @@ var _ = Describe("Basic PTP using cnitool", func() {
|
|||||||
By(fmt.Sprintf("starting echo server in %s\n\n", contNS2.ShortName()))
|
By(fmt.Sprintf("starting echo server in %s\n\n", contNS2.ShortName()))
|
||||||
basicBridgePort, basicBridgeSession = startEchoServerInNamespace(contNS2)
|
basicBridgePort, basicBridgeSession = startEchoServerInNamespace(contNS2)
|
||||||
|
|
||||||
packetInBytes := 3000
|
packetInBytes := 20000 // The shaper needs to 'warm'. Send enough to cause it to throttle,
|
||||||
|
// balanced by run time.
|
||||||
|
|
||||||
By(fmt.Sprintf("sending tcp traffic to the chained, bridged, traffic shaped container on ip address '%s:%d'\n\n", chainedBridgeIP, chainedBridgeBandwidthPort))
|
By(fmt.Sprintf("sending tcp traffic to the chained, bridged, traffic shaped container on ip address '%s:%d'\n\n", chainedBridgeIP, chainedBridgeBandwidthPort))
|
||||||
start := time.Now()
|
runtimeWithLimit := b.Time("with chained bridge and bandwidth plugins", func() {
|
||||||
makeTCPClientInNS(hostNS.ShortName(), chainedBridgeIP, chainedBridgeBandwidthPort, packetInBytes)
|
makeTCPClientInNS(hostNS.ShortName(), chainedBridgeIP, chainedBridgeBandwidthPort, packetInBytes)
|
||||||
runtimeWithLimit := time.Since(start)
|
})
|
||||||
|
|
||||||
log.Printf("Runtime with qos limit %.2f seconds", runtimeWithLimit.Seconds())
|
|
||||||
|
|
||||||
By(fmt.Sprintf("sending tcp traffic to the basic bridged container on ip address '%s:%d'\n\n", basicBridgeIP, basicBridgePort))
|
By(fmt.Sprintf("sending tcp traffic to the basic bridged container on ip address '%s:%d'\n\n", basicBridgeIP, basicBridgePort))
|
||||||
start = time.Now()
|
runtimeWithoutLimit := b.Time("with basic bridged plugin", func() {
|
||||||
makeTCPClientInNS(hostNS.ShortName(), basicBridgeIP, basicBridgePort, packetInBytes)
|
makeTCPClientInNS(hostNS.ShortName(), basicBridgeIP, basicBridgePort, packetInBytes)
|
||||||
runtimeWithoutLimit := time.Since(start)
|
})
|
||||||
log.Printf("Runtime without qos limit %.2f seconds", runtimeWithLimit.Seconds())
|
|
||||||
|
|
||||||
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
|
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
|
||||||
})
|
}, 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
3
integration/testdata/basic-ptp.json
vendored
3
integration/testdata/basic-ptp.json
vendored
@ -6,7 +6,6 @@
|
|||||||
"mtu": 512,
|
"mtu": 512,
|
||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"subnet": "10.1.2.0/24",
|
"subnet": "10.1.2.0/24"
|
||||||
"dataDir": "/tmp/basic-ptp-test"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,7 @@
|
|||||||
"mtu": 512,
|
"mtu": 512,
|
||||||
"ipam": {
|
"ipam": {
|
||||||
"type": "host-local",
|
"type": "host-local",
|
||||||
"subnet": "10.9.2.0/24",
|
"subnet": "10.9.2.0/24"
|
||||||
"dataDir": "/tmp/chained-ptp-bandwidth-test"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,563 +0,0 @@
|
|||||||
// Copyright 2023 CNI authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
"github.com/vishvananda/netlink"
|
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/skel"
|
|
||||||
types100 "github.com/containernetworking/cni/pkg/types/100"
|
|
||||||
"github.com/containernetworking/plugins/pkg/ns"
|
|
||||||
"github.com/containernetworking/plugins/pkg/testutils"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = Describe("bandwidth config test", func() {
|
|
||||||
var (
|
|
||||||
hostNs ns.NetNS
|
|
||||||
containerNs ns.NetNS
|
|
||||||
ifbDeviceName string
|
|
||||||
hostIfname string
|
|
||||||
containerIfname string
|
|
||||||
hostIP net.IP
|
|
||||||
containerIP net.IP
|
|
||||||
hostIfaceMTU int
|
|
||||||
)
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
hostIfname = "host-veth"
|
|
||||||
containerIfname = "container-veth"
|
|
||||||
|
|
||||||
hostNs, err = testutils.NewNS()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
containerNs, err = testutils.NewNS()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
hostIP = net.IP{169, 254, 0, 1}
|
|
||||||
containerIP = net.IP{10, 254, 0, 1}
|
|
||||||
hostIfaceMTU = 1024
|
|
||||||
ifbDeviceName = "bwpa8eda89404b7"
|
|
||||||
|
|
||||||
createVeth(hostNs, hostIfname, containerNs, containerIfname, hostIP, containerIP, hostIfaceMTU)
|
|
||||||
})
|
|
||||||
|
|
||||||
AfterEach(func() {
|
|
||||||
Expect(containerNs.Close()).To(Succeed())
|
|
||||||
Expect(testutils.UnmountNS(containerNs)).To(Succeed())
|
|
||||||
Expect(hostNs.Close()).To(Succeed())
|
|
||||||
Expect(testutils.UnmountNS(hostNs)).To(Succeed())
|
|
||||||
})
|
|
||||||
|
|
||||||
// Bandwidth requires host-side interface info, and thus only
|
|
||||||
// supports 0.3.0 and later CNI versions
|
|
||||||
for _, ver := range []string{"0.3.0", "0.3.1", "0.4.0", "1.0.0"} {
|
|
||||||
// Redefine ver inside for scope so real value is picked up by each dynamically defined It()
|
|
||||||
// See Gingkgo's "Patterns for dynamically generating tests" documentation.
|
|
||||||
ver := ver
|
|
||||||
|
|
||||||
Describe("cmdADD", func() {
|
|
||||||
It(fmt.Sprintf("[%s] fails with invalid UnshapedSubnets", ver), func() {
|
|
||||||
conf := fmt.Sprintf(`{
|
|
||||||
"cniVersion": "%s",
|
|
||||||
"name": "cni-plugin-bandwidth-test",
|
|
||||||
"type": "bandwidth",
|
|
||||||
"ingressRate": 123,
|
|
||||||
"ingressBurst": 123,
|
|
||||||
"egressRate": 123,
|
|
||||||
"egressBurst": 123,
|
|
||||||
"unshapedSubnets": ["10.0.0.0/8", "hello"],
|
|
||||||
"prevResult": {
|
|
||||||
"interfaces": [
|
|
||||||
{
|
|
||||||
"name": "%s",
|
|
||||||
"sandbox": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "%s",
|
|
||||||
"sandbox": "%s"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ips": [
|
|
||||||
{
|
|
||||||
"version": "4",
|
|
||||||
"address": "%s/24",
|
|
||||||
"gateway": "10.0.0.1",
|
|
||||||
"interface": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"routes": []
|
|
||||||
}
|
|
||||||
}`, ver, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy",
|
|
||||||
Netns: containerNs.Path(),
|
|
||||||
IfName: "eth0",
|
|
||||||
StdinData: []byte(conf),
|
|
||||||
}
|
|
||||||
|
|
||||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
|
|
||||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
|
||||||
Expect(err).To(MatchError("bad subnet \"hello\" provided, details invalid CIDR address: hello"))
|
|
||||||
return nil
|
|
||||||
})).To(Succeed())
|
|
||||||
})
|
|
||||||
|
|
||||||
It(fmt.Sprintf("[%s] fails with invalid ShapedSubnets", ver), func() {
|
|
||||||
conf := fmt.Sprintf(`{
|
|
||||||
"cniVersion": "%s",
|
|
||||||
"name": "cni-plugin-bandwidth-test",
|
|
||||||
"type": "bandwidth",
|
|
||||||
"ingressRate": 123,
|
|
||||||
"ingressBurst": 123,
|
|
||||||
"egressRate": 123,
|
|
||||||
"egressBurst": 123,
|
|
||||||
"ShapedSubnets": ["10.0.0.0/8", "hello"],
|
|
||||||
"prevResult": {
|
|
||||||
"interfaces": [
|
|
||||||
{
|
|
||||||
"name": "%s",
|
|
||||||
"sandbox": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "%s",
|
|
||||||
"sandbox": "%s"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ips": [
|
|
||||||
{
|
|
||||||
"version": "4",
|
|
||||||
"address": "%s/24",
|
|
||||||
"gateway": "10.0.0.1",
|
|
||||||
"interface": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"routes": []
|
|
||||||
}
|
|
||||||
}`, ver, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy",
|
|
||||||
Netns: containerNs.Path(),
|
|
||||||
IfName: "eth0",
|
|
||||||
StdinData: []byte(conf),
|
|
||||||
}
|
|
||||||
|
|
||||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
|
|
||||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
|
||||||
Expect(err).To(MatchError("bad subnet \"hello\" provided, details invalid CIDR address: hello"))
|
|
||||||
return nil
|
|
||||||
})).To(Succeed())
|
|
||||||
})
|
|
||||||
|
|
||||||
It(fmt.Sprintf("[%s] fails with both ShapedSubnets and UnshapedSubnets specified", ver), func() {
|
|
||||||
conf := fmt.Sprintf(`{
|
|
||||||
"cniVersion": "%s",
|
|
||||||
"name": "cni-plugin-bandwidth-test",
|
|
||||||
"type": "bandwidth",
|
|
||||||
"ingressRate": 123,
|
|
||||||
"ingressBurst": 123,
|
|
||||||
"egressRate": 123,
|
|
||||||
"egressBurst": 123,
|
|
||||||
"shapedSubnets": ["10.0.0.0/8"],
|
|
||||||
"unshapedSubnets": ["192.168.0.0/16"],
|
|
||||||
"prevResult": {
|
|
||||||
"interfaces": [
|
|
||||||
{
|
|
||||||
"name": "%s",
|
|
||||||
"sandbox": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "%s",
|
|
||||||
"sandbox": "%s"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ips": [
|
|
||||||
{
|
|
||||||
"version": "4",
|
|
||||||
"address": "%s/24",
|
|
||||||
"gateway": "10.0.0.1",
|
|
||||||
"interface": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"routes": []
|
|
||||||
}
|
|
||||||
}`, ver, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy",
|
|
||||||
Netns: containerNs.Path(),
|
|
||||||
IfName: "eth0",
|
|
||||||
StdinData: []byte(conf),
|
|
||||||
}
|
|
||||||
|
|
||||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
|
|
||||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
|
||||||
Expect(err).To(MatchError("unshapedSubnets and shapedSubnets cannot be both specified, one of them should be discarded"))
|
|
||||||
return nil
|
|
||||||
})).To(Succeed())
|
|
||||||
})
|
|
||||||
|
|
||||||
It(fmt.Sprintf("[%s] fails an invalid ingress config", ver), func() {
|
|
||||||
conf := fmt.Sprintf(`{
|
|
||||||
"cniVersion": "%s",
|
|
||||||
"name": "cni-plugin-bandwidth-test",
|
|
||||||
"type": "bandwidth",
|
|
||||||
"ingressRate": 0,
|
|
||||||
"ingressBurst": 123,
|
|
||||||
"egressRate": 123,
|
|
||||||
"egressBurst": 123,
|
|
||||||
"prevResult": {
|
|
||||||
"interfaces": [
|
|
||||||
{
|
|
||||||
"name": "%s",
|
|
||||||
"sandbox": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "%s",
|
|
||||||
"sandbox": "%s"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ips": [
|
|
||||||
{
|
|
||||||
"version": "4",
|
|
||||||
"address": "%s/24",
|
|
||||||
"gateway": "10.0.0.1",
|
|
||||||
"interface": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"routes": []
|
|
||||||
}
|
|
||||||
}`, ver, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy",
|
|
||||||
Netns: containerNs.Path(),
|
|
||||||
IfName: "eth0",
|
|
||||||
StdinData: []byte(conf),
|
|
||||||
}
|
|
||||||
|
|
||||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
|
|
||||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
|
||||||
Expect(err).To(MatchError("if burst is set, rate must also be set"))
|
|
||||||
return nil
|
|
||||||
})).To(Succeed())
|
|
||||||
})
|
|
||||||
|
|
||||||
It(fmt.Sprintf("[%s] fails an invalid egress config", ver), func() {
|
|
||||||
conf := fmt.Sprintf(`{
|
|
||||||
"cniVersion": "%s",
|
|
||||||
"name": "cni-plugin-bandwidth-test",
|
|
||||||
"type": "bandwidth",
|
|
||||||
"ingressRate": 123,
|
|
||||||
"ingressBurst": 123,
|
|
||||||
"egressRate": 0,
|
|
||||||
"egressBurst": 123,
|
|
||||||
"prevResult": {
|
|
||||||
"interfaces": [
|
|
||||||
{
|
|
||||||
"name": "%s",
|
|
||||||
"sandbox": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "%s",
|
|
||||||
"sandbox": "%s"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ips": [
|
|
||||||
{
|
|
||||||
"version": "4",
|
|
||||||
"address": "%s/24",
|
|
||||||
"gateway": "10.0.0.1",
|
|
||||||
"interface": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"routes": []
|
|
||||||
}
|
|
||||||
}`, ver, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy",
|
|
||||||
Netns: containerNs.Path(),
|
|
||||||
IfName: "eth0",
|
|
||||||
StdinData: []byte(conf),
|
|
||||||
}
|
|
||||||
|
|
||||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
|
|
||||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
|
||||||
Expect(err).To(MatchError("if burst is set, rate must also be set"))
|
|
||||||
return nil
|
|
||||||
})).To(Succeed())
|
|
||||||
})
|
|
||||||
|
|
||||||
// Runtime config parameters are expected to be preempted by the global config ones whenever specified
|
|
||||||
It(fmt.Sprintf("[%s] should apply static config when both static config and runtime config exist", ver), func() {
|
|
||||||
conf := fmt.Sprintf(`{
|
|
||||||
"cniVersion": "%s",
|
|
||||||
"name": "cni-plugin-bandwidth-test",
|
|
||||||
"type": "bandwidth",
|
|
||||||
"ingressRate": 0,
|
|
||||||
"ingressBurst": 0,
|
|
||||||
"egressRate": 123,
|
|
||||||
"egressBurst": 123,
|
|
||||||
"unshapedSubnets": ["192.168.0.0/24"],
|
|
||||||
"runtimeConfig": {
|
|
||||||
"bandWidth": {
|
|
||||||
"ingressRate": 8,
|
|
||||||
"ingressBurst": 8,
|
|
||||||
"egressRate": 16,
|
|
||||||
"egressBurst": 9,
|
|
||||||
"unshapedSubnets": ["10.0.0.0/8", "fd00:db8:abcd:1234:e000::/68"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"prevResult": {
|
|
||||||
"interfaces": [
|
|
||||||
{
|
|
||||||
"name": "%s",
|
|
||||||
"sandbox": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "%s",
|
|
||||||
"sandbox": "%s"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ips": [
|
|
||||||
{
|
|
||||||
"version": "4",
|
|
||||||
"address": "%s/24",
|
|
||||||
"gateway": "10.0.0.1",
|
|
||||||
"interface": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"routes": []
|
|
||||||
}
|
|
||||||
}`, ver, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy",
|
|
||||||
Netns: containerNs.Path(),
|
|
||||||
IfName: containerIfname,
|
|
||||||
StdinData: []byte(conf),
|
|
||||||
}
|
|
||||||
|
|
||||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
r, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
|
||||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
|
||||||
result, err := types100.GetResult(r)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
Expect(result.Interfaces).To(HaveLen(3))
|
|
||||||
Expect(result.Interfaces[2].Name).To(Equal(ifbDeviceName))
|
|
||||||
Expect(result.Interfaces[2].Sandbox).To(Equal(""))
|
|
||||||
|
|
||||||
ifbLink, err := netlink.LinkByName(ifbDeviceName)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(ifbLink.Attrs().MTU).To(Equal(hostIfaceMTU))
|
|
||||||
|
|
||||||
qdiscs, err := netlink.QdiscList(ifbLink)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
Expect(qdiscs).To(HaveLen(1))
|
|
||||||
Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index))
|
|
||||||
Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Htb{}))
|
|
||||||
Expect(qdiscs[0].(*netlink.Htb).Defcls).To(Equal(uint32(ShapedClassMinorID)))
|
|
||||||
|
|
||||||
classes, err := netlink.ClassList(ifbLink, qdiscs[0].Attrs().Handle)
|
|
||||||
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(classes).To(HaveLen(2))
|
|
||||||
|
|
||||||
// Uncapped class
|
|
||||||
Expect(classes[0]).To(BeAssignableToTypeOf(&netlink.HtbClass{}))
|
|
||||||
Expect(classes[0].(*netlink.HtbClass).Handle).To(Equal(netlink.MakeHandle(1, 1)))
|
|
||||||
Expect(classes[0].(*netlink.HtbClass).Rate).To(Equal(UncappedRate))
|
|
||||||
Expect(classes[0].(*netlink.HtbClass).Buffer).To(Equal(uint32(0)))
|
|
||||||
Expect(classes[0].(*netlink.HtbClass).Ceil).To(Equal(UncappedRate))
|
|
||||||
Expect(classes[0].(*netlink.HtbClass).Cbuffer).To(Equal(uint32(0)))
|
|
||||||
|
|
||||||
// Class with traffic shapping settings
|
|
||||||
Expect(classes[1]).To(BeAssignableToTypeOf(&netlink.HtbClass{}))
|
|
||||||
Expect(classes[1].(*netlink.HtbClass).Handle).To(Equal(netlink.MakeHandle(1, uint16(qdiscs[0].(*netlink.Htb).Defcls))))
|
|
||||||
Expect(classes[1].(*netlink.HtbClass).Rate).To(Equal(uint64(15)))
|
|
||||||
// Expect(classes[1].(*netlink.HtbClass).Buffer).To(Equal(uint32(7812500)))
|
|
||||||
Expect(classes[1].(*netlink.HtbClass).Ceil).To(Equal(uint64(30)))
|
|
||||||
// Expect(classes[1].(*netlink.HtbClass).Cbuffer).To(Equal(uint32(0)))
|
|
||||||
|
|
||||||
filters, err := netlink.FilterList(ifbLink, qdiscs[0].Attrs().Handle)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(filters).To(HaveLen(1))
|
|
||||||
|
|
||||||
// traffic to 192.168.0.0/24 redirected to uncapped class
|
|
||||||
Expect(filters[0]).To(BeAssignableToTypeOf(&netlink.U32{}))
|
|
||||||
Expect(filters[0].(*netlink.U32).Actions).To(BeEmpty())
|
|
||||||
Expect(filters[0].Attrs().Protocol).To(Equal(uint16(syscall.ETH_P_IP)))
|
|
||||||
Expect(filters[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index))
|
|
||||||
Expect(filters[0].Attrs().Priority).To(Equal(uint16(16)))
|
|
||||||
Expect(filters[0].Attrs().Parent).To(Equal(qdiscs[0].Attrs().Handle))
|
|
||||||
Expect(filters[0].(*netlink.U32).ClassId).To(Equal(netlink.MakeHandle(1, 1)))
|
|
||||||
|
|
||||||
filterSel := filters[0].(*netlink.U32).Sel
|
|
||||||
Expect(filterSel).To(BeAssignableToTypeOf(&netlink.TcU32Sel{}))
|
|
||||||
Expect(filterSel.Flags).To(Equal(uint8(netlink.TC_U32_TERMINAL)))
|
|
||||||
Expect(filterSel.Keys).To(HaveLen(1))
|
|
||||||
Expect(filterSel.Nkeys).To(Equal(uint8(1)))
|
|
||||||
|
|
||||||
// The filter should match to 192.168.0.0/24 dst address in other words it should be:
|
|
||||||
// match c0a80000/ffffff00 at 16
|
|
||||||
selKey := filterSel.Keys[0]
|
|
||||||
Expect(selKey.Val).To(Equal(uint32(192*math.Pow(256, 3) + 168*math.Pow(256, 2))))
|
|
||||||
Expect(selKey.Off).To(Equal(int32(16)))
|
|
||||||
Expect(selKey.Mask).To(Equal(uint32(255*math.Pow(256, 3) + 255*math.Pow(256, 2) + 255*256)))
|
|
||||||
|
|
||||||
hostVethLink, err := netlink.LinkByName(hostIfname)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
qdiscFilters, err := netlink.FilterList(hostVethLink, netlink.MakeHandle(0xffff, 0))
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
Expect(qdiscFilters).To(HaveLen(1))
|
|
||||||
Expect(qdiscFilters[0].(*netlink.U32).Actions[0].(*netlink.MirredAction).Ifindex).To(Equal(ifbLink.Attrs().Index))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})).To(Succeed())
|
|
||||||
|
|
||||||
// Container ingress (host egress)
|
|
||||||
Expect(hostNs.Do(func(n ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
|
|
||||||
vethLink, err := netlink.LinkByName(hostIfname)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
qdiscs, err := netlink.QdiscList(vethLink)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
// No ingress QoS just mirroring
|
|
||||||
Expect(qdiscs).To(HaveLen(2))
|
|
||||||
Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(vethLink.Attrs().Index))
|
|
||||||
Expect(qdiscs[0]).NotTo(BeAssignableToTypeOf(&netlink.Htb{}))
|
|
||||||
Expect(qdiscs[1]).NotTo(BeAssignableToTypeOf(&netlink.Htb{}))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})).To(Succeed())
|
|
||||||
})
|
|
||||||
|
|
||||||
It(fmt.Sprintf("[%s] should apply static config when both static config and runtime config exist (bad config example)", ver), func() {
|
|
||||||
conf := fmt.Sprintf(`{
|
|
||||||
"cniVersion": "%s",
|
|
||||||
"name": "cni-plugin-bandwidth-test",
|
|
||||||
"type": "bandwidth",
|
|
||||||
"ingressRate": 0,
|
|
||||||
"ingressBurst": 123,
|
|
||||||
"egressRate": 123,
|
|
||||||
"egressBurst": 123,
|
|
||||||
"runtimeConfig": {
|
|
||||||
"bandWidth": {
|
|
||||||
"ingressRate": 8,
|
|
||||||
"ingressBurst": 8,
|
|
||||||
"egressRate": 16,
|
|
||||||
"egressBurst": 9
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"prevResult": {
|
|
||||||
"interfaces": [
|
|
||||||
{
|
|
||||||
"name": "%s",
|
|
||||||
"sandbox": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "%s",
|
|
||||||
"sandbox": "%s"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ips": [
|
|
||||||
{
|
|
||||||
"version": "4",
|
|
||||||
"address": "%s/24",
|
|
||||||
"gateway": "10.0.0.1",
|
|
||||||
"interface": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"routes": []
|
|
||||||
}
|
|
||||||
}`, ver, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy",
|
|
||||||
Netns: containerNs.Path(),
|
|
||||||
IfName: "eth0",
|
|
||||||
StdinData: []byte(conf),
|
|
||||||
}
|
|
||||||
|
|
||||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
|
|
||||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
|
||||||
Expect(err).To(MatchError("if burst is set, rate must also be set"))
|
|
||||||
return nil
|
|
||||||
})).To(Succeed())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Describe("Validating input", func() {
|
|
||||||
It("Should allow only 4GB burst rate", func() {
|
|
||||||
err := validateRateAndBurst(5000, 4*1024*1024*1024*8-16) // 2 bytes less than the max should pass
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
err = validateRateAndBurst(5000, 4*1024*1024*1024*8) // we're 1 bit above MaxUint32
|
|
||||||
Expect(err).To(HaveOccurred())
|
|
||||||
err = validateRateAndBurst(0, 1)
|
|
||||||
Expect(err).To(HaveOccurred())
|
|
||||||
err = validateRateAndBurst(1, 0)
|
|
||||||
Expect(err).To(HaveOccurred())
|
|
||||||
err = validateRateAndBurst(0, 0)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("Should fail if both ShapedSubnets and UnshapedSubnets are specified", func() {
|
|
||||||
err := validateSubnets([]string{"10.0.0.0/8"}, []string{"192.168.0.0/24"})
|
|
||||||
Expect(err).To(HaveOccurred())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("Should fail if specified UnshapedSubnets are not valid CIDRs", func() {
|
|
||||||
err := validateSubnets([]string{"10.0.0.0/8", "hello"}, []string{})
|
|
||||||
Expect(err).To(HaveOccurred())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("Should fail if specified ShapedSubnets are not valid CIDRs", func() {
|
|
||||||
err := validateSubnets([]string{}, []string{"10.0.0.0/8", "hello"})
|
|
||||||
Expect(err).To(HaveOccurred())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
File diff suppressed because it is too large
Load Diff
@ -1,824 +0,0 @@
|
|||||||
// Copyright 2023 CNI authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
"github.com/onsi/gomega/gexec"
|
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/invoke"
|
|
||||||
"github.com/containernetworking/cni/pkg/skel"
|
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
|
||||||
types100 "github.com/containernetworking/cni/pkg/types/100"
|
|
||||||
"github.com/containernetworking/plugins/pkg/ns"
|
|
||||||
"github.com/containernetworking/plugins/pkg/testutils"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = Describe("bandwidth measure test", func() {
|
|
||||||
var (
|
|
||||||
hostNs ns.NetNS
|
|
||||||
containerNs ns.NetNS
|
|
||||||
hostIfname string
|
|
||||||
containerIfname string
|
|
||||||
hostIP net.IP
|
|
||||||
containerIP net.IP
|
|
||||||
hostIfaceMTU int
|
|
||||||
)
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
hostIfname = "host-veth"
|
|
||||||
containerIfname = "container-veth"
|
|
||||||
|
|
||||||
hostNs, err = testutils.NewNS()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
containerNs, err = testutils.NewNS()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
hostIP = net.IP{169, 254, 0, 1}
|
|
||||||
containerIP = net.IP{10, 254, 0, 1}
|
|
||||||
hostIfaceMTU = 1024
|
|
||||||
|
|
||||||
createVeth(hostNs, hostIfname, containerNs, containerIfname, hostIP, containerIP, hostIfaceMTU)
|
|
||||||
})
|
|
||||||
|
|
||||||
AfterEach(func() {
|
|
||||||
Expect(containerNs.Close()).To(Succeed())
|
|
||||||
Expect(testutils.UnmountNS(containerNs)).To(Succeed())
|
|
||||||
Expect(hostNs.Close()).To(Succeed())
|
|
||||||
Expect(testutils.UnmountNS(hostNs)).To(Succeed())
|
|
||||||
})
|
|
||||||
|
|
||||||
// Bandwidth requires host-side interface info, and thus only
|
|
||||||
// supports 0.3.0 and later CNI versions
|
|
||||||
for _, ver := range []string{"0.3.0", "0.3.1", "0.4.0", "1.0.0"} {
|
|
||||||
Describe(fmt.Sprintf("[%s] QoS effective", ver), func() {
|
|
||||||
Context(fmt.Sprintf("[%s] when chaining bandwidth plugin with PTP", ver), func() {
|
|
||||||
var ptpConf string
|
|
||||||
var rateInBits uint64
|
|
||||||
var burstInBits uint64
|
|
||||||
var packetInBytes int
|
|
||||||
var containerWithoutQoSNS ns.NetNS
|
|
||||||
var containerWithQoSNS ns.NetNS
|
|
||||||
var portServerWithQoS int
|
|
||||||
var portServerWithoutQoS int
|
|
||||||
|
|
||||||
var containerWithQoSRes types.Result
|
|
||||||
var containerWithoutQoSRes types.Result
|
|
||||||
var echoServerWithQoS *gexec.Session
|
|
||||||
var echoServerWithoutQoS *gexec.Session
|
|
||||||
var dataDir string
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
rateInBytes := 1000
|
|
||||||
rateInBits = uint64(rateInBytes * 8)
|
|
||||||
burstInBits = rateInBits * 2
|
|
||||||
|
|
||||||
// NOTE: Traffic shapping is not that precise at low rates, would be better to use higher rates + simple time+netcat for data transfer, rather than the provided
|
|
||||||
// client/server bin (limited to small amount of data)
|
|
||||||
packetInBytes = rateInBytes * 3
|
|
||||||
|
|
||||||
var err error
|
|
||||||
dataDir, err = os.MkdirTemp("", "bandwidth_linux_test")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
ptpConf = fmt.Sprintf(`{
|
|
||||||
"cniVersion": "%s",
|
|
||||||
"name": "myBWnet",
|
|
||||||
"type": "ptp",
|
|
||||||
"ipMasq": true,
|
|
||||||
"mtu": 512,
|
|
||||||
"ipam": {
|
|
||||||
"type": "host-local",
|
|
||||||
"subnet": "10.1.2.0/24",
|
|
||||||
"dataDir": "%s"
|
|
||||||
}
|
|
||||||
}`, ver, dataDir)
|
|
||||||
|
|
||||||
const (
|
|
||||||
containerWithQoSIFName = "ptp0"
|
|
||||||
containerWithoutQoSIFName = "ptp1"
|
|
||||||
)
|
|
||||||
|
|
||||||
containerWithQoSNS, err = testutils.NewNS()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
containerWithoutQoSNS, err = testutils.NewNS()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
By("create two containers, and use the bandwidth plugin on one of them")
|
|
||||||
Expect(hostNs.Do(func(ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
|
|
||||||
containerWithQoSRes, _, err = testutils.CmdAdd(containerWithQoSNS.Path(), "dummy", containerWithQoSIFName, []byte(ptpConf), func() error {
|
|
||||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(r.Print()).To(Succeed())
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
containerWithoutQoSRes, _, err = testutils.CmdAdd(containerWithoutQoSNS.Path(), "dummy2", containerWithoutQoSIFName, []byte(ptpConf), func() error {
|
|
||||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(r.Print()).To(Succeed())
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
containerWithQoSResult, err := types100.GetResult(containerWithQoSRes)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
bandwidthPluginConf := &PluginConf{}
|
|
||||||
err = json.Unmarshal([]byte(ptpConf), &bandwidthPluginConf)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
bandwidthPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
|
||||||
IngressBurst: burstInBits,
|
|
||||||
IngressRate: rateInBits,
|
|
||||||
EgressBurst: burstInBits,
|
|
||||||
EgressRate: rateInBits,
|
|
||||||
}
|
|
||||||
bandwidthPluginConf.Type = "bandwidth"
|
|
||||||
newConfBytes, err := buildOneConfig(ver, bandwidthPluginConf, containerWithQoSResult)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy3",
|
|
||||||
Netns: containerWithQoSNS.Path(),
|
|
||||||
IfName: containerWithQoSIFName,
|
|
||||||
StdinData: newConfBytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, out, err := testutils.CmdAdd(containerWithQoSNS.Path(), args.ContainerID, "", newConfBytes, func() error { return cmdAdd(args) })
|
|
||||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
|
||||||
|
|
||||||
if testutils.SpecVersionHasCHECK(ver) {
|
|
||||||
// Do CNI Check
|
|
||||||
checkConf := &PluginConf{}
|
|
||||||
err = json.Unmarshal([]byte(ptpConf), &checkConf)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
checkConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
|
||||||
IngressBurst: burstInBits,
|
|
||||||
IngressRate: rateInBits,
|
|
||||||
EgressBurst: burstInBits,
|
|
||||||
EgressRate: rateInBits,
|
|
||||||
}
|
|
||||||
checkConf.Type = "bandwidth"
|
|
||||||
|
|
||||||
newCheckBytes, err := buildOneConfig(ver, checkConf, result)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
args = &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy3",
|
|
||||||
Netns: containerWithQoSNS.Path(),
|
|
||||||
IfName: containerWithQoSIFName,
|
|
||||||
StdinData: newCheckBytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = testutils.CmdCheck(containerWithQoSNS.Path(), args.ContainerID, "", func() error { return cmdCheck(args) })
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})).To(Succeed())
|
|
||||||
|
|
||||||
By("starting a tcp server on both containers")
|
|
||||||
portServerWithQoS, echoServerWithQoS = startEchoServerInNamespace(containerWithQoSNS)
|
|
||||||
portServerWithoutQoS, echoServerWithoutQoS = startEchoServerInNamespace(containerWithoutQoSNS)
|
|
||||||
})
|
|
||||||
|
|
||||||
AfterEach(func() {
|
|
||||||
Expect(os.RemoveAll(dataDir)).To(Succeed())
|
|
||||||
|
|
||||||
Expect(containerWithQoSNS.Close()).To(Succeed())
|
|
||||||
Expect(testutils.UnmountNS(containerWithQoSNS)).To(Succeed())
|
|
||||||
Expect(containerWithoutQoSNS.Close()).To(Succeed())
|
|
||||||
Expect(testutils.UnmountNS(containerWithoutQoSNS)).To(Succeed())
|
|
||||||
|
|
||||||
if echoServerWithoutQoS != nil {
|
|
||||||
echoServerWithoutQoS.Kill()
|
|
||||||
}
|
|
||||||
if echoServerWithQoS != nil {
|
|
||||||
echoServerWithQoS.Kill()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
It("limits ingress traffic on veth device", func() {
|
|
||||||
var runtimeWithLimit time.Duration
|
|
||||||
var runtimeWithoutLimit time.Duration
|
|
||||||
|
|
||||||
By("gather timing statistics about both containers")
|
|
||||||
|
|
||||||
By("sending tcp traffic to the container that has traffic shaped", func() {
|
|
||||||
start := time.Now()
|
|
||||||
result, err := types100.GetResult(containerWithQoSRes)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithQoS, packetInBytes)
|
|
||||||
end := time.Now()
|
|
||||||
runtimeWithLimit = end.Sub(start)
|
|
||||||
log.Printf("Elapsed with qos %.2f", runtimeWithLimit.Seconds())
|
|
||||||
})
|
|
||||||
|
|
||||||
By("sending tcp traffic to the container that does not have traffic shaped", func() {
|
|
||||||
start := time.Now()
|
|
||||||
result, err := types100.GetResult(containerWithoutQoSRes)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithoutQoS, packetInBytes)
|
|
||||||
end := time.Now()
|
|
||||||
runtimeWithoutLimit = end.Sub(start)
|
|
||||||
log.Printf("Elapsed without qos %.2f", runtimeWithoutLimit.Seconds())
|
|
||||||
})
|
|
||||||
|
|
||||||
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Context(fmt.Sprintf("[%s] when chaining bandwidth plugin with PTP and excluding specific subnets from traffic", ver), func() {
|
|
||||||
var ptpConf string
|
|
||||||
var rateInBits uint64
|
|
||||||
var burstInBits uint64
|
|
||||||
var packetInBytes int
|
|
||||||
var containerWithoutQoSNS ns.NetNS
|
|
||||||
var containerWithQoSNS ns.NetNS
|
|
||||||
var portServerWithQoS int
|
|
||||||
var portServerWithoutQoS int
|
|
||||||
|
|
||||||
var containerWithQoSRes types.Result
|
|
||||||
var containerWithoutQoSRes types.Result
|
|
||||||
var echoServerWithQoS *gexec.Session
|
|
||||||
var echoServerWithoutQoS *gexec.Session
|
|
||||||
var dataDir string
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
rateInBytes := 1000
|
|
||||||
rateInBits = uint64(rateInBytes * 8)
|
|
||||||
burstInBits = rateInBits * 2
|
|
||||||
unshapedSubnets := []string{"10.1.2.0/24"}
|
|
||||||
// NOTE: Traffic shapping is not that precise at low rates, would be better to use higher rates + simple time+netcat for data transfer, rather than the provided
|
|
||||||
// client/server bin (limited to small amount of data)
|
|
||||||
packetInBytes = rateInBytes * 3
|
|
||||||
|
|
||||||
var err error
|
|
||||||
dataDir, err = os.MkdirTemp("", "bandwidth_linux_test")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
ptpConf = fmt.Sprintf(`{
|
|
||||||
"cniVersion": "%s",
|
|
||||||
"name": "myBWnet",
|
|
||||||
"type": "ptp",
|
|
||||||
"ipMasq": true,
|
|
||||||
"mtu": 512,
|
|
||||||
"ipam": {
|
|
||||||
"type": "host-local",
|
|
||||||
"subnet": "10.1.2.0/24",
|
|
||||||
"dataDir": "%s"
|
|
||||||
}
|
|
||||||
}`, ver, dataDir)
|
|
||||||
|
|
||||||
const (
|
|
||||||
containerWithQoSIFName = "ptp0"
|
|
||||||
containerWithoutQoSIFName = "ptp1"
|
|
||||||
)
|
|
||||||
|
|
||||||
containerWithQoSNS, err = testutils.NewNS()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
containerWithoutQoSNS, err = testutils.NewNS()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
By("create two containers, and use the bandwidth plugin on one of them")
|
|
||||||
Expect(hostNs.Do(func(ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
|
|
||||||
containerWithQoSRes, _, err = testutils.CmdAdd(containerWithQoSNS.Path(), "dummy", containerWithQoSIFName, []byte(ptpConf), func() error {
|
|
||||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(r.Print()).To(Succeed())
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
containerWithoutQoSRes, _, err = testutils.CmdAdd(containerWithoutQoSNS.Path(), "dummy2", containerWithoutQoSIFName, []byte(ptpConf), func() error {
|
|
||||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(r.Print()).To(Succeed())
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
containerWithQoSResult, err := types100.GetResult(containerWithQoSRes)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
bandwidthPluginConf := &PluginConf{}
|
|
||||||
err = json.Unmarshal([]byte(ptpConf), &bandwidthPluginConf)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
bandwidthPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
|
||||||
IngressBurst: burstInBits,
|
|
||||||
IngressRate: rateInBits,
|
|
||||||
EgressBurst: burstInBits,
|
|
||||||
EgressRate: rateInBits,
|
|
||||||
UnshapedSubnets: unshapedSubnets,
|
|
||||||
}
|
|
||||||
bandwidthPluginConf.Type = "bandwidth"
|
|
||||||
newConfBytes, err := buildOneConfig(ver, bandwidthPluginConf, containerWithQoSResult)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy3",
|
|
||||||
Netns: containerWithQoSNS.Path(),
|
|
||||||
IfName: containerWithQoSIFName,
|
|
||||||
StdinData: newConfBytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, out, err := testutils.CmdAdd(containerWithQoSNS.Path(), args.ContainerID, "", newConfBytes, func() error { return cmdAdd(args) })
|
|
||||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
|
||||||
|
|
||||||
if testutils.SpecVersionHasCHECK(ver) {
|
|
||||||
// Do CNI Check
|
|
||||||
checkConf := &PluginConf{}
|
|
||||||
err = json.Unmarshal([]byte(ptpConf), &checkConf)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
checkConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
|
||||||
IngressBurst: burstInBits,
|
|
||||||
IngressRate: rateInBits,
|
|
||||||
EgressBurst: burstInBits,
|
|
||||||
EgressRate: rateInBits,
|
|
||||||
UnshapedSubnets: unshapedSubnets,
|
|
||||||
}
|
|
||||||
checkConf.Type = "bandwidth"
|
|
||||||
|
|
||||||
newCheckBytes, err := buildOneConfig(ver, checkConf, result)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
args = &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy3",
|
|
||||||
Netns: containerWithQoSNS.Path(),
|
|
||||||
IfName: containerWithQoSIFName,
|
|
||||||
StdinData: newCheckBytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = testutils.CmdCheck(containerWithQoSNS.Path(), args.ContainerID, "", func() error { return cmdCheck(args) })
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})).To(Succeed())
|
|
||||||
|
|
||||||
By("starting a tcp server on both containers")
|
|
||||||
portServerWithQoS, echoServerWithQoS = startEchoServerInNamespace(containerWithQoSNS)
|
|
||||||
portServerWithoutQoS, echoServerWithoutQoS = startEchoServerInNamespace(containerWithoutQoSNS)
|
|
||||||
})
|
|
||||||
|
|
||||||
AfterEach(func() {
|
|
||||||
Expect(os.RemoveAll(dataDir)).To(Succeed())
|
|
||||||
|
|
||||||
Expect(containerWithQoSNS.Close()).To(Succeed())
|
|
||||||
Expect(testutils.UnmountNS(containerWithQoSNS)).To(Succeed())
|
|
||||||
Expect(containerWithoutQoSNS.Close()).To(Succeed())
|
|
||||||
Expect(testutils.UnmountNS(containerWithoutQoSNS)).To(Succeed())
|
|
||||||
|
|
||||||
if echoServerWithoutQoS != nil {
|
|
||||||
echoServerWithoutQoS.Kill()
|
|
||||||
}
|
|
||||||
if echoServerWithQoS != nil {
|
|
||||||
echoServerWithQoS.Kill()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
It("does not limits ingress traffic on veth device coming from 10.1.2.0/24", func() {
|
|
||||||
var runtimeWithLimit time.Duration
|
|
||||||
var runtimeWithoutLimit time.Duration
|
|
||||||
|
|
||||||
By("gather timing statistics about both containers")
|
|
||||||
|
|
||||||
By("sending tcp traffic to the container that has traffic shaped", func() {
|
|
||||||
start := time.Now()
|
|
||||||
result, err := types100.GetResult(containerWithQoSRes)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithQoS, packetInBytes)
|
|
||||||
end := time.Now()
|
|
||||||
runtimeWithLimit = end.Sub(start)
|
|
||||||
log.Printf("Elapsed with qos %.2f", runtimeWithLimit.Seconds())
|
|
||||||
})
|
|
||||||
|
|
||||||
By("sending tcp traffic to the container that does not have traffic shaped", func() {
|
|
||||||
start := time.Now()
|
|
||||||
result, err := types100.GetResult(containerWithoutQoSRes)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithoutQoS, packetInBytes)
|
|
||||||
end := time.Now()
|
|
||||||
runtimeWithoutLimit = end.Sub(start)
|
|
||||||
log.Printf("Elapsed without qos %.2f", runtimeWithoutLimit.Seconds())
|
|
||||||
})
|
|
||||||
|
|
||||||
Expect(runtimeWithLimit - runtimeWithoutLimit).To(BeNumerically("<", 100*time.Millisecond))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Context(fmt.Sprintf("[%s] when chaining bandwidth plugin with PTP and only including specific subnets in traffic shapping (not including the main ns one)", ver), func() {
|
|
||||||
var ptpConf string
|
|
||||||
var rateInBits uint64
|
|
||||||
var burstInBits uint64
|
|
||||||
var packetInBytes int
|
|
||||||
var containerWithoutQoSNS ns.NetNS
|
|
||||||
var containerWithQoSNS ns.NetNS
|
|
||||||
var portServerWithQoS int
|
|
||||||
var portServerWithoutQoS int
|
|
||||||
|
|
||||||
var containerWithQoSRes types.Result
|
|
||||||
var containerWithoutQoSRes types.Result
|
|
||||||
var echoServerWithQoS *gexec.Session
|
|
||||||
var echoServerWithoutQoS *gexec.Session
|
|
||||||
var dataDir string
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
rateInBytes := 1000
|
|
||||||
rateInBits = uint64(rateInBytes * 8)
|
|
||||||
burstInBits = rateInBits * 2
|
|
||||||
shapedSubnets := []string{"10.2.2.0/24"}
|
|
||||||
// NOTE: Traffic shapping is not that precise at low rates, would be better to use higher rates + simple time+netcat for data transfer, rather than the provided
|
|
||||||
// client/server bin (limited to small amount of data)
|
|
||||||
packetInBytes = rateInBytes * 3
|
|
||||||
|
|
||||||
var err error
|
|
||||||
dataDir, err = os.MkdirTemp("", "bandwidth_linux_test")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
ptpConf = fmt.Sprintf(`{
|
|
||||||
"cniVersion": "%s",
|
|
||||||
"name": "myBWnet",
|
|
||||||
"type": "ptp",
|
|
||||||
"ipMasq": true,
|
|
||||||
"mtu": 512,
|
|
||||||
"ipam": {
|
|
||||||
"type": "host-local",
|
|
||||||
"subnet": "10.1.2.0/24",
|
|
||||||
"dataDir": "%s"
|
|
||||||
}
|
|
||||||
}`, ver, dataDir)
|
|
||||||
|
|
||||||
const (
|
|
||||||
containerWithQoSIFName = "ptp0"
|
|
||||||
containerWithoutQoSIFName = "ptp1"
|
|
||||||
)
|
|
||||||
|
|
||||||
containerWithQoSNS, err = testutils.NewNS()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
containerWithoutQoSNS, err = testutils.NewNS()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
By("create two containers, and use the bandwidth plugin on one of them")
|
|
||||||
|
|
||||||
Expect(hostNs.Do(func(ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
|
|
||||||
containerWithQoSRes, _, err = testutils.CmdAdd(containerWithQoSNS.Path(), "dummy", containerWithQoSIFName, []byte(ptpConf), func() error {
|
|
||||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(r.Print()).To(Succeed())
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
containerWithoutQoSRes, _, err = testutils.CmdAdd(containerWithoutQoSNS.Path(), "dummy2", containerWithoutQoSIFName, []byte(ptpConf), func() error {
|
|
||||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(r.Print()).To(Succeed())
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
containerWithQoSResult, err := types100.GetResult(containerWithQoSRes)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
bandwidthPluginConf := &PluginConf{}
|
|
||||||
err = json.Unmarshal([]byte(ptpConf), &bandwidthPluginConf)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
bandwidthPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
|
||||||
IngressBurst: burstInBits,
|
|
||||||
IngressRate: rateInBits,
|
|
||||||
EgressBurst: burstInBits,
|
|
||||||
EgressRate: rateInBits,
|
|
||||||
ShapedSubnets: shapedSubnets,
|
|
||||||
}
|
|
||||||
bandwidthPluginConf.Type = "bandwidth"
|
|
||||||
newConfBytes, err := buildOneConfig(ver, bandwidthPluginConf, containerWithQoSResult)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy3",
|
|
||||||
Netns: containerWithQoSNS.Path(),
|
|
||||||
IfName: containerWithQoSIFName,
|
|
||||||
StdinData: newConfBytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, out, err := testutils.CmdAdd(containerWithQoSNS.Path(), args.ContainerID, "", newConfBytes, func() error { return cmdAdd(args) })
|
|
||||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
|
||||||
|
|
||||||
if testutils.SpecVersionHasCHECK(ver) {
|
|
||||||
// Do CNI Check
|
|
||||||
checkConf := &PluginConf{}
|
|
||||||
err = json.Unmarshal([]byte(ptpConf), &checkConf)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
checkConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
|
||||||
IngressBurst: burstInBits,
|
|
||||||
IngressRate: rateInBits,
|
|
||||||
EgressBurst: burstInBits,
|
|
||||||
EgressRate: rateInBits,
|
|
||||||
ShapedSubnets: shapedSubnets,
|
|
||||||
}
|
|
||||||
checkConf.Type = "bandwidth"
|
|
||||||
|
|
||||||
newCheckBytes, err := buildOneConfig(ver, checkConf, result)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
args = &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy3",
|
|
||||||
Netns: containerWithQoSNS.Path(),
|
|
||||||
IfName: containerWithQoSIFName,
|
|
||||||
StdinData: newCheckBytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = testutils.CmdCheck(containerWithQoSNS.Path(), args.ContainerID, "", func() error { return cmdCheck(args) })
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})).To(Succeed())
|
|
||||||
|
|
||||||
By("starting a tcp server on both containers")
|
|
||||||
portServerWithQoS, echoServerWithQoS = startEchoServerInNamespace(containerWithQoSNS)
|
|
||||||
portServerWithoutQoS, echoServerWithoutQoS = startEchoServerInNamespace(containerWithoutQoSNS)
|
|
||||||
})
|
|
||||||
|
|
||||||
AfterEach(func() {
|
|
||||||
Expect(os.RemoveAll(dataDir)).To(Succeed())
|
|
||||||
|
|
||||||
Expect(containerWithQoSNS.Close()).To(Succeed())
|
|
||||||
Expect(testutils.UnmountNS(containerWithQoSNS)).To(Succeed())
|
|
||||||
Expect(containerWithoutQoSNS.Close()).To(Succeed())
|
|
||||||
Expect(testutils.UnmountNS(containerWithoutQoSNS)).To(Succeed())
|
|
||||||
|
|
||||||
if echoServerWithoutQoS != nil {
|
|
||||||
echoServerWithoutQoS.Kill()
|
|
||||||
}
|
|
||||||
if echoServerWithQoS != nil {
|
|
||||||
echoServerWithQoS.Kill()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
It("does not limit ingress traffic on veth device coming from non included subnets", func() {
|
|
||||||
var runtimeWithLimit time.Duration
|
|
||||||
var runtimeWithoutLimit time.Duration
|
|
||||||
|
|
||||||
By("gather timing statistics about both containers")
|
|
||||||
|
|
||||||
By("sending tcp traffic to the container that has traffic shaped", func() {
|
|
||||||
start := time.Now()
|
|
||||||
result, err := types100.GetResult(containerWithQoSRes)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithQoS, packetInBytes)
|
|
||||||
end := time.Now()
|
|
||||||
runtimeWithLimit = end.Sub(start)
|
|
||||||
log.Printf("Elapsed with qos %.2f", runtimeWithLimit.Seconds())
|
|
||||||
})
|
|
||||||
|
|
||||||
By("sending tcp traffic to the container that does not have traffic shaped", func() {
|
|
||||||
start := time.Now()
|
|
||||||
result, err := types100.GetResult(containerWithoutQoSRes)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithoutQoS, packetInBytes)
|
|
||||||
end := time.Now()
|
|
||||||
runtimeWithoutLimit = end.Sub(start)
|
|
||||||
log.Printf("Elapsed without qos %.2f", runtimeWithoutLimit.Seconds())
|
|
||||||
})
|
|
||||||
|
|
||||||
Expect(runtimeWithLimit - runtimeWithoutLimit).To(BeNumerically("<", 100*time.Millisecond))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Context(fmt.Sprintf("[%s] when chaining bandwidth plugin with PTP and only including specific subnets in traffic shapping (including the main ns one)", ver), func() {
|
|
||||||
var ptpConf string
|
|
||||||
var rateInBits uint64
|
|
||||||
var burstInBits uint64
|
|
||||||
var packetInBytes int
|
|
||||||
var containerWithoutQoSNS ns.NetNS
|
|
||||||
var containerWithQoSNS ns.NetNS
|
|
||||||
var portServerWithQoS int
|
|
||||||
var portServerWithoutQoS int
|
|
||||||
|
|
||||||
var containerWithQoSRes types.Result
|
|
||||||
var containerWithoutQoSRes types.Result
|
|
||||||
var echoServerWithQoS *gexec.Session
|
|
||||||
var echoServerWithoutQoS *gexec.Session
|
|
||||||
var dataDir string
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
rateInBytes := 1000
|
|
||||||
rateInBits = uint64(rateInBytes * 8)
|
|
||||||
burstInBits = rateInBits * 2
|
|
||||||
shapedSubnets := []string{"10.1.2.1/32"}
|
|
||||||
// NOTE: Traffic shapping is not that precise at low rates, would be better to use higher rates + simple time+netcat for data transfer, rather than the provided
|
|
||||||
// client/server bin (limited to small amount of data)
|
|
||||||
packetInBytes = rateInBytes * 3
|
|
||||||
|
|
||||||
var err error
|
|
||||||
dataDir, err = os.MkdirTemp("", "bandwidth_linux_test")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
ptpConf = fmt.Sprintf(`{
|
|
||||||
"cniVersion": "%s",
|
|
||||||
"name": "myBWnet",
|
|
||||||
"type": "ptp",
|
|
||||||
"ipMasq": true,
|
|
||||||
"mtu": 512,
|
|
||||||
"ipam": {
|
|
||||||
"type": "host-local",
|
|
||||||
"subnet": "10.1.2.0/24",
|
|
||||||
"dataDir": "%s"
|
|
||||||
}
|
|
||||||
}`, ver, dataDir)
|
|
||||||
|
|
||||||
const (
|
|
||||||
containerWithQoSIFName = "ptp0"
|
|
||||||
containerWithoutQoSIFName = "ptp1"
|
|
||||||
)
|
|
||||||
|
|
||||||
containerWithQoSNS, err = testutils.NewNS()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
containerWithoutQoSNS, err = testutils.NewNS()
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
By("create two containers, and use the bandwidth plugin on one of them")
|
|
||||||
|
|
||||||
Expect(hostNs.Do(func(ns.NetNS) error {
|
|
||||||
defer GinkgoRecover()
|
|
||||||
|
|
||||||
containerWithQoSRes, _, err = testutils.CmdAdd(containerWithQoSNS.Path(), "dummy", containerWithQoSIFName, []byte(ptpConf), func() error {
|
|
||||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(r.Print()).To(Succeed())
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
containerWithoutQoSRes, _, err = testutils.CmdAdd(containerWithoutQoSNS.Path(), "dummy2", containerWithoutQoSIFName, []byte(ptpConf), func() error {
|
|
||||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(r.Print()).To(Succeed())
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
containerWithQoSResult, err := types100.GetResult(containerWithQoSRes)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
bandwidthPluginConf := &PluginConf{}
|
|
||||||
err = json.Unmarshal([]byte(ptpConf), &bandwidthPluginConf)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
bandwidthPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
|
||||||
IngressBurst: burstInBits,
|
|
||||||
IngressRate: rateInBits,
|
|
||||||
EgressBurst: burstInBits,
|
|
||||||
EgressRate: rateInBits,
|
|
||||||
ShapedSubnets: shapedSubnets,
|
|
||||||
}
|
|
||||||
bandwidthPluginConf.Type = "bandwidth"
|
|
||||||
newConfBytes, err := buildOneConfig(ver, bandwidthPluginConf, containerWithQoSResult)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
args := &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy3",
|
|
||||||
Netns: containerWithQoSNS.Path(),
|
|
||||||
IfName: containerWithQoSIFName,
|
|
||||||
StdinData: newConfBytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, out, err := testutils.CmdAdd(containerWithQoSNS.Path(), args.ContainerID, "", newConfBytes, func() error { return cmdAdd(args) })
|
|
||||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
|
||||||
|
|
||||||
if testutils.SpecVersionHasCHECK(ver) {
|
|
||||||
// Do CNI Check
|
|
||||||
checkConf := &PluginConf{}
|
|
||||||
err = json.Unmarshal([]byte(ptpConf), &checkConf)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
checkConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
|
||||||
IngressBurst: burstInBits,
|
|
||||||
IngressRate: rateInBits,
|
|
||||||
EgressBurst: burstInBits,
|
|
||||||
EgressRate: rateInBits,
|
|
||||||
ShapedSubnets: shapedSubnets,
|
|
||||||
}
|
|
||||||
checkConf.Type = "bandwidth"
|
|
||||||
|
|
||||||
newCheckBytes, err := buildOneConfig(ver, checkConf, result)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
|
|
||||||
args = &skel.CmdArgs{
|
|
||||||
ContainerID: "dummy3",
|
|
||||||
Netns: containerWithQoSNS.Path(),
|
|
||||||
IfName: containerWithQoSIFName,
|
|
||||||
StdinData: newCheckBytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = testutils.CmdCheck(containerWithQoSNS.Path(), args.ContainerID, "", func() error { return cmdCheck(args) })
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})).To(Succeed())
|
|
||||||
|
|
||||||
By("starting a tcp server on both containers")
|
|
||||||
portServerWithQoS, echoServerWithQoS = startEchoServerInNamespace(containerWithQoSNS)
|
|
||||||
portServerWithoutQoS, echoServerWithoutQoS = startEchoServerInNamespace(containerWithoutQoSNS)
|
|
||||||
})
|
|
||||||
|
|
||||||
AfterEach(func() {
|
|
||||||
Expect(os.RemoveAll(dataDir)).To(Succeed())
|
|
||||||
|
|
||||||
Expect(containerWithQoSNS.Close()).To(Succeed())
|
|
||||||
Expect(testutils.UnmountNS(containerWithQoSNS)).To(Succeed())
|
|
||||||
Expect(containerWithoutQoSNS.Close()).To(Succeed())
|
|
||||||
Expect(testutils.UnmountNS(containerWithoutQoSNS)).To(Succeed())
|
|
||||||
|
|
||||||
if echoServerWithoutQoS != nil {
|
|
||||||
echoServerWithoutQoS.Kill()
|
|
||||||
}
|
|
||||||
if echoServerWithQoS != nil {
|
|
||||||
echoServerWithQoS.Kill()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
It("limits ingress traffic on veth device coming from included subnets", func() {
|
|
||||||
var runtimeWithLimit time.Duration
|
|
||||||
var runtimeWithoutLimit time.Duration
|
|
||||||
|
|
||||||
By("gather timing statistics about both containers")
|
|
||||||
|
|
||||||
By("sending tcp traffic to the container that has traffic shaped", func() {
|
|
||||||
start := time.Now()
|
|
||||||
result, err := types100.GetResult(containerWithQoSRes)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithQoS, packetInBytes)
|
|
||||||
end := time.Now()
|
|
||||||
runtimeWithLimit = end.Sub(start)
|
|
||||||
log.Printf("Elapsed with qos %.2f", runtimeWithLimit.Seconds())
|
|
||||||
})
|
|
||||||
|
|
||||||
By("sending tcp traffic to the container that does not have traffic shaped", func() {
|
|
||||||
start := time.Now()
|
|
||||||
result, err := types100.GetResult(containerWithoutQoSRes)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithoutQoS, packetInBytes)
|
|
||||||
end := time.Now()
|
|
||||||
runtimeWithoutLimit = end.Sub(start)
|
|
||||||
log.Printf("Elapsed without qos %.2f", runtimeWithoutLimit.Seconds())
|
|
||||||
})
|
|
||||||
|
|
||||||
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
@ -15,7 +15,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@ -31,11 +30,10 @@ import (
|
|||||||
"github.com/onsi/gomega/gexec"
|
"github.com/onsi/gomega/gexec"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
|
||||||
"github.com/containernetworking/plugins/pkg/ns"
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHTB(t *testing.T) {
|
func TestTBF(t *testing.T) {
|
||||||
RegisterFailHandler(Fail)
|
RegisterFailHandler(Fail)
|
||||||
RunSpecs(t, "plugins/meta/bandwidth")
|
RunSpecs(t, "plugins/meta/bandwidth")
|
||||||
}
|
}
|
||||||
@ -245,47 +243,3 @@ func createMacvlan(netNS ns.NetNS, master, macvlanName string) {
|
|||||||
})
|
})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildOneConfig(cniVersion string, orig *PluginConf, prevResult types.Result) ([]byte, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
inject := map[string]interface{}{
|
|
||||||
"name": "myBWnet",
|
|
||||||
"cniVersion": cniVersion,
|
|
||||||
}
|
|
||||||
// Add previous plugin result
|
|
||||||
if prevResult != nil {
|
|
||||||
r, err := prevResult.GetAsVersion(cniVersion)
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
inject["prevResult"] = r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure every config uses the same name and version
|
|
||||||
config := make(map[string]interface{})
|
|
||||||
|
|
||||||
confBytes, err := json.Marshal(orig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(confBytes, &config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value := range inject {
|
|
||||||
config[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
newBytes, err := json.Marshal(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := &PluginConf{}
|
|
||||||
if err := json.Unmarshal(newBytes, &conf); err != nil {
|
|
||||||
return nil, fmt.Errorf("error parsing configuration: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return newBytes, nil
|
|
||||||
}
|
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -26,24 +24,14 @@ import (
|
|||||||
"github.com/containernetworking/plugins/pkg/ip"
|
"github.com/containernetworking/plugins/pkg/ip"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const latencyInMillis = 25
|
||||||
latencyInMillis = 25
|
|
||||||
UncappedRate uint64 = 100_000_000_000
|
|
||||||
ShapedClassMinorID uint16 = 48
|
|
||||||
UnShapedClassMinorID uint16 = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateIfb(ifbDeviceName string, mtu int, qlen int) error {
|
|
||||||
if qlen < 1000 {
|
|
||||||
qlen = 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func CreateIfb(ifbDeviceName string, mtu int) error {
|
||||||
err := netlink.LinkAdd(&netlink.Ifb{
|
err := netlink.LinkAdd(&netlink.Ifb{
|
||||||
LinkAttrs: netlink.LinkAttrs{
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
Name: ifbDeviceName,
|
Name: ifbDeviceName,
|
||||||
Flags: net.FlagUp,
|
Flags: net.FlagUp,
|
||||||
MTU: mtu,
|
MTU: mtu,
|
||||||
TxQLen: qlen,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -61,24 +49,15 @@ func TeardownIfb(deviceName string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateIngressQdisc(rateInBits, burstInBits uint64, excludeSubnets []string, includeSubnets []string, hostDeviceName string) error {
|
func CreateIngressQdisc(rateInBits, burstInBits uint64, hostDeviceName string) error {
|
||||||
hostDevice, err := netlink.LinkByName(hostDeviceName)
|
hostDevice, err := netlink.LinkByName(hostDeviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get host device: %s", err)
|
return fmt.Errorf("get host device: %s", err)
|
||||||
}
|
}
|
||||||
|
return createTBF(rateInBits, burstInBits, hostDevice.Attrs().Index)
|
||||||
subnets := includeSubnets
|
|
||||||
exclude := false
|
|
||||||
|
|
||||||
if len(excludeSubnets) > 0 {
|
|
||||||
subnets = excludeSubnets
|
|
||||||
exclude = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return createHTB(rateInBits, burstInBits, hostDevice.Attrs().Index, subnets, exclude)
|
func CreateEgressQdisc(rateInBits, burstInBits uint64, hostDeviceName string, ifbDeviceName string) error {
|
||||||
}
|
|
||||||
|
|
||||||
func CreateEgressQdisc(rateInBits, burstInBits uint64, excludeSubnets []string, includeSubnets []string, hostDeviceName string, ifbDeviceName string) error {
|
|
||||||
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
|
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get ifb device: %s", err)
|
return fmt.Errorf("get ifb device: %s", err)
|
||||||
@ -125,216 +104,44 @@ func CreateEgressQdisc(rateInBits, burstInBits uint64, excludeSubnets []string,
|
|||||||
return fmt.Errorf("add filter: %s", err)
|
return fmt.Errorf("add filter: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
subnets := excludeSubnets
|
|
||||||
exclude := true
|
|
||||||
|
|
||||||
if len(includeSubnets) > 0 {
|
|
||||||
subnets = includeSubnets
|
|
||||||
exclude = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// throttle traffic on ifb device
|
// throttle traffic on ifb device
|
||||||
err = createHTB(rateInBits, burstInBits, ifbDevice.Attrs().Index, subnets, exclude)
|
err = createTBF(rateInBits, burstInBits, ifbDevice.Attrs().Index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// egress from the container/netns pov = ingress from the main netns/host pov
|
return fmt.Errorf("create ifb qdisc: %s", err)
|
||||||
return fmt.Errorf("create htb container egress qos rules: %s", err)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createHTB(rateInBits, burstInBits uint64, linkIndex int, subnets []string, excludeSubnets bool) error {
|
func createTBF(rateInBits, burstInBits uint64, linkIndex int) error {
|
||||||
// Netlink struct fields are not clear, let's use shell
|
// Equivalent to
|
||||||
|
// tc qdisc add dev link root tbf
|
||||||
defaultClassID := UnShapedClassMinorID
|
// rate netConf.BandwidthLimits.Rate
|
||||||
// If no subnets are specified, then shaping should apply to everything
|
// burst netConf.BandwidthLimits.Burst
|
||||||
if len(subnets) == 0 || excludeSubnets {
|
if rateInBits <= 0 {
|
||||||
defaultClassID = ShapedClassMinorID
|
return fmt.Errorf("invalid rate: %d", rateInBits)
|
||||||
}
|
}
|
||||||
|
if burstInBits <= 0 {
|
||||||
|
return fmt.Errorf("invalid burst: %d", burstInBits)
|
||||||
|
}
|
||||||
|
rateInBytes := rateInBits / 8
|
||||||
|
burstInBytes := burstInBits / 8
|
||||||
|
bufferInBytes := buffer(rateInBytes, uint32(burstInBytes))
|
||||||
|
latency := latencyInUsec(latencyInMillis)
|
||||||
|
limitInBytes := limit(rateInBytes, latency, uint32(burstInBytes))
|
||||||
|
|
||||||
// Step 1 qdisc
|
qdisc := &netlink.Tbf{
|
||||||
// cmd := exec.Command("/usr/sbin/tc", "qdisc", "add", "dev", interfaceName, "root", "handle", "1:", "htb", "default", "30")
|
|
||||||
qdisc := &netlink.Htb{
|
|
||||||
QdiscAttrs: netlink.QdiscAttrs{
|
QdiscAttrs: netlink.QdiscAttrs{
|
||||||
LinkIndex: linkIndex,
|
LinkIndex: linkIndex,
|
||||||
Handle: netlink.MakeHandle(1, 0),
|
Handle: netlink.MakeHandle(1, 0),
|
||||||
Parent: netlink.HANDLE_ROOT,
|
Parent: netlink.HANDLE_ROOT,
|
||||||
},
|
},
|
||||||
Defcls: uint32(defaultClassID),
|
Limit: limitInBytes,
|
||||||
// No idea what these are so let's keep the default values from source code...
|
Rate: rateInBytes,
|
||||||
Version: 3,
|
Buffer: bufferInBytes,
|
||||||
Rate2Quantum: 10,
|
|
||||||
}
|
}
|
||||||
err := netlink.QdiscAdd(qdisc)
|
err := netlink.QdiscAdd(qdisc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error while creating qdisc: %s", err)
|
return fmt.Errorf("create qdisc: %s", err)
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2 classes
|
|
||||||
|
|
||||||
rateInBytes := rateInBits / 8
|
|
||||||
burstInBytes := burstInBits / 8
|
|
||||||
bufferInBytes := buffer(rateInBytes, uint32(burstInBytes))
|
|
||||||
|
|
||||||
// The capped class for shaped traffic (included subnets or all but excluded subnets)
|
|
||||||
// cmd = exec.Command("/usr/sbin/tc", "class", "add", "dev", interfaceName, "parent", "1:", "classid", "1:30", "htb", "rate",
|
|
||||||
// fmt.Sprintf("%d", rateInBits), "burst", fmt.Sprintf("%d", burstInBits))
|
|
||||||
shapedClass := &netlink.HtbClass{
|
|
||||||
ClassAttrs: netlink.ClassAttrs{
|
|
||||||
LinkIndex: linkIndex,
|
|
||||||
Handle: netlink.MakeHandle(1, ShapedClassMinorID),
|
|
||||||
Parent: netlink.MakeHandle(1, 0),
|
|
||||||
},
|
|
||||||
Rate: rateInBytes,
|
|
||||||
Buffer: bufferInBytes,
|
|
||||||
// Let's set up the "burst" rate to twice the specified rate
|
|
||||||
Ceil: 2 * rateInBytes,
|
|
||||||
Cbuffer: bufferInBytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = netlink.ClassAdd(shapedClass)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error while creating htb default class: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The uncapped class for non shaped traffic (either all but included subnets or excluded subnets only)
|
|
||||||
// cmd = exec.Command("/usr/sbin/tc", "class", "add", "dev", interfaceName, "parent", "1:", "classid", "1:1", "htb",
|
|
||||||
// "rate", "100000000000")
|
|
||||||
bigRate := UncappedRate
|
|
||||||
unshapedClass := &netlink.HtbClass{
|
|
||||||
ClassAttrs: netlink.ClassAttrs{
|
|
||||||
LinkIndex: linkIndex,
|
|
||||||
Handle: netlink.MakeHandle(1, UnShapedClassMinorID),
|
|
||||||
Parent: qdisc.Handle,
|
|
||||||
},
|
|
||||||
Rate: bigRate,
|
|
||||||
Ceil: bigRate,
|
|
||||||
// No need for any burst, the minimum buffer size in q_htb.c should be enough to handle the rate which
|
|
||||||
// is already more than enough
|
|
||||||
}
|
|
||||||
err = netlink.ClassAdd(unshapedClass)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error while creating htb uncapped class: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now add filters to redirect subnets to the class 1 if excluded instead of the default one (30)
|
|
||||||
for _, subnet := range subnets {
|
|
||||||
|
|
||||||
// cmd = exec.Command("/usr/sbin/tc", "filter", "add", "dev", interfaceName, "parent", "1:", "protocol", protocol,
|
|
||||||
// "prio", "16", "u32", "match", "ip", "dst", subnet, "flowid", "1:1")
|
|
||||||
_, nw, err := net.ParseCIDR(subnet)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("bad subnet %s: %s", subnet, err)
|
|
||||||
}
|
|
||||||
var maskBytes []byte = nw.Mask
|
|
||||||
var subnetBytes []byte = nw.IP
|
|
||||||
|
|
||||||
if len(maskBytes) != len(subnetBytes) {
|
|
||||||
return fmt.Errorf("error using net lib for subnet %s len(maskBytes) != len(subnetBytes) "+
|
|
||||||
"(%d != %d) should not happen", subnet, len(maskBytes), len(subnetBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
isIpv4 := nw.IP.To4() != nil
|
|
||||||
protocol := syscall.ETH_P_IPV6
|
|
||||||
var prio uint16 = 15
|
|
||||||
var offset int32 = 24
|
|
||||||
keepBytes := 16
|
|
||||||
if isIpv4 {
|
|
||||||
protocol = syscall.ETH_P_IP
|
|
||||||
offset = 16
|
|
||||||
keepBytes = 4
|
|
||||||
// prio/pref needs to be changed if we change the protocol, looks like we cannot mix protocols with the same pref
|
|
||||||
prio = 16
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(maskBytes) < keepBytes {
|
|
||||||
return fmt.Errorf("error with net lib, unexpected count of bytes for ipv4 mask (%d < %d)",
|
|
||||||
len(maskBytes), keepBytes)
|
|
||||||
}
|
|
||||||
if len(subnetBytes) < keepBytes {
|
|
||||||
return fmt.Errorf("error with net lib, unexpected count of bytes for ipv4 subnet (%d < %d)",
|
|
||||||
len(subnetBytes), keepBytes)
|
|
||||||
}
|
|
||||||
maskBytes = maskBytes[len(maskBytes)-keepBytes:]
|
|
||||||
subnetBytes = subnetBytes[len(subnetBytes)-keepBytes:]
|
|
||||||
|
|
||||||
// For ipv4 we should have at most 1 key, for ipv6 at most 4
|
|
||||||
keys := make([]netlink.TcU32Key, 0, 4)
|
|
||||||
|
|
||||||
for i := 0; i < len(maskBytes); i += 4 {
|
|
||||||
var mask, subnetI uint32
|
|
||||||
buf := bytes.NewReader(maskBytes[i : i+4])
|
|
||||||
err = binary.Read(buf, binary.BigEndian, &mask)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error, htb filter, unable to build mask match filter, iter %d for subnet %s",
|
|
||||||
i, subnet)
|
|
||||||
}
|
|
||||||
|
|
||||||
if mask != 0 {
|
|
||||||
// If mask == 0, any value on this section will be a match and we do not need a filter for this
|
|
||||||
buf = bytes.NewReader(subnetBytes[i : i+4])
|
|
||||||
err = binary.Read(buf, binary.BigEndian, &subnetI)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error, htb filter, unable to build subnet match filter, iter %d for subnet %s",
|
|
||||||
i, subnet)
|
|
||||||
}
|
|
||||||
keys = append(keys, netlink.TcU32Key{
|
|
||||||
Mask: mask,
|
|
||||||
Val: subnetI,
|
|
||||||
Off: offset,
|
|
||||||
OffMask: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += 4
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(keys) != cap(keys) {
|
|
||||||
shrinkedKeys := make([]netlink.TcU32Key, len(keys))
|
|
||||||
copied := copy(shrinkedKeys, keys)
|
|
||||||
if copied != len(keys) {
|
|
||||||
return fmt.Errorf("copy tc u32 keys error, for subnet %s copied %d != keys %d", subnet, copied, len(keys))
|
|
||||||
}
|
|
||||||
keys = shrinkedKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
if isIpv4 && len(keys) > 1 {
|
|
||||||
return fmt.Errorf("error, htb ipv4 filter, unexpected rule length (%d > 1), for subnet %s",
|
|
||||||
len(keys), subnet)
|
|
||||||
} else if len(keys) > 4 {
|
|
||||||
return fmt.Errorf("error, htb ipv6 filter, unexpected rule length (%d > 4), for subnet %s",
|
|
||||||
len(keys), subnet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If len(keys) == 0, it means that we want to wildcard all traffic on the non default/uncapped class
|
|
||||||
var selector *netlink.TcU32Sel
|
|
||||||
if len(keys) > 0 {
|
|
||||||
selector = &netlink.TcU32Sel{
|
|
||||||
Nkeys: uint8(len(keys)),
|
|
||||||
Flags: netlink.TC_U32_TERMINAL,
|
|
||||||
Keys: keys,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
classID := shapedClass.Handle
|
|
||||||
if excludeSubnets {
|
|
||||||
classID = unshapedClass.Handle
|
|
||||||
}
|
|
||||||
|
|
||||||
tcFilter := netlink.U32{
|
|
||||||
FilterAttrs: netlink.FilterAttrs{
|
|
||||||
LinkIndex: linkIndex,
|
|
||||||
Parent: qdisc.Handle,
|
|
||||||
Priority: prio,
|
|
||||||
Protocol: uint16(protocol),
|
|
||||||
},
|
|
||||||
ClassId: classID,
|
|
||||||
Sel: selector,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = netlink.FilterAdd(&tcFilter)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error, unable to create htb filter, details %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -346,3 +153,11 @@ func time2Tick(time uint32) uint32 {
|
|||||||
func buffer(rate uint64, burst uint32) uint32 {
|
func buffer(rate uint64, burst uint32) uint32 {
|
||||||
return time2Tick(uint32(float64(burst) * float64(netlink.TIME_UNITS_PER_SEC) / float64(rate)))
|
return time2Tick(uint32(float64(burst) * float64(netlink.TIME_UNITS_PER_SEC) / float64(rate)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func limit(rate uint64, latency float64, buffer uint32) uint32 {
|
||||||
|
return uint32(float64(rate)*latency/float64(netlink.TIME_UNITS_PER_SEC)) + buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func latencyInUsec(latencyInMillis float64) float64 {
|
||||||
|
return float64(netlink.TIME_UNITS_PER_SEC) * (latencyInMillis / 1000.0)
|
||||||
|
}
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
@ -40,10 +39,9 @@ const (
|
|||||||
// BandwidthEntry corresponds to a single entry in the bandwidth argument,
|
// BandwidthEntry corresponds to a single entry in the bandwidth argument,
|
||||||
// see CONVENTIONS.md
|
// see CONVENTIONS.md
|
||||||
type BandwidthEntry struct {
|
type BandwidthEntry struct {
|
||||||
UnshapedSubnets []string `json:"unshapedSubnets"` // Ipv4/ipv6 subnets to be excluded from traffic shaping. UnshapedSubnets and ShapedSubnets parameters are mutually exlusive
|
|
||||||
ShapedSubnets []string `json:"shapedSubnets"` // Ipv4/ipv6 subnets to be included in traffic shaping. UnshapedSubnets and ShapedSubnets parameters are mutually exlusive
|
|
||||||
IngressRate uint64 `json:"ingressRate"` // Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
|
IngressRate uint64 `json:"ingressRate"` // Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
|
||||||
IngressBurst uint64 `json:"ingressBurst"` // Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
|
IngressBurst uint64 `json:"ingressBurst"` // Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
|
||||||
|
|
||||||
EgressRate uint64 `json:"egressRate"` // Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
|
EgressRate uint64 `json:"egressRate"` // Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
|
||||||
EgressBurst uint64 `json:"egressBurst"` // Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
|
EgressBurst uint64 `json:"egressBurst"` // Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
|
||||||
}
|
}
|
||||||
@ -98,21 +96,10 @@ func parseConfig(stdin []byte) (*PluginConf, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getBandwidth(conf *PluginConf) *BandwidthEntry {
|
func getBandwidth(conf *PluginConf) *BandwidthEntry {
|
||||||
bw := conf.BandwidthEntry
|
if conf.BandwidthEntry == nil && conf.RuntimeConfig.Bandwidth != nil {
|
||||||
if bw == nil && conf.RuntimeConfig.Bandwidth != nil {
|
return conf.RuntimeConfig.Bandwidth
|
||||||
bw = conf.RuntimeConfig.Bandwidth
|
|
||||||
}
|
}
|
||||||
|
return conf.BandwidthEntry
|
||||||
if bw != nil {
|
|
||||||
if bw.UnshapedSubnets == nil {
|
|
||||||
bw.UnshapedSubnets = make([]string, 0)
|
|
||||||
}
|
|
||||||
if bw.ShapedSubnets == nil {
|
|
||||||
bw.ShapedSubnets = make([]string, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRateAndBurst(rate, burst uint64) error {
|
func validateRateAndBurst(rate, burst uint64) error {
|
||||||
@ -132,13 +119,13 @@ func getIfbDeviceName(networkName string, containerID string) string {
|
|||||||
return utils.MustFormatHashWithPrefix(maxIfbDeviceLength, ifbDevicePrefix, networkName+containerID)
|
return utils.MustFormatHashWithPrefix(maxIfbDeviceLength, ifbDevicePrefix, networkName+containerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMTUAndQLen(deviceName string) (int, int, error) {
|
func getMTU(deviceName string) (int, error) {
|
||||||
link, err := netlink.LinkByName(deviceName)
|
link, err := netlink.LinkByName(deviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return link.Attrs().MTU, link.Attrs().TxQLen, nil
|
return link.Attrs().MTU, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the veth peer of container interface in host namespace
|
// get the veth peer of container interface in host namespace
|
||||||
@ -172,28 +159,6 @@ func getHostInterface(interfaces []*current.Interface, containerIfName string, n
|
|||||||
return nil, fmt.Errorf("no veth peer of container interface found in host ns")
|
return nil, fmt.Errorf("no veth peer of container interface found in host ns")
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSubnets(unshapedSubnets []string, shapedSubnets []string) error {
|
|
||||||
if len(unshapedSubnets) > 0 && len(shapedSubnets) > 0 {
|
|
||||||
return fmt.Errorf("unshapedSubnets and shapedSubnets cannot be both specified, one of them should be discarded")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, subnet := range unshapedSubnets {
|
|
||||||
_, _, err := net.ParseCIDR(subnet)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("bad subnet %q provided, details %s", subnet, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, subnet := range shapedSubnets {
|
|
||||||
_, _, err := net.ParseCIDR(subnet)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("bad subnet %q provided, details %s", subnet, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cmdAdd(args *skel.CmdArgs) error {
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
conf, err := parseConfig(args.StdinData)
|
conf, err := parseConfig(args.StdinData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -205,10 +170,6 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
return types.PrintResult(conf.PrevResult, conf.CNIVersion)
|
return types.PrintResult(conf.PrevResult, conf.CNIVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validateSubnets(bandwidth.UnshapedSubnets, bandwidth.ShapedSubnets); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.PrevResult == nil {
|
if conf.PrevResult == nil {
|
||||||
return fmt.Errorf("must be called as chained plugin")
|
return fmt.Errorf("must be called as chained plugin")
|
||||||
}
|
}
|
||||||
@ -230,22 +191,21 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if bandwidth.IngressRate > 0 && bandwidth.IngressBurst > 0 {
|
if bandwidth.IngressRate > 0 && bandwidth.IngressBurst > 0 {
|
||||||
err = CreateIngressQdisc(bandwidth.IngressRate, bandwidth.IngressBurst,
|
err = CreateIngressQdisc(bandwidth.IngressRate, bandwidth.IngressBurst, hostInterface.Name)
|
||||||
bandwidth.UnshapedSubnets, bandwidth.ShapedSubnets, hostInterface.Name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if bandwidth.EgressRate > 0 && bandwidth.EgressBurst > 0 {
|
if bandwidth.EgressRate > 0 && bandwidth.EgressBurst > 0 {
|
||||||
mtu, qlen, err := getMTUAndQLen(hostInterface.Name)
|
mtu, err := getMTU(hostInterface.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ifbDeviceName := getIfbDeviceName(conf.Name, args.ContainerID)
|
ifbDeviceName := getIfbDeviceName(conf.Name, args.ContainerID)
|
||||||
|
|
||||||
err = CreateIfb(ifbDeviceName, mtu, qlen)
|
err = CreateIfb(ifbDeviceName, mtu)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -259,9 +219,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
Name: ifbDeviceName,
|
Name: ifbDeviceName,
|
||||||
Mac: ifbDevice.Attrs().HardwareAddr.String(),
|
Mac: ifbDevice.Attrs().HardwareAddr.String(),
|
||||||
})
|
})
|
||||||
err = CreateEgressQdisc(bandwidth.EgressRate, bandwidth.EgressBurst,
|
err = CreateEgressQdisc(bandwidth.EgressRate, bandwidth.EgressBurst, hostInterface.Name, ifbDeviceName)
|
||||||
bandwidth.UnshapedSubnets, bandwidth.ShapedSubnets, hostInterface.Name,
|
|
||||||
ifbDeviceName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -340,37 +298,13 @@ func cmdCheck(args *skel.CmdArgs) error {
|
|||||||
|
|
||||||
bandwidth := getBandwidth(bwConf)
|
bandwidth := getBandwidth(bwConf)
|
||||||
|
|
||||||
if err = validateSubnets(bandwidth.UnshapedSubnets, bandwidth.ShapedSubnets); err != nil {
|
|
||||||
return fmt.Errorf("failed to check subnets, details %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bandwidth.IngressRate > 0 && bandwidth.IngressBurst > 0 {
|
if bandwidth.IngressRate > 0 && bandwidth.IngressBurst > 0 {
|
||||||
rateInBytes := bandwidth.IngressRate / 8
|
rateInBytes := bandwidth.IngressRate / 8
|
||||||
burstInBytes := bandwidth.IngressBurst / 8
|
burstInBytes := bandwidth.IngressBurst / 8
|
||||||
bufferInBytes := buffer(rateInBytes, uint32(burstInBytes))
|
bufferInBytes := buffer(rateInBytes, uint32(burstInBytes))
|
||||||
err = checkHTB(link, rateInBytes, bufferInBytes, bandwidth.ShapedSubnets)
|
latency := latencyInUsec(latencyInMillis)
|
||||||
if err != nil {
|
limitInBytes := limit(rateInBytes, latency, uint32(burstInBytes))
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if bandwidth.EgressRate > 0 && bandwidth.EgressBurst > 0 {
|
|
||||||
rateInBytes := bandwidth.EgressRate / 8
|
|
||||||
burstInBytes := bandwidth.EgressBurst / 8
|
|
||||||
bufferInBytes := buffer(rateInBytes, uint32(burstInBytes))
|
|
||||||
ifbDeviceName := getIfbDeviceName(bwConf.Name, args.ContainerID)
|
|
||||||
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get ifb device: %s", err)
|
|
||||||
}
|
|
||||||
err = checkHTB(ifbDevice, rateInBytes, bufferInBytes, bandwidth.ShapedSubnets)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkHTB(link netlink.Link, rateInBytes uint64, bufferInBytes uint32, shapedSubnets []string) error {
|
|
||||||
qdiscs, err := SafeQdiscList(link)
|
qdiscs, err := SafeQdiscList(link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -378,62 +312,62 @@ func checkHTB(link netlink.Link, rateInBytes uint64, bufferInBytes uint32, shape
|
|||||||
if len(qdiscs) == 0 {
|
if len(qdiscs) == 0 {
|
||||||
return fmt.Errorf("Failed to find qdisc")
|
return fmt.Errorf("Failed to find qdisc")
|
||||||
}
|
}
|
||||||
foundHTB := false
|
|
||||||
for _, qdisc := range qdiscs {
|
for _, qdisc := range qdiscs {
|
||||||
htb, isHtb := qdisc.(*netlink.Htb)
|
tbf, isTbf := qdisc.(*netlink.Tbf)
|
||||||
if !isHtb {
|
if !isTbf {
|
||||||
continue
|
break
|
||||||
|
}
|
||||||
|
if tbf.Rate != rateInBytes {
|
||||||
|
return fmt.Errorf("Rate doesn't match")
|
||||||
|
}
|
||||||
|
if tbf.Limit != limitInBytes {
|
||||||
|
return fmt.Errorf("Limit doesn't match")
|
||||||
|
}
|
||||||
|
if tbf.Buffer != bufferInBytes {
|
||||||
|
return fmt.Errorf("Buffer doesn't match")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if foundHTB {
|
if bandwidth.EgressRate > 0 && bandwidth.EgressBurst > 0 {
|
||||||
return fmt.Errorf("Several htb qdisc found for device %s", link.Attrs().Name)
|
rateInBytes := bandwidth.EgressRate / 8
|
||||||
}
|
burstInBytes := bandwidth.EgressBurst / 8
|
||||||
|
bufferInBytes := buffer(rateInBytes, uint32(burstInBytes))
|
||||||
|
latency := latencyInUsec(latencyInMillis)
|
||||||
|
limitInBytes := limit(rateInBytes, latency, uint32(burstInBytes))
|
||||||
|
|
||||||
foundHTB = true
|
ifbDeviceName := getIfbDeviceName(bwConf.Name, args.ContainerID)
|
||||||
defaultClassMinorID := ShapedClassMinorID
|
|
||||||
if len(shapedSubnets) > 0 {
|
|
||||||
defaultClassMinorID = UnShapedClassMinorID
|
|
||||||
}
|
|
||||||
|
|
||||||
if htb.Defcls != uint32(defaultClassMinorID) {
|
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
|
||||||
return fmt.Errorf("Default class does not match")
|
|
||||||
}
|
|
||||||
|
|
||||||
classes, err := netlink.ClassList(link, htb.Handle)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to list classes bound to htb qdisc for device %s. Details %s",
|
return fmt.Errorf("get ifb device: %s", err)
|
||||||
link.Attrs().Name, err)
|
|
||||||
}
|
|
||||||
if len(classes) != 2 {
|
|
||||||
return fmt.Errorf("Number of htb classes does not match for device %s (%d != 2)",
|
|
||||||
link.Attrs().Name, len(classes))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range classes {
|
qdiscs, err := SafeQdiscList(ifbDevice)
|
||||||
htbClass, isHtb := c.(*netlink.HtbClass)
|
if err != nil {
|
||||||
if !isHtb {
|
return err
|
||||||
return fmt.Errorf("Unexpected class for parent htb qdisc bound to device %s", link.Attrs().Name)
|
|
||||||
}
|
}
|
||||||
if htbClass.Handle == htb.Defcls {
|
if len(qdiscs) == 0 {
|
||||||
if htbClass.Rate != rateInBytes {
|
return fmt.Errorf("Failed to find qdisc")
|
||||||
return fmt.Errorf("Rate does not match for the default class for device %s (%d != %d)",
|
|
||||||
link.Attrs().Name, htbClass.Rate, rateInBytes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if htbClass.Buffer != bufferInBytes {
|
for _, qdisc := range qdiscs {
|
||||||
return fmt.Errorf("Burst buffer size does not match for the default class for device %s (%d != %d)",
|
tbf, isTbf := qdisc.(*netlink.Tbf)
|
||||||
link.Attrs().Name, htbClass.Buffer, bufferInBytes)
|
if !isTbf {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
} else if htbClass.Handle == netlink.MakeHandle(1, 1) {
|
if tbf.Rate != rateInBytes {
|
||||||
if htbClass.Rate != UncappedRate {
|
return fmt.Errorf("Rate doesn't match")
|
||||||
return fmt.Errorf("Rate does not match for the uncapped class for device %s (%d != %d)",
|
}
|
||||||
link.Attrs().Name, htbClass.Rate, UncappedRate)
|
if tbf.Limit != limitInBytes {
|
||||||
|
return fmt.Errorf("Limit doesn't match")
|
||||||
|
}
|
||||||
|
if tbf.Buffer != bufferInBytes {
|
||||||
|
return fmt.Errorf("Buffer doesn't match")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check subnet filters
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user