Add check support for: bridge, ipvlan, macvlan, p2p, vlan and host-device main plugins

host-local and static ipam plugins
  tuning, bandwidth and portmap meta plugins

  Utility functions created for common PrevResult checking

  Fix windows build
This commit is contained in:
Michael Cambria
2018-12-06 15:42:37 -05:00
parent 82a0651d0a
commit 74a2596573
28 changed files with 3759 additions and 167 deletions

View File

@ -35,6 +35,49 @@ import (
"github.com/onsi/gomega/gexec"
)
func buildOneConfig(name, cniVersion string, orig *PluginConf, prevResult types.Result) (*PluginConf, []byte, error) {
var err error
inject := map[string]interface{}{
"name": name,
"cniVersion": cniVersion,
}
// Add previous plugin result
if prevResult != nil {
inject["prevResult"] = prevResult
}
// Ensure every config uses the same name and version
config := make(map[string]interface{})
confBytes, err := json.Marshal(orig)
if err != nil {
return nil, nil, err
}
err = json.Unmarshal(confBytes, &config)
if err != nil {
return nil, 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, nil, err
}
conf := &PluginConf{}
if err := json.Unmarshal(newBytes, &conf); err != nil {
return nil, nil, fmt.Errorf("error parsing configuration: %s", err)
}
return conf, newBytes, nil
}
var _ = Describe("bandwidth test", func() {
var (
hostNs ns.NetNS
@ -643,7 +686,6 @@ var _ = Describe("bandwidth test", func() {
containerWithTbfResult, err := current.GetResult(containerWithTbfRes)
Expect(err).NotTo(HaveOccurred())
tbfPluginConf := PluginConf{}
tbfPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
IngressBurst: burstInBits,
@ -654,7 +696,7 @@ var _ = Describe("bandwidth test", func() {
tbfPluginConf.Name = "mynet"
tbfPluginConf.CNIVersion = "0.3.0"
tbfPluginConf.Type = "bandwidth"
tbfPluginConf.RawPrevResult = &map[string]interface{}{
tbfPluginConf.RawPrevResult = map[string]interface{}{
"ips": containerWithTbfResult.IPs,
"interfaces": containerWithTbfResult.Interfaces,
}
@ -663,7 +705,6 @@ var _ = Describe("bandwidth test", func() {
IPs: containerWithTbfResult.IPs,
Interfaces: containerWithTbfResult.Interfaces,
}
conf, err := json.Marshal(tbfPluginConf)
Expect(err).NotTo(HaveOccurred())
@ -725,4 +766,169 @@ var _ = Describe("bandwidth test", func() {
}, 1)
})
Context("when chaining bandwidth plugin with PTP using 0.4.0 config", func() {
var ptpConf string
var rateInBits int
var burstInBits int
var packetInBytes int
var containerWithoutTbfNS ns.NetNS
var containerWithTbfNS ns.NetNS
var portServerWithTbf int
var portServerWithoutTbf int
var containerWithTbfRes types.Result
var containerWithoutTbfRes types.Result
var echoServerWithTbf *gexec.Session
var echoServerWithoutTbf *gexec.Session
BeforeEach(func() {
rateInBytes := 1000
rateInBits = rateInBytes * 8
burstInBits = rateInBits * 2
packetInBytes = rateInBytes * 25
ptpConf = `{
"cniVersion": "0.4.0",
"name": "myBWnet",
"type": "ptp",
"ipMasq": true,
"mtu": 512,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`
containerWithTbfIFName := "ptp0"
containerWithoutTbfIFName := "ptp1"
var err error
containerWithTbfNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
containerWithoutTbfNS, 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()
containerWithTbfRes, _, err = testutils.CmdAdd(containerWithTbfNS.Path(), "dummy", containerWithTbfIFName, []byte(ptpConf), func() error {
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
Expect(r.Print()).To(Succeed())
return err
})
Expect(err).NotTo(HaveOccurred())
containerWithoutTbfRes, _, err = testutils.CmdAdd(containerWithoutTbfNS.Path(), "dummy2", containerWithoutTbfIFName, []byte(ptpConf), func() error {
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
Expect(r.Print()).To(Succeed())
return err
})
Expect(err).NotTo(HaveOccurred())
containerWithTbfResult, err := current.GetResult(containerWithTbfRes)
Expect(err).NotTo(HaveOccurred())
tbfPluginConf := &PluginConf{}
err = json.Unmarshal([]byte(ptpConf), &tbfPluginConf)
Expect(err).NotTo(HaveOccurred())
tbfPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
IngressBurst: burstInBits,
IngressRate: rateInBits,
EgressBurst: burstInBits,
EgressRate: rateInBits,
}
tbfPluginConf.Type = "bandwidth"
cniVersion := "0.4.0"
_, newConfBytes, err := buildOneConfig("myBWnet", cniVersion, tbfPluginConf, containerWithTbfResult)
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
ContainerID: "dummy3",
Netns: containerWithTbfNS.Path(),
IfName: containerWithTbfIFName,
StdinData: newConfBytes,
}
result, out, err := testutils.CmdAdd(containerWithTbfNS.Path(), args.ContainerID, "", newConfBytes, func() error { return cmdAdd(args) })
Expect(err).NotTo(HaveOccurred(), string(out))
// 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("myBWnet", cniVersion, checkConf, result)
Expect(err).NotTo(HaveOccurred())
args = &skel.CmdArgs{
ContainerID: "dummy3",
Netns: containerWithTbfNS.Path(),
IfName: containerWithTbfIFName,
StdinData: newCheckBytes,
}
err = testutils.CmdCheck(containerWithTbfNS.Path(), args.ContainerID, "", newCheckBytes, func() error { return cmdCheck(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})).To(Succeed())
By("starting a tcp server on both containers")
portServerWithTbf, echoServerWithTbf, err = startEchoServerInNamespace(containerWithTbfNS)
Expect(err).NotTo(HaveOccurred())
portServerWithoutTbf, echoServerWithoutTbf, err = startEchoServerInNamespace(containerWithoutTbfNS)
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
containerWithTbfNS.Close()
containerWithoutTbfNS.Close()
if echoServerWithoutTbf != nil {
echoServerWithoutTbf.Kill()
}
if echoServerWithTbf != nil {
echoServerWithTbf.Kill()
}
})
Measure("limits ingress traffic on veth device", func(b Benchmarker) {
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() {
runtimeWithLimit = b.Time("with tbf", func() {
result, err := current.GetResult(containerWithTbfRes)
Expect(err).NotTo(HaveOccurred())
makeTcpClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithTbf, packetInBytes)
})
})
By("sending tcp traffic to the container that does not have traffic shaped", func() {
runtimeWithoutLimit = b.Time("without tbf", func() {
result, err := current.GetResult(containerWithoutTbfRes)
Expect(err).NotTo(HaveOccurred())
makeTcpClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithoutTbf, packetInBytes)
})
})
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
}, 1)
})
})

View File

@ -50,10 +50,6 @@ type PluginConf struct {
Bandwidth *BandwidthEntry `json:"bandwidth,omitempty"`
} `json:"runtimeConfig,omitempty"`
// RuntimeConfig *struct{} `json:"runtimeConfig"`
RawPrevResult *map[string]interface{} `json:"prevResult"`
PrevResult *current.Result `json:"-"`
*BandwidthEntry
}
@ -65,21 +61,6 @@ func parseConfig(stdin []byte) (*PluginConf, error) {
return nil, fmt.Errorf("failed to parse network configuration: %v", err)
}
if conf.RawPrevResult != nil {
resultBytes, err := json.Marshal(conf.RawPrevResult)
if err != nil {
return nil, fmt.Errorf("could not serialize prevResult: %v", err)
}
res, err := version.NewResult(conf.CNIVersion, resultBytes)
if err != nil {
return nil, fmt.Errorf("could not parse prevResult: %v", err)
}
conf.RawPrevResult = nil
conf.PrevResult, err = current.NewResultFromResult(res)
if err != nil {
return nil, fmt.Errorf("could not convert result to current version: %v", err)
}
}
bandwidth := getBandwidth(&conf)
if bandwidth != nil {
err := validateRateAndBurst(bandwidth.IngressRate, bandwidth.IngressBurst)
@ -92,6 +73,18 @@ func parseConfig(stdin []byte) (*PluginConf, error) {
}
}
if conf.RawPrevResult != nil {
var err error
if err = version.ParsePrevResult(&conf.NetConf); err != nil {
return nil, fmt.Errorf("could not parse prevResult: %v", err)
}
_, err = current.NewResultFromResult(conf.PrevResult)
if err != nil {
return nil, fmt.Errorf("could not convert result to current version: %v", err)
}
}
return &conf, nil
}
@ -167,7 +160,11 @@ func cmdAdd(args *skel.CmdArgs) error {
return fmt.Errorf("must be called as chained plugin")
}
hostInterface, err := getHostInterface(conf.PrevResult.Interfaces)
result, err := current.NewResultFromResult(conf.PrevResult)
if err != nil {
return fmt.Errorf("could not convert result to current version: %v", err)
}
hostInterface, err := getHostInterface(result.Interfaces)
if err != nil {
return err
}
@ -200,7 +197,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
conf.PrevResult.Interfaces = append(conf.PrevResult.Interfaces, &current.Interface{
result.Interfaces = append(result.Interfaces, &current.Interface{
Name: ifbDeviceName,
Mac: ifbDevice.Attrs().HardwareAddr.String(),
})
@ -210,7 +207,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
}
return types.PrintResult(conf.PrevResult, conf.CNIVersion)
return types.PrintResult(result, conf.CNIVersion)
}
func cmdDel(args *skel.CmdArgs) error {
@ -233,10 +230,125 @@ func cmdDel(args *skel.CmdArgs) error {
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.PluginSupports("0.3.0", "0.3.1", version.Current()), "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports("0.3.0", "0.3.1", version.Current()), "TODO")
}
func cmdGet(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
func SafeQdiscList(link netlink.Link) ([]netlink.Qdisc, error) {
qdiscs, err := netlink.QdiscList(link)
if err != nil {
return nil, err
}
result := []netlink.Qdisc{}
for _, qdisc := range qdiscs {
// filter out pfifo_fast qdiscs because
// older kernels don't return them
_, pfifo := qdisc.(*netlink.PfifoFast)
if !pfifo {
result = append(result, qdisc)
}
}
return result, nil
}
func cmdCheck(args *skel.CmdArgs) error {
bwConf, err := parseConfig(args.StdinData)
if err != nil {
return err
}
if bwConf.PrevResult == nil {
return fmt.Errorf("must be called as a chained plugin")
}
result, err := current.NewResultFromResult(bwConf.PrevResult)
if err != nil {
return fmt.Errorf("could not convert result to current version: %v", err)
}
hostInterface, err := getHostInterface(result.Interfaces)
if err != nil {
return err
}
link, err := netlink.LinkByName(hostInterface.Name)
if err != nil {
return err
}
bandwidth := getBandwidth(bwConf)
if bandwidth.IngressRate > 0 && bandwidth.IngressBurst > 0 {
rateInBytes := bandwidth.IngressRate / 8
burstInBytes := bandwidth.IngressBurst / 8
bufferInBytes := buffer(uint64(rateInBytes), uint32(burstInBytes))
latency := latencyInUsec(latencyInMillis)
limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes))
qdiscs, err := SafeQdiscList(link)
if err != nil {
return err
}
if len(qdiscs) == 0 {
return fmt.Errorf("Failed to find qdisc")
}
for _, qdisc := range qdiscs {
tbf, isTbf := qdisc.(*netlink.Tbf)
if !isTbf {
break
}
if tbf.Rate != uint64(rateInBytes) {
return fmt.Errorf("Rate doesn't match")
}
if tbf.Limit != uint32(limitInBytes) {
return fmt.Errorf("Limit doesn't match")
}
if tbf.Buffer != uint32(bufferInBytes) {
return fmt.Errorf("Buffer doesn't match")
}
}
}
if bandwidth.EgressRate > 0 && bandwidth.EgressBurst > 0 {
rateInBytes := bandwidth.EgressRate / 8
burstInBytes := bandwidth.EgressBurst / 8
bufferInBytes := buffer(uint64(rateInBytes), uint32(burstInBytes))
latency := latencyInUsec(latencyInMillis)
limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes))
ifbDeviceName, err := getIfbDeviceName(bwConf.Name, args.ContainerID)
if err != nil {
return err
}
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
if err != nil {
return fmt.Errorf("get ifb device: %s", err)
}
qdiscs, err := SafeQdiscList(ifbDevice)
if err != nil {
return err
}
if len(qdiscs) == 0 {
return fmt.Errorf("Failed to find qdisc")
}
for _, qdisc := range qdiscs {
tbf, isTbf := qdisc.(*netlink.Tbf)
if !isTbf {
break
}
if tbf.Rate != uint64(rateInBytes) {
return fmt.Errorf("Rate doesn't match")
}
if tbf.Limit != uint32(limitInBytes) {
return fmt.Errorf("Limit doesn't match")
}
if tbf.Buffer != uint32(bufferInBytes) {
return fmt.Errorf("Buffer doesn't match")
}
}
}
return nil
}

View File

@ -138,3 +138,44 @@ func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, err
}
return false, nil
}
// check the chain.
func (c *chain) check(ipt *iptables.IPTables) error {
exists, err := chainExists(ipt, c.table, c.name)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("chain %s not found in iptables table %s", c.name, c.table)
}
for i := len(c.rules) - 1; i >= 0; i-- {
match := checkRule(ipt, c.table, c.name, c.rules[i])
if !match {
return fmt.Errorf("rule %s in chain %s not found in table %s", c.rules, c.name, c.table)
}
}
for _, entryChain := range c.entryChains {
for i := len(c.entryRules) - 1; i >= 0; i-- {
r := []string{}
r = append(r, c.entryRules[i]...)
r = append(r, "-j", c.name)
matchEntryChain := checkRule(ipt, c.table, entryChain, r)
if !matchEntryChain {
return fmt.Errorf("rule %s in chain %s not found in table %s", c.entryRules, entryChain, c.table)
}
}
}
return nil
}
func checkRule(ipt *iptables.IPTables, table, chain string, rule []string) bool {
exists, err := ipt.Exists(table, chain, rule...)
if err != nil {
return false
}
return exists
}

View File

@ -55,8 +55,6 @@ type PortMapConf struct {
RuntimeConfig struct {
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
} `json:"runtimeConfig,omitempty"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult *current.Result `json:"-"`
// These are fields parsed out of the config or the environment;
// included here for convenience
@ -70,7 +68,7 @@ type PortMapConf struct {
const DefaultMarkBit = 13
func cmdAdd(args *skel.CmdArgs) error {
netConf, err := parseConfig(args.StdinData, args.IfName)
netConf, _, err := parseConfig(args.StdinData, args.IfName)
if err != nil {
return fmt.Errorf("failed to parse config: %v", err)
}
@ -102,7 +100,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
func cmdDel(args *skel.CmdArgs) error {
netConf, err := parseConfig(args.StdinData, args.IfName)
netConf, _, err := parseConfig(args.StdinData, args.IfName)
if err != nil {
return fmt.Errorf("failed to parse config: %v", err)
}
@ -119,36 +117,60 @@ func cmdDel(args *skel.CmdArgs) error {
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "TODO")
}
func cmdGet(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
func cmdCheck(args *skel.CmdArgs) error {
conf, result, err := parseConfig(args.StdinData, args.IfName)
if err != nil {
return err
}
// Ensure we have previous result.
if result == nil {
return fmt.Errorf("Required prevResult missing")
}
if len(conf.RuntimeConfig.PortMaps) == 0 {
return nil
}
conf.ContainerID = args.ContainerID
if conf.ContIPv4 != nil {
if err := checkPorts(conf, conf.ContIPv4); err != nil {
return err
}
}
if conf.ContIPv6 != nil {
if err := checkPorts(conf, conf.ContIPv6); err != nil {
return err
}
}
return nil
}
// parseConfig parses the supplied configuration (and prevResult) from stdin.
func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, error) {
conf := PortMapConf{}
if err := json.Unmarshal(stdin, &conf); err != nil {
return nil, fmt.Errorf("failed to parse network configuration: %v", err)
return nil, nil, fmt.Errorf("failed to parse network configuration: %v", err)
}
// Parse previous result.
var result *current.Result
if conf.RawPrevResult != nil {
resultBytes, err := json.Marshal(conf.RawPrevResult)
if err != nil {
return nil, fmt.Errorf("could not serialize prevResult: %v", err)
var err error
if err = version.ParsePrevResult(&conf.NetConf); err != nil {
return nil, nil, fmt.Errorf("could not parse prevResult: %v", err)
}
res, err := version.NewResult(conf.CNIVersion, resultBytes)
result, err = current.NewResultFromResult(conf.PrevResult)
if err != nil {
return nil, fmt.Errorf("could not parse prevResult: %v", err)
}
conf.RawPrevResult = nil
conf.PrevResult, err = current.NewResultFromResult(res)
if err != nil {
return nil, fmt.Errorf("could not convert result to current version: %v", err)
return nil, nil, fmt.Errorf("could not convert result to current version: %v", err)
}
}
@ -158,7 +180,7 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
}
if conf.MarkMasqBit != nil && conf.ExternalSetMarkChain != nil {
return nil, fmt.Errorf("Cannot specify externalSetMarkChain and markMasqBit")
return nil, nil, fmt.Errorf("Cannot specify externalSetMarkChain and markMasqBit")
}
if conf.MarkMasqBit == nil {
@ -167,21 +189,21 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
}
if *conf.MarkMasqBit < 0 || *conf.MarkMasqBit > 31 {
return nil, fmt.Errorf("MasqMarkBit must be between 0 and 31")
return nil, nil, fmt.Errorf("MasqMarkBit must be between 0 and 31")
}
// Reject invalid port numbers
for _, pm := range conf.RuntimeConfig.PortMaps {
if pm.ContainerPort <= 0 {
return nil, fmt.Errorf("Invalid container port number: %d", pm.ContainerPort)
return nil, nil, fmt.Errorf("Invalid container port number: %d", pm.ContainerPort)
}
if pm.HostPort <= 0 {
return nil, fmt.Errorf("Invalid host port number: %d", pm.HostPort)
return nil, nil, fmt.Errorf("Invalid host port number: %d", pm.HostPort)
}
}
if conf.PrevResult != nil {
for _, ip := range conf.PrevResult.IPs {
for _, ip := range result.IPs {
if ip.Version == "6" && conf.ContIPv6 != nil {
continue
} else if ip.Version == "4" && conf.ContIPv4 != nil {
@ -192,9 +214,9 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
if ip.Interface != nil {
intIdx := *ip.Interface
if intIdx >= 0 &&
intIdx < len(conf.PrevResult.Interfaces) &&
(conf.PrevResult.Interfaces[intIdx].Name != ifName ||
conf.PrevResult.Interfaces[intIdx].Sandbox == "") {
intIdx < len(result.Interfaces) &&
(result.Interfaces[intIdx].Name != ifName ||
result.Interfaces[intIdx].Sandbox == "") {
continue
}
}
@ -207,5 +229,5 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
}
}
return &conf, nil
return &conf, result, nil
}

View File

@ -111,6 +111,46 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
return nil
}
func checkPorts(config *PortMapConf, containerIP net.IP) error {
dnatChain := genDnatChain(config.Name, config.ContainerID)
fillDnatRules(&dnatChain, config, containerIP)
ip4t := maybeGetIptables(false)
ip6t := maybeGetIptables(true)
if ip4t == nil && ip6t == nil {
return fmt.Errorf("neither iptables nor ip6tables usable")
}
if ip4t != nil {
exists, err := chainExists(ip4t, dnatChain.table, dnatChain.name)
if err != nil {
return err
}
if !exists {
return err
}
if err := dnatChain.check(ip4t); err != nil {
return fmt.Errorf("could not check ipv4 dnat: %v", err)
}
}
if ip6t != nil {
exists, err := chainExists(ip6t, dnatChain.table, dnatChain.name)
if err != nil {
return err
}
if !exists {
return err
}
if err := dnatChain.check(ip6t); err != nil {
return fmt.Errorf("could not check ipv6 dnat: %v", err)
}
}
return nil
}
// genToplevelDnatChain creates the top-level summary chain that we'll
// add our chain to. This is easy, because creating chains is idempotent.
// IMPORTANT: do not change this, or else upgrading plugins will require

View File

@ -68,7 +68,7 @@ var _ = Describe("portmapping configuration", func() {
]
}
}`)
c, err := parseConfig(configBytes, "container")
c, _, err := parseConfig(configBytes, "container")
Expect(err).NotTo(HaveOccurred())
Expect(c.CNIVersion).To(Equal("0.3.1"))
Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"}))
@ -91,7 +91,7 @@ var _ = Describe("portmapping configuration", func() {
"conditionsV4": ["a", "b"],
"conditionsV6": ["c", "d"]
}`)
c, err := parseConfig(configBytes, "container")
c, _, err := parseConfig(configBytes, "container")
Expect(err).NotTo(HaveOccurred())
Expect(c.CNIVersion).To(Equal("0.3.1"))
Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"}))
@ -115,7 +115,7 @@ var _ = Describe("portmapping configuration", func() {
]
}
}`)
_, err := parseConfig(configBytes, "container")
_, _, err := parseConfig(configBytes, "container")
Expect(err).To(MatchError("Invalid host port number: 0"))
})
@ -143,7 +143,7 @@ var _ = Describe("portmapping configuration", func() {
]
}
}`)
_, err := parseConfig(configBytes, "container")
_, _, err := parseConfig(configBytes, "container")
Expect(err).NotTo(HaveOccurred())
})
})
@ -175,7 +175,7 @@ var _ = Describe("portmapping configuration", func() {
"conditionsV6": ["c", "d"]
}`)
conf, err := parseConfig(configBytes, "foo")
conf, _, err := parseConfig(configBytes, "foo")
Expect(err).NotTo(HaveOccurred())
conf.ContainerID = containerID
@ -271,7 +271,7 @@ var _ = Describe("portmapping configuration", func() {
"conditionsV6": ["c", "d"]
}`)
conf, err := parseConfig(configBytes, "foo")
conf, _, err := parseConfig(configBytes, "foo")
Expect(err).NotTo(HaveOccurred())
conf.ContainerID = containerID

View File

@ -36,12 +36,10 @@ import (
// TuningConf represents the network tuning configuration.
type TuningConf struct {
types.NetConf
SysCtl map[string]string `json:"sysctl"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult *current.Result `json:"-"`
Mac string `json:"mac,omitempty"`
Promisc bool `json:"promisc,omitempty"`
Mtu int `json:"mtu,omitempty"`
SysCtl map[string]string `json:"sysctl"`
Mac string `json:"mac,omitempty"`
Promisc bool `json:"promisc,omitempty"`
Mtu int `json:"mtu,omitempty"`
}
type MACEnvArgs struct {
@ -68,23 +66,6 @@ func parseConf(data []byte, envArgs string) (*TuningConf, error) {
}
}
// Parse previous result.
if conf.RawPrevResult != nil {
resultBytes, err := json.Marshal(conf.RawPrevResult)
if err != nil {
return nil, fmt.Errorf("could not serialize prevResult: %v", err)
}
res, err := version.NewResult(conf.CNIVersion, resultBytes)
if err != nil {
return nil, fmt.Errorf("could not parse prevResult: %v", err)
}
conf.RawPrevResult = nil
conf.PrevResult, err = current.NewResultFromResult(res)
if err != nil {
return nil, fmt.Errorf("could not convert result to current version: %v", err)
}
}
return &conf, nil
}
@ -111,7 +92,15 @@ func changeMacAddr(ifName string, newMacAddr string) error {
}
func updateResultsMacAddr(config TuningConf, ifName string, newMacAddr string) {
for _, i := range config.PrevResult.Interfaces {
// Parse previous result.
if config.PrevResult == nil {
return
}
version.ParsePrevResult(&config.NetConf)
result, _ := current.NewResultFromResult(config.PrevResult)
for _, i := range result.Interfaces {
if i.Name == ifName {
i.Mac = newMacAddr
}
@ -144,6 +133,20 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
// Parse previous result.
if tuningConf.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(&tuningConf.NetConf); err != nil {
return err
}
_, err = current.NewResultFromResult(tuningConf.PrevResult)
if err != nil {
return err
}
// The directory /proc/sys/net is per network namespace. Enter in the
// network namespace before writing on it.
@ -200,10 +203,80 @@ func cmdDel(args *skel.CmdArgs) error {
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "TODO")
}
func cmdGet(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
func cmdCheck(args *skel.CmdArgs) error {
tuningConf, err := parseConf(args.StdinData, args.Args)
if err != nil {
return err
}
// Parse previous result.
if tuningConf.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(&tuningConf.NetConf); err != nil {
return err
}
_, err = current.NewResultFromResult(tuningConf.PrevResult)
if err != nil {
return err
}
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
// Check each configured value vs what's currently in the container
for key, conf_value := range tuningConf.SysCtl {
fileName := filepath.Join("/proc/sys", strings.Replace(key, ".", "/", -1))
fileName = filepath.Clean(fileName)
contents, err := ioutil.ReadFile(fileName)
if err != nil {
return err
}
cur_value := strings.TrimSuffix(string(contents), "\n")
if conf_value != cur_value {
return fmt.Errorf("Error: Tuning configured value of %s is %s, current value is %s", fileName, conf_value, cur_value)
}
}
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return fmt.Errorf("Cannot find container link %v", args.IfName)
}
if tuningConf.Mac != "" {
if tuningConf.Mac != link.Attrs().HardwareAddr.String() {
return fmt.Errorf("Error: Tuning configured Ethernet of %s is %s, current value is %s",
args.IfName, tuningConf.Mac, link.Attrs().HardwareAddr)
}
}
if tuningConf.Promisc {
if link.Attrs().Promisc == 0 {
return fmt.Errorf("Error: Tuning link %s configured promisc is %v, current value is %d",
args.IfName, tuningConf.Promisc, link.Attrs().Promisc)
}
} else {
if link.Attrs().Promisc != 0 {
return fmt.Errorf("Error: Tuning link %s configured promisc is %v, current value is %d",
args.IfName, tuningConf.Promisc, link.Attrs().Promisc)
}
}
if tuningConf.Mtu != 0 {
if tuningConf.Mtu != link.Attrs().MTU {
return fmt.Errorf("Error: Tuning configured MTU of %s is %d, current value is %d",
args.IfName, tuningConf.Mtu, link.Attrs().MTU)
}
}
return nil
})
if err != nil {
return err
}
return nil
}

View File

@ -15,7 +15,11 @@
package main
import (
"encoding/json"
"fmt"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
@ -27,6 +31,49 @@ import (
. "github.com/onsi/gomega"
)
func buildOneConfig(name, cniVersion string, orig *TuningConf, prevResult types.Result) (*TuningConf, []byte, error) {
var err error
inject := map[string]interface{}{
"name": name,
"cniVersion": cniVersion,
}
// Add previous plugin result
if prevResult != nil {
inject["prevResult"] = prevResult
}
// Ensure every config uses the same name and version
config := make(map[string]interface{})
confBytes, err := json.Marshal(orig)
if err != nil {
return nil, nil, err
}
err = json.Unmarshal(confBytes, &config)
if err != nil {
return nil, 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, nil, err
}
conf := &TuningConf{}
if err := json.Unmarshal(newBytes, &conf); err != nil {
return nil, nil, fmt.Errorf("error parsing configuration: %s", err)
}
return conf, newBytes, nil
}
var _ = Describe("tuning plugin", func() {
var originalNS ns.NetNS
const IFNAME string = "dummy0"
@ -342,4 +389,296 @@ var _ = Describe("tuning plugin", func() {
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures promiscuous mode with CNI 0.4.0 ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.4.0",
"promisc": true,
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(1))
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
Expect(len(result.IPs)).To(Equal(1))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Promisc).To(Equal(1))
n := &TuningConf{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
err = testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mtu with CNI 0.4.0 ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.4.0",
"mtu": 1454,
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(1))
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
Expect(len(result.IPs)).To(Equal(1))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().MTU).To(Equal(1454))
n := &TuningConf{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
err = testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mac address (from conf file) with CNI v4.0 ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.4.0",
"mac": "c2:11:22:33:44:55",
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(1))
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
Expect(len(result.IPs)).To(Equal(1))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
hw, err := net.ParseMAC("c2:11:22:33:44:55")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
n := &TuningConf{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
err = testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mac address (from CNI_ARGS) with CNI v4 ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.4.0",
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
Args: "IgnoreUnknown=true;MAC=c2:11:22:33:44:66",
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(1))
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
Expect(len(result.IPs)).To(Equal(1))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
hw, err := net.ParseMAC("c2:11:22:33:44:66")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
n := &TuningConf{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
err = testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
})