Merge pull request #138 from m1093782566/runtime-config

traffic shaping: take configuration via a runtimeConfig
This commit is contained in:
Casey Callendrello 2018-04-25 17:09:41 +02:00 committed by GitHub
commit adba9ec16e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 223 additions and 32 deletions

View File

@ -14,10 +14,14 @@
}, },
{ {
"type": "bandwidth", "type": "bandwidth",
"ingressRate": 8000, "runtimeConfig": {
"ingressBurst": 16000, "bandWidth": {
"egressRate": 8000, "ingressRate": 8000,
"egressBurst": 16000 "ingressBurst": 16000,
"egressRate": 8000,
"egressBurst": 16000
}
}
} }
] ]
} }

View File

@ -13,10 +13,14 @@
}, },
{ {
"type": "bandwidth", "type": "bandwidth",
"ingressRate": 800, "runtimeConfig": {
"ingressBurst": 200, "bandWidth": {
"egressRate": 800, "ingressRate": 800,
"egressBurst": 200 "ingressBurst": 200,
"egressRate": 800,
"egressBurst": 200
}
}
} }
] ]
} }

View File

@ -358,6 +358,165 @@ var _ = Describe("bandwidth test", func() {
return nil return nil
})).To(Succeed()) })).To(Succeed())
}) })
It("Works with a Veth pair using runtime config", func() {
conf := `{
"cniVersion": "0.3.0",
"name": "cni-plugin-bandwidth-test",
"type": "bandwidth",
"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": []
}
}`
conf = fmt.Sprintf(conf, 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.CmdAddWithResult(containerNs.Path(), "", []byte(conf), func() error { return cmdAdd(args) })
Expect(err).NotTo(HaveOccurred(), string(out))
result, err := current.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.Tbf{}))
Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(2)))
Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(9)))
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())
Expect(hostNs.Do(func(n ns.NetNS) error {
defer GinkgoRecover()
ifbLink, err := netlink.LinkByName(hostIfname)
Expect(err).NotTo(HaveOccurred())
qdiscs, err := netlink.QdiscList(ifbLink)
Expect(err).NotTo(HaveOccurred())
Expect(qdiscs).To(HaveLen(2))
Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index))
Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{}))
Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(1)))
Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(8)))
return nil
})).To(Succeed())
})
It("Should apply static config when both static config and runtime config exist", func() {
conf := `{
"cniVersion": "0.3.0",
"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": []
}
}`
conf = fmt.Sprintf(conf, 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.CmdAddWithResult(containerNs.Path(), "", []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("cmdDEL", func() { Describe("cmdDEL", func() {
@ -484,7 +643,8 @@ var _ = Describe("bandwidth test", func() {
containerWithTbfResult, err := current.GetResult(containerWithTbfRes) containerWithTbfResult, err := current.GetResult(containerWithTbfRes)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
tbfPluginConf := PluginConf{ tbfPluginConf := PluginConf{}
tbfPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
IngressBurst: burstInBits, IngressBurst: burstInBits,
IngressRate: rateInBits, IngressRate: rateInBits,
EgressBurst: burstInBits, EgressBurst: burstInBits,

View File

@ -17,25 +17,21 @@ package main
import ( import (
"crypto/sha1" "crypto/sha1"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
"errors"
"github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/ip"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
) )
type PluginConf struct { // BandwidthEntry corresponds to a single entry in the bandwidth argument,
types.NetConf // see CONVENTIONS.md
RuntimeConfig *struct{} `json:"runtimeConfig"` type BandwidthEntry struct {
RawPrevResult *map[string]interface{} `json:"prevResult"`
PrevResult *current.Result `json:"-"`
IngressRate int `json:"ingressRate"` //Bandwidth rate in Kbps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set IngressRate int `json:"ingressRate"` //Bandwidth rate in Kbps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
IngressBurst int `json:"ingressBurst"` //Bandwidth burst in Kb for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set IngressBurst int `json:"ingressBurst"` //Bandwidth burst in Kb for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
@ -43,6 +39,24 @@ type PluginConf struct {
EgressBurst int `json:"egressBurst"` //Bandwidth burst in Kb for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set EgressBurst int `json:"egressBurst"` //Bandwidth burst in Kb for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
} }
func (bw *BandwidthEntry) isZero() bool {
return bw.IngressBurst == 0 && bw.IngressRate == 0 && bw.EgressBurst == 0 && bw.EgressRate == 0
}
type PluginConf struct {
types.NetConf
RuntimeConfig struct {
Bandwidth *BandwidthEntry `json:"bandwidth,omitempty"`
} `json:"runtimeConfig,omitempty"`
// RuntimeConfig *struct{} `json:"runtimeConfig"`
RawPrevResult *map[string]interface{} `json:"prevResult"`
PrevResult *current.Result `json:"-"`
*BandwidthEntry
}
// parseConfig parses the supplied configuration (and prevResult) from stdin. // parseConfig parses the supplied configuration (and prevResult) from stdin.
func parseConfig(stdin []byte) (*PluginConf, error) { func parseConfig(stdin []byte) (*PluginConf, error) {
conf := PluginConf{} conf := PluginConf{}
@ -66,20 +80,29 @@ func parseConfig(stdin []byte) (*PluginConf, error) {
return nil, fmt.Errorf("could not convert result to current version: %v", err) return nil, fmt.Errorf("could not convert result to current version: %v", err)
} }
} }
bandwidth := getBandwidth(&conf)
err := validateRateAndBurst(conf.IngressRate, conf.IngressBurst) if bandwidth != nil {
if err != nil { err := validateRateAndBurst(bandwidth.IngressRate, bandwidth.IngressBurst)
return nil, err if err != nil {
} return nil, err
err = validateRateAndBurst(conf.EgressRate, conf.EgressBurst) }
if err != nil { err = validateRateAndBurst(bandwidth.EgressRate, bandwidth.EgressBurst)
return nil, err if err != nil {
return nil, err
}
} }
return &conf, nil return &conf, nil
} }
func getBandwidth(conf *PluginConf) *BandwidthEntry {
if conf.BandwidthEntry == nil && conf.RuntimeConfig.Bandwidth != nil {
return conf.RuntimeConfig.Bandwidth
}
return conf.BandwidthEntry
}
func validateRateAndBurst(rate int, burst int) error { func validateRateAndBurst(rate int, burst int) error {
switch { switch {
case burst < 0 || rate < 0: case burst < 0 || rate < 0:
@ -135,8 +158,8 @@ func cmdAdd(args *skel.CmdArgs) error {
return err return err
} }
//no traffic shaping was requested, so just no-op and quit bandwidth := getBandwidth(conf)
if conf.IngressRate == 0 && conf.IngressBurst == 0 && conf.EgressRate == 0 && conf.EgressBurst == 0 { if bandwidth == nil || bandwidth.isZero() {
return types.PrintResult(conf.PrevResult, conf.CNIVersion) return types.PrintResult(conf.PrevResult, conf.CNIVersion)
} }
@ -149,14 +172,14 @@ func cmdAdd(args *skel.CmdArgs) error {
return err return err
} }
if conf.IngressRate > 0 && conf.IngressBurst > 0 { if bandwidth.IngressRate > 0 && bandwidth.IngressBurst > 0 {
err = CreateIngressQdisc(conf.IngressRate, conf.IngressBurst, hostInterface.Name) err = CreateIngressQdisc(bandwidth.IngressRate, bandwidth.IngressBurst, hostInterface.Name)
if err != nil { if err != nil {
return err return err
} }
} }
if conf.EgressRate > 0 && conf.EgressBurst > 0 { if bandwidth.EgressRate > 0 && bandwidth.EgressBurst > 0 {
mtu, err := getMTU(hostInterface.Name) mtu, err := getMTU(hostInterface.Name)
if err != nil { if err != nil {
return err return err
@ -181,7 +204,7 @@ func cmdAdd(args *skel.CmdArgs) error {
Name: ifbDeviceName, Name: ifbDeviceName,
Mac: ifbDevice.Attrs().HardwareAddr.String(), Mac: ifbDevice.Attrs().HardwareAddr.String(),
}) })
err = CreateEgressQdisc(conf.EgressRate, conf.EgressBurst, hostInterface.Name, ifbDeviceName) err = CreateEgressQdisc(bandwidth.EgressRate, bandwidth.EgressBurst, hostInterface.Name, ifbDeviceName)
if err != nil { if err != nil {
return err return err
} }