Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add multi-config and handle static ips #158

Merged
merged 1 commit into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions cni/plugins/main/multi-nic/ipvlan.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/utils"
"github.com/vishvananda/netlink"
)

Expand Down Expand Up @@ -40,6 +41,17 @@ func loadIPVANConf(bytes []byte, ifName string, n *NetConf, ipConfigs []*current
return
}

var ipamMap map[string][]byte

if n.IPAM.Type == MultiConfigIPAMType {
var err error
ipamMap, err = getMultiIPAMConfigBytes(bytes)
if err != nil {
ipamMap = nil
utils.Logger.Debug(fmt.Sprintf("getMultiIPAMConfigBytes failed: %v", err))
}
}

// interfaces are orderly assigned from interface set
for index, masterName := range n.Masters {
if masterName == "" {
Expand All @@ -62,9 +74,16 @@ func loadIPVANConf(bytes []byte, ifName string, n *NetConf, ipConfigs []*current
return
}

if n.IsMultiNICIPAM {
// multi-NIC IPAM config
if len(ipConfigs) > 0 {
// no need to call ipam due to static ip
confBytes, multiPathRoutes = injectMultiNicIPAM(confBytes, bytes, ipConfigs, index)
} else if ipamMap != nil {
if ipamBytes, found := ipamMap[masterName]; found {
confBytes, multiPathRoutes = replaceSingleNicIPAMWithMultiConfig(confBytes, bytes, ipamBytes)
} else {
utils.Logger.Debug(fmt.Sprintf("Multi-config IPAM has no definition of %s", masterName))
continue
}
} else {
confBytes, multiPathRoutes = injectSingleNicIPAM(confBytes, bytes)
}
Expand Down
24 changes: 24 additions & 0 deletions cni/plugins/main/multi-nic/multi-nic.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,30 @@ func cmdAdd(args *skel.CmdArgs) error {
if len(result.IPs) == 0 {
return fmt.Errorf("IPAM plugin returned missing IP config %v", string(injectedStdIn))
}
} else if !haveResult && n.IPAM.Type == MultiConfigIPAMType {
var ips []string
ips, args.Args = getStaticIPs(args.Args)
for index, ipnet := range ips {
ipVal, reservedIP, err := net.ParseCIDR(ipnet)
if err != nil {
utils.Logger.Debug(fmt.Sprintf("failed to parse static IP %s: %v", ipnet, err))
return err
}
reservedIP.IP = ipVal
ipConf := &current.IPConfig{
Address: *reservedIP,
Interface: current.Int(index),
}
result.IPs = append(result.IPs, ipConf)
}
if len(n.Masters) == 0 {
ipamObject, err := getMultiIPAMConfig(args.StdinData)
if err == nil && ipamObject.IPAM.Args != nil {
for masterName, _ := range ipamObject.IPAM.Args {
n.Masters = append(n.Masters, masterName)
}
}
}
}

// get device config and apply
Expand Down
107 changes: 107 additions & 0 deletions cni/plugins/main/multi-nic/multi-nic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,71 @@ var _ = Describe("Operations", func() {
multiPathTest(ver, singleNICIPAMWithMultiPath, fullNets, POOL_MASTER_NAMES)
})

It(fmt.Sprintf("[%s] check multi-config IPAM", ver), func() {
conf, n := getSampleMultiIPAMConfig(ver, POOL_MASTER_NAMES, masterNets)
confBytesArray, _, err := loadIPVANConf([]byte(conf), "net1", n, []*types100.IPConfig{})
Expect(err).NotTo(HaveOccurred())
Expect(len(confBytesArray)).To(Equal(len(POOL_MASTER_NAMES)))
for index, confBytes := range confBytesArray {
log.Printf("%s", confBytes)
confObj := &IPVLANTypeNetConf{}
err = json.Unmarshal(confBytes, confObj)
Expect(err).NotTo(HaveOccurred())
Expect(confObj.IPAM.Type).To(Equal("whereabouts"))
ipamObject := &IPAMExtract{}
err = json.Unmarshal(confBytes, ipamObject)
Expect(err).NotTo(HaveOccurred())
Expect(ipamObject.IPAM["range"].(string)).To(Equal(POOL_IP_ADDRESSES[index]))
Expect(ipamObject.IPAM["network_name"].(string)).To(Equal(POOL_MASTER_NAMES[index]))
}
})

It(fmt.Sprintf("[%s] check multi-config IPAM with static IP", ver), func() {
var ips []*types100.IPConfig
for _, podIP := range POOL_IP_ADDRESSES {
ipVal, ipnet, err := net.ParseCIDR(podIP)
ipnet.IP = ipVal
Expect(err).NotTo(HaveOccurred())
podIPConfig := &types100.IPConfig{Address: *ipnet}
ips = append(ips, podIPConfig)
}
conf, n := getSampleMultiIPAMConfig(ver, POOL_MASTER_NAMES, masterNets)
confBytesArray, _, err := loadIPVANConf([]byte(conf), "net1", n, ips)
Expect(err).NotTo(HaveOccurred())
Expect(len(confBytesArray)).To(Equal(len(POOL_MASTER_NAMES)))
for index, confBytes := range confBytesArray {
log.Printf("%s", confBytes)
confObj := &IPVLANTypeNetConf{}
err = json.Unmarshal(confBytes, confObj)
Expect(err).NotTo(HaveOccurred())
Expect(confObj.IPAM.Type).To(Equal("static"))
ipamObject := &IPAMExtract{}
err = json.Unmarshal(confBytes, ipamObject)
Expect(err).NotTo(HaveOccurred())
addresses := ipamObject.IPAM["addresses"].([]interface{})
Expect(len(addresses)).To(Equal(1))
addressMap := addresses[0].(map[string]interface{})
Expect(addressMap["address"].(string)).To(Equal(POOL_IP_ADDRESSES[index]))
}
})

It(fmt.Sprintf("[%s] check getStaticIPs", ver), func() {
var ips []string
joinedIPs := strings.Join(POOL_IP_ADDRESSES, ",")
argStr := fmt.Sprintf("POD_NAME=a;IP=%s", joinedIPs)
ips, argStr = getStaticIPs(argStr)
for index, podIP := range ips {
Expect(podIP).To(Equal(POOL_IP_ADDRESSES[index]))
}
Expect(argStr).To(Equal("POD_NAME=a"))

argStr = fmt.Sprintf("POD_NAME=a;IP=%s;SOME_ARG=b", joinedIPs)
ips, argStr = getStaticIPs(argStr)
for index, podIP := range ips {
Expect(podIP).To(Equal(POOL_IP_ADDRESSES[index]))
}
Expect(argStr).To(Equal("POD_NAME=a;SOME_ARG=b"))
})
}
})

Expand Down Expand Up @@ -583,6 +648,48 @@ func getAwsIpvlanConfig(ver, masterNets string) ([]byte, *NetConf) {
return conf, n
}

func getSampleMultiIPAMConfig(ver string, masterNames []string, masterNets string) ([]byte, *NetConf) {
ipamArgs := ""
for index, masterName := range masterNames {
if index > 0 {
ipamArgs += ","
}
ipamArgs += fmt.Sprintf(`
"%s": {"range": "%s"}
`, masterName, POOL_IP_ADDRESSES[index])
}
confStr := fmt.Sprintf(`{
"cniVersion": "%s",
"name": "multi-nic-sample",
"type": "multi-nic",
"plugin": {
"cniVersion": "0.3.0",
"type": "ipvlan",
"mode": "l2"
},
"vlanMode": "l2",
"ipam": {
"type": "multi-config",
"ipam_type": "whereabouts",
"args": {
%s
}
},
"daemonIP": "%s",
"daemonPort": %d,
"subnet": "192.168.0.0/16",
"masterNets": %s
}`, ver, ipamArgs, BRIDGE_HOST_IP, daemonPort, masterNets)
log.Printf("%s", confStr)
conf := []byte(confStr)
n := &NetConf{}
err := json.Unmarshal(conf, n)
Expect(err).NotTo(HaveOccurred())
n.DeviceIDs = POOL_MASTER_NAMES
n.Masters = POOL_MASTER_NAMES
return conf, n
}

func closeServer(srv *http.Server, httpServerExitDone *sync.WaitGroup) {
if err := srv.Shutdown(context.TODO()); err != nil {
panic(err)
Expand Down
86 changes: 82 additions & 4 deletions cni/plugins/main/multi-nic/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,31 @@ import (

"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/utils"
"github.com/vishvananda/netlink"
)

const (
MultiConfigIPAMType = "multi-config"
WhereaboutsIPAMType = "whereabouts"
)

type IPAMExtract struct {
IPAM map[string]interface{} `json:"ipam"`
}

type MultiIPAMConfig struct {
Name string
Type string `json:"type"`
IpamType string `json:"ipam_type"`
Args map[string]map[string]interface{} `json:"args"`
Routes []*types.Route `json:"routes"`
}

type MultiIPAMExtract struct {
IPAM MultiIPAMConfig `json:"ipam"`
}

// getPodInfo extracts pod Name and Namespace from cniArgs
func getPodInfo(cniArgs string) (string, string) {
splits := strings.Split(cniArgs, ";")
Expand All @@ -33,6 +55,57 @@ func getPodInfo(cniArgs string) (string, string) {
return podName, podNamespace
}

// getStaticIPs extracts static ip from cniArgs
// reference: https://github.com/k8snetworkplumbingwg/multus-cni/blob/e2e8cfb677e8cf5352f737b5004effed7518d71a/pkg/multus/multus.go#L324
func getStaticIPs(cniArgs string) ([]string, string) {
splits := strings.Split(cniArgs, ";")
for index, split := range splits {
if strings.HasPrefix(split, "IP=") {
ipsStr := strings.TrimPrefix(split, "IP=")
newItems := splits[0:index]
if index < len(split)-1 {
newItems = append(newItems, splits[index+1:len(splits)]...)
}
newCniArgs := strings.Join(newItems, ";")
return strings.Split(ipsStr, ","), newCniArgs
}
}
return []string{}, cniArgs
}

func getMultiIPAMConfig(multiNicConfBytes []byte) (*MultiIPAMExtract, error) {
ipamObject := &MultiIPAMExtract{}
err := json.Unmarshal(multiNicConfBytes, ipamObject)
return ipamObject, err

}

func getMultiIPAMConfigBytes(multiNicConfBytes []byte) (map[string][]byte, error) {
multiIPAMConfigBytes := make(map[string][]byte)
ipamObject, err := getMultiIPAMConfig(multiNicConfBytes)
if err == nil && ipamObject.IPAM.Args != nil {
for masterName, singleIPAMObject := range ipamObject.IPAM.Args {
singleIPAMConfBytes := make(map[string]interface{})
if singleIPAMObject == nil {
utils.Logger.Debug(fmt.Sprintf("ipamObject.IPAM.Args not defined on %s", masterName))
continue
}
// set type
singleIPAMConfBytes["type"] = ipamObject.IPAM.IpamType
if ipamObject.IPAM.IpamType == WhereaboutsIPAMType {
singleIPAMConfBytes["network_name"] = masterName
}
// set args
for k, v := range singleIPAMObject {
singleIPAMConfBytes[k] = v
}
ipamBytes, _ := json.Marshal(singleIPAMConfBytes)
multiIPAMConfigBytes[masterName] = ipamBytes
}
}
return multiIPAMConfigBytes, err
}

// injectIPAM injects ipam bytes to config
func injectMultiNicIPAM(singleNicConfBytes, multiNicConfBytes []byte, ipConfigs []*current.IPConfig, ipIndex int) ([]byte, map[string][]*netlink.NexthopInfo) {
var ipConfig *current.IPConfig
Expand All @@ -46,11 +119,17 @@ func injectSingleNicIPAM(singleNicConfBytes []byte, multiNicConfBytes []byte) ([
return replaceSingleNicIPAM(singleNicConfBytes, multiNicConfBytes)
}

type IPAMExtract struct {
IPAM map[string]interface{} `json:"ipam"`
func replaceSingleNicIPAM(singleNicConfBytes, multiNicConfBytes []byte) ([]byte, map[string][]*netlink.NexthopInfo) {
ipamObject := &IPAMExtract{}
err := json.Unmarshal(multiNicConfBytes, ipamObject)
if err == nil {
ipamBytes, _ := json.Marshal(ipamObject.IPAM)
return replaceSingleNicIPAMWithMultiConfig(singleNicConfBytes, multiNicConfBytes, ipamBytes)
}
return singleNicConfBytes, nil
}

func replaceSingleNicIPAM(singleNicConfBytes, multiNicConfBytes []byte) ([]byte, map[string][]*netlink.NexthopInfo) {
func replaceSingleNicIPAMWithMultiConfig(singleNicConfBytes, multiNicConfBytes, ipamBytes []byte) ([]byte, map[string][]*netlink.NexthopInfo) {
confStr := string(singleNicConfBytes)
ipamObject := &IPAMExtract{}
err := json.Unmarshal(multiNicConfBytes, ipamObject)
Expand All @@ -60,7 +139,6 @@ func replaceSingleNicIPAM(singleNicConfBytes, multiNicConfBytes []byte) ([]byte,
ipamObject.IPAM["routes"] = nonMultiPathRoutes
}

ipamBytes, _ := json.Marshal(ipamObject.IPAM)
singleIPAM := fmt.Sprintf("\"ipam\":%s", string(ipamBytes))
injectedStr := strings.ReplaceAll(confStr, "\"ipam\":{}", singleIPAM)
return []byte(injectedStr), multiPathRoutes
Expand Down
25 changes: 25 additions & 0 deletions config/samples/multinicnetwork/ipvlanl2_multiconfig.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: multinic.fms.io/v1
kind: MultiNicNetwork
metadata:
name: multinic-multiconfig
spec:
subnet: ""
ipam: |
{
"type": "multi-config",
"ipam_type": "whereabouts",
"args": {
"p0": {
"range": "192.168.0.0/18"
},
"p1": {
"range": "192.168.64.0/18"
}
}
}
multiNICIPAM: false
plugin:
cniVersion: "0.3.0"
type: ipvlan
args:
mode: l2
Loading