flannel: allow input ipam parameters as basis for delegate

This change allows providing an 'ipam' section as part of the
input network configuration for flannel. It is then used as
basis to construct the ipam parameters provided to the delegate.

All parameters from the input ipam are preserved except:
* 'subnet' which is set to the flannel host subnet
* 'routes' which is complemented by a route to the flannel
  network.

One use case of this feature is to allow adding back the routes
to the cluster services and/or to the hosts (HostPort) when
using isDefaultGateway=false. In that case, the bridge plugin
does not install a default route and, as a result, only pod-to-pod
connectivity would be available.

Example:
    {
      "name": "cbr0",
      "cniVersion": "0.3.1",
      "type": "flannel",
      "ipam": {
        "routes": [
          {
            "dst": "192.168.242.0/24"
          },
          {
            "dst": "10.96.0.0/12"
          }
        ],
        "unknown-param": "value"
      },
      "delegate": {
        "hairpinMode": true,
        "isDefaultGateway": false
      }
      ...
    }

This results in the following 'ipam' being provided to the delegate:
    {
      "routes" : [
        {
          "dst": "192.168.242.0/24"
        },
        {
          "dst": "10.96.0.0/12"
        },
        {
          "dst" : "10.1.0.0/16"
        }
      ],
      "subnet" : "10.1.17.0/24",
      "type" : "host-local"
      "unknown-param": "value"
    }

where "10.1.0.0/16" is the flannel network and "10.1.17.0/24" is
the host flannel subnet.

Note that this also allows setting a different ipam 'type' than
"host-local".

Signed-off-by: David Verbeiren <david.verbeiren@tessares.net>
This commit is contained in:
David Verbeiren
2020-09-04 11:11:48 +02:00
parent e78e6aa5b9
commit 9ce99d3f07
4 changed files with 134 additions and 13 deletions

View File

@ -14,6 +14,7 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
@ -50,9 +51,25 @@ var _ = Describe("Flannel", func() {
"name": "cni-flannel",
"type": "flannel",
"subnetFile": "%s",
"dataDir": "%s"
"dataDir": "%s"%s
}`
const inputIPAMTemplate = `
"unknown-param": "unknown-value",
"routes": [%s]%s`
const inputIPAMType = "my-ipam"
const inputIPAMNoTypeTemplate = `
{
"unknown-param": "unknown-value",
"routes": [%s]%s
}`
const inputIPAMRoutes = `
{ "dst": "10.96.0.0/12" },
{ "dst": "192.168.244.0/24", "gw": "10.1.17.20" }`
const flannelSubnetEnv = `
FLANNEL_NETWORK=10.1.0.0/16
FLANNEL_SUBNET=10.1.17.1/24
@ -68,6 +85,26 @@ FLANNEL_IPMASQ=true
return file.Name()
}
var makeInputIPAM = func(ipamType, routes, extra string) string {
c := "{\n"
if len(ipamType) > 0 {
c += fmt.Sprintf(" \"type\": \"%s\",", ipamType)
}
c += fmt.Sprintf(inputIPAMTemplate, routes, extra)
c += "\n}"
return c
}
var makeInput = func(inputIPAM string) string {
ipamPart := ""
if len(inputIPAM) > 0 {
ipamPart = ",\n \"ipam\":\n" + inputIPAM
}
return fmt.Sprintf(inputTemplate, subnetFile, dataDir, ipamPart)
}
BeforeEach(func() {
var err error
// flannel subnet.env
@ -76,7 +113,7 @@ FLANNEL_IPMASQ=true
// flannel state dir
dataDir, err = ioutil.TempDir("", "dataDir")
Expect(err).NotTo(HaveOccurred())
input = fmt.Sprintf(inputTemplate, subnetFile, dataDir)
input = makeInput("")
})
AfterEach(func() {
@ -108,7 +145,7 @@ FLANNEL_IPMASQ=true
})
Expect(err).NotTo(HaveOccurred())
By("check that plugin writes to net config to dataDir")
By("check that plugin writes the net config to dataDir")
path := fmt.Sprintf("%s/%s", dataDir, "some-container-id")
Expect(path).Should(BeAnExistingFile())
@ -213,4 +250,49 @@ FLANNEL_IPMASQ=true
})
})
})
Describe("getDelegateIPAM", func() {
Context("when input IPAM is provided", func() {
BeforeEach(func() {
inputIPAM := makeInputIPAM(inputIPAMType, inputIPAMRoutes, "")
input = makeInput(inputIPAM)
})
It("configures Delegate IPAM accordingly", func() {
conf, err := loadFlannelNetConf([]byte(input))
Expect(err).ShouldNot(HaveOccurred())
fenv, err := loadFlannelSubnetEnv(subnetFile)
Expect(err).ShouldNot(HaveOccurred())
ipam, err := getDelegateIPAM(conf, fenv)
Expect(err).ShouldNot(HaveOccurred())
podsRoute := "{ \"dst\": \"10.1.0.0/16\" }\n"
subnet := "\"subnet\": \"10.1.17.0/24\""
expected := makeInputIPAM(inputIPAMType, inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
buf, _ := json.Marshal(ipam)
Expect(buf).Should(MatchJSON(expected))
})
})
Context("when input IPAM is provided without 'type'", func() {
BeforeEach(func() {
inputIPAM := makeInputIPAM("", inputIPAMRoutes, "")
input = makeInput(inputIPAM)
})
It("configures Delegate IPAM with 'host-local' ipam", func() {
conf, err := loadFlannelNetConf([]byte(input))
Expect(err).ShouldNot(HaveOccurred())
fenv, err := loadFlannelSubnetEnv(subnetFile)
Expect(err).ShouldNot(HaveOccurred())
ipam, err := getDelegateIPAM(conf, fenv)
Expect(err).ShouldNot(HaveOccurred())
podsRoute := "{ \"dst\": \"10.1.0.0/16\" }\n"
subnet := "\"subnet\": \"10.1.17.0/24\""
expected := makeInputIPAM("host-local", inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
buf, _ := json.Marshal(ipam)
Expect(buf).Should(MatchJSON(expected))
})
})
})
})