Skip to content

Commit 6e6260a

Browse files
sharmasushantYongli Chen
authored andcommitted
Implement CNI update for Azure CNI (#265)
* Implement CNI Update for Azure CNI (#21)
1 parent 5f123a0 commit 6e6260a

File tree

12 files changed

+619
-89
lines changed

12 files changed

+619
-89
lines changed

cni/cni.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import (
99

1010
const (
1111
// CNI commands.
12-
Cmd = "CNI_COMMAND"
13-
CmdAdd = "ADD"
14-
CmdGet = "GET"
15-
CmdDel = "DEL"
12+
Cmd = "CNI_COMMAND"
13+
CmdAdd = "ADD"
14+
CmdGet = "GET"
15+
CmdDel = "DEL"
16+
CmdUpdate = "UPDATE"
1617

1718
// CNI errors.
1819
ErrRuntime = 100
@@ -29,4 +30,5 @@ type PluginApi interface {
2930
Add(args *cniSkel.CmdArgs) error
3031
Get(args *cniSkel.CmdArgs) error
3132
Delete(args *cniSkel.CmdArgs) error
33+
Update(args *cniSkel.CmdArgs) error
3234
}

cni/ipam/ipam.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,3 +290,8 @@ func (plugin *ipamPlugin) Delete(args *cniSkel.CmdArgs) error {
290290

291291
return nil
292292
}
293+
294+
// Update handles CNI update command.
295+
func (plugin *ipamPlugin) Update(args *cniSkel.CmdArgs) error {
296+
return nil
297+
}

cni/netconfig.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type NetworkConfig struct {
4848
MultiTenancy bool `json:"multiTenancy,omitempty"`
4949
EnableSnatOnHost bool `json:"enableSnatOnHost,omitempty"`
5050
EnableExactMatchForPodName bool `json:"enableExactMatchForPodName,omitempty"`
51+
CNSUrl string `json:"cnsurl,omitempty"`
5152
Ipam struct {
5253
Type string `json:"type"`
5354
Environment string `json:"environment,omitempty"`

cni/network/mutlitenancy.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ func checkIfSubnetOverlaps(enableInfraVnet bool, nwCfg *cni.NetworkConfig, cnsNe
205205
return false
206206
}
207207

208+
// GetMultiTenancyCNIResult retrieves network goal state of a container from CNS
208209
func GetMultiTenancyCNIResult(
209210
enableInfraVnet bool,
210211
nwCfg *cni.NetworkConfig,
@@ -214,7 +215,7 @@ func GetMultiTenancyCNIResult(
214215
ifName string) (*cniTypesCurr.Result, *cns.GetNetworkContainerResponse, net.IPNet, *cniTypesCurr.Result, error) {
215216

216217
if nwCfg.MultiTenancy {
217-
result, cnsNetworkConfig, subnetPrefix, err := getContainerNetworkConfiguration(nwCfg, "", k8sPodName, k8sNamespace, ifName)
218+
result, cnsNetworkConfig, subnetPrefix, err := getContainerNetworkConfiguration(nwCfg, nwCfg.CNSUrl, k8sPodName, k8sNamespace, ifName)
218219
if err != nil {
219220
log.Printf("GetContainerNetworkConfiguration failed for podname %v namespace %v with error %v", k8sPodName, k8sNamespace, err)
220221
return nil, nil, net.IPNet{}, nil, err

cni/network/network.go

Lines changed: 172 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
package network
55

66
import (
7+
"encoding/json"
78
"fmt"
89
"net"
910

1011
"github.com/Azure/azure-container-networking/cni"
1112
"github.com/Azure/azure-container-networking/cns"
13+
"github.com/Azure/azure-container-networking/cns/cnsclient"
1214
"github.com/Azure/azure-container-networking/common"
1315
"github.com/Azure/azure-container-networking/log"
1416
"github.com/Azure/azure-container-networking/network"
@@ -397,17 +399,20 @@ func (plugin *netPlugin) Add(args *cniSkel.CmdArgs) error {
397399
}
398400

399401
epInfo = &network.EndpointInfo{
400-
Id: endpointId,
401-
ContainerID: args.ContainerID,
402-
NetNsPath: args.Netns,
403-
IfName: args.IfName,
404-
EnableSnatOnHost: nwCfg.EnableSnatOnHost,
405-
EnableInfraVnet: enableInfraVnet,
406-
Data: make(map[string]interface{}),
407-
DNS: epDNSInfo,
408-
Policies: policies,
409-
}
410-
402+
Id: endpointId,
403+
ContainerID: args.ContainerID,
404+
NetNsPath: args.Netns,
405+
IfName: args.IfName,
406+
Data: make(map[string]interface{}),
407+
DNS: epDNSInfo,
408+
Policies: policies,
409+
EnableSnatOnHost: nwCfg.EnableSnatOnHost,
410+
EnableMultiTenancy: nwCfg.MultiTenancy,
411+
EnableInfraVnet: enableInfraVnet,
412+
PODName: k8sPodName,
413+
PODNameSpace: k8sNamespace,
414+
}
415+
411416
epPolicies := getPoliciesFromRuntimeCfg(nwCfg)
412417
for _, epPolicy := range epPolicies {
413418
epInfo.Policies = append(epInfo.Policies, epPolicy)
@@ -599,3 +604,159 @@ func (plugin *netPlugin) Delete(args *cniSkel.CmdArgs) error {
599604

600605
return nil
601606
}
607+
608+
// Update handles CNI update commands.
609+
// Update is only supported for multitenancy and to update routes.
610+
func (plugin *netPlugin) Update(args *cniSkel.CmdArgs) error {
611+
var (
612+
result *cniTypesCurr.Result
613+
err error
614+
nwCfg *cni.NetworkConfig
615+
existingEpInfo *network.EndpointInfo
616+
)
617+
618+
log.Printf("[cni-net] Processing UPDATE command with args {Netns:%v Args:%v Path:%v}.",
619+
args.Netns, args.Args, args.Path)
620+
621+
// Parse network configuration from stdin.
622+
nwCfg, err = cni.ParseNetworkConfig(args.StdinData)
623+
if err != nil {
624+
err = plugin.Errorf("Failed to parse network configuration: %v.", err)
625+
return err
626+
}
627+
628+
log.Printf("[cni-net] Read network configuration %+v.", nwCfg)
629+
630+
defer func() {
631+
if result == nil {
632+
result = &cniTypesCurr.Result{}
633+
}
634+
635+
// Convert result to the requested CNI version.
636+
res, vererr := result.GetAsVersion(nwCfg.CNIVersion)
637+
if vererr != nil {
638+
log.Printf("GetAsVersion failed with error %v", vererr)
639+
plugin.Error(vererr)
640+
}
641+
642+
if err == nil && res != nil {
643+
// Output the result to stdout.
644+
res.Print()
645+
}
646+
647+
log.Printf("[cni-net] UPDATE command completed with result:%+v err:%v.", result, err)
648+
}()
649+
650+
// Parse Pod arguments.
651+
podCfg, err := cni.ParseCniArgs(args.Args)
652+
if err != nil {
653+
log.Printf("Error while parsing CNI Args during UPDATE %v", err)
654+
return err
655+
}
656+
657+
k8sNamespace := string(podCfg.K8S_POD_NAMESPACE)
658+
if len(k8sNamespace) == 0 {
659+
errMsg := "Required parameter Pod Namespace not specified in CNI Args during UPDATE"
660+
log.Printf(errMsg)
661+
return plugin.Errorf(errMsg)
662+
}
663+
664+
k8sPodName := string(podCfg.K8S_POD_NAME)
665+
if len(k8sPodName) == 0 {
666+
errMsg := "Required parameter Pod Name not specified in CNI Args during UPDATE"
667+
log.Printf(errMsg)
668+
return plugin.Errorf(errMsg)
669+
}
670+
671+
// Initialize values from network config.
672+
networkID := nwCfg.Name
673+
674+
// Query the network.
675+
_, err = plugin.nm.GetNetworkInfo(networkID)
676+
if err != nil {
677+
errMsg := fmt.Sprintf("Failed to query network during CNI UPDATE: %v", err)
678+
log.Printf(errMsg)
679+
return plugin.Errorf(errMsg)
680+
}
681+
682+
// Query the existing endpoint since this is an update.
683+
// Right now, we do not support updating pods that have multiple endpoints.
684+
existingEpInfo, err = plugin.nm.GetEndpointInfoBasedOnPODDetails(networkID, k8sPodName, k8sNamespace)
685+
if err != nil {
686+
plugin.Errorf("Failed to retrieve target endpoint for CNI UPDATE [name=%v, namespace=%v]: %v", k8sPodName, k8sNamespace, err)
687+
return err
688+
} else {
689+
log.Printf("Retrieved existing endpoint from state that may get update: %+v", existingEpInfo)
690+
}
691+
692+
// now query CNS to get the target routes that should be there in the networknamespace (as a result of update)
693+
log.Printf("Going to collect target routes for [name=%v, namespace=%v] from CNS.", k8sPodName, k8sNamespace)
694+
cnsClient, err := cnsclient.NewCnsClient(nwCfg.CNSUrl)
695+
if err != nil {
696+
log.Printf("Initializing CNS client error in CNI Update%v", err)
697+
log.Printf(err.Error())
698+
return plugin.Errorf(err.Error())
699+
}
700+
701+
// create struct with info for target POD
702+
podInfo := cns.KubernetesPodInfo{PodName: k8sPodName, PodNamespace: k8sNamespace}
703+
orchestratorContext, err := json.Marshal(podInfo)
704+
if err != nil {
705+
log.Printf("Marshalling KubernetesPodInfo failed with %v", err)
706+
return plugin.Errorf(err.Error())
707+
}
708+
709+
targetNetworkConfig, err := cnsClient.GetNetworkConfiguration(orchestratorContext)
710+
if err != nil {
711+
log.Printf("GetNetworkConfiguration failed with %v", err)
712+
return plugin.Errorf(err.Error())
713+
}
714+
715+
log.Printf("Network config received from cns for [name=%v, namespace=%v] is as follows -> %+v", k8sPodName, k8sNamespace, targetNetworkConfig)
716+
targetEpInfo := &network.EndpointInfo{}
717+
718+
// get the target routes that should replace existingEpInfo.Routes inside the network namespace
719+
log.Printf("Going to collect target routes for [name=%v, namespace=%v] from targetNetworkConfig.", k8sPodName, k8sNamespace)
720+
if targetNetworkConfig.Routes != nil && len(targetNetworkConfig.Routes) > 0 {
721+
for _, route := range targetNetworkConfig.Routes {
722+
log.Printf("Adding route from routes to targetEpInfo %+v", route)
723+
_, dstIPNet, _ := net.ParseCIDR(route.IPAddress)
724+
gwIP := net.ParseIP(route.GatewayIPAddress)
725+
targetEpInfo.Routes = append(targetEpInfo.Routes, network.RouteInfo{Dst: *dstIPNet, Gw: gwIP, DevName: existingEpInfo.IfName})
726+
log.Printf("Successfully added route from routes to targetEpInfo %+v", route)
727+
}
728+
}
729+
730+
log.Printf("Going to collect target routes based on Cnetaddressspace for [name=%v, namespace=%v] from targetNetworkConfig.", k8sPodName, k8sNamespace)
731+
ipconfig := targetNetworkConfig.IPConfiguration
732+
for _, ipRouteSubnet := range targetNetworkConfig.CnetAddressSpace {
733+
log.Printf("Adding route from cnetAddressspace to targetEpInfo %+v", ipRouteSubnet)
734+
dstIPNet := net.IPNet{IP: net.ParseIP(ipRouteSubnet.IPAddress), Mask: net.CIDRMask(int(ipRouteSubnet.PrefixLength), 32)}
735+
gwIP := net.ParseIP(ipconfig.GatewayIPAddress)
736+
route := network.RouteInfo{Dst: dstIPNet, Gw: gwIP, DevName: existingEpInfo.IfName}
737+
targetEpInfo.Routes = append(targetEpInfo.Routes, route)
738+
log.Printf("Successfully added route from cnetAddressspace to targetEpInfo %+v", ipRouteSubnet)
739+
}
740+
741+
log.Printf("Finished collecting new routes in targetEpInfo as follows: %+v", targetEpInfo.Routes)
742+
log.Printf("Now saving existing infravnetaddress space if needed.")
743+
for _, ns := range nwCfg.PodNamespaceForDualNetwork {
744+
if k8sNamespace == ns {
745+
targetEpInfo.EnableInfraVnet = true
746+
targetEpInfo.InfraVnetAddressSpace = nwCfg.InfraVnetAddressSpace
747+
log.Printf("Saving infravnet address space %s for [%s-%s]",
748+
targetEpInfo.InfraVnetAddressSpace, existingEpInfo.PODNameSpace, existingEpInfo.PODName)
749+
break
750+
}
751+
}
752+
753+
// Update the endpoint.
754+
log.Printf("Now updating existing endpoint %v with targetNetworkConfig %+v.", existingEpInfo.Id, targetNetworkConfig)
755+
err = plugin.nm.UpdateEndpoint(networkID, existingEpInfo, targetEpInfo)
756+
if err != nil {
757+
err = plugin.Errorf("Failed to update endpoint: %v", err)
758+
return err
759+
}
760+
761+
return nil
762+
}

cni/network/plugin/main.go

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@
44
package main
55

66
import (
7+
"encoding/json"
8+
"fmt"
9+
"io/ioutil"
710
"os"
811
"reflect"
912

1013
"github.com/Azure/azure-container-networking/cni"
1114
"github.com/Azure/azure-container-networking/cni/network"
1215
"github.com/Azure/azure-container-networking/common"
16+
acn "github.com/Azure/azure-container-networking/common"
1317
"github.com/Azure/azure-container-networking/log"
1418
"github.com/Azure/azure-container-networking/telemetry"
19+
"github.com/containernetworking/cni/pkg/skel"
1520
)
1621

1722
const (
@@ -23,6 +28,22 @@ const (
2328
// Version is populated by make during build.
2429
var version string
2530

31+
// Command line arguments for CNI plugin.
32+
var args = acn.ArgumentList{
33+
{
34+
Name: acn.OptVersion,
35+
Shorthand: acn.OptVersionAlias,
36+
Description: "Print version information",
37+
Type: "bool",
38+
DefaultValue: false,
39+
},
40+
}
41+
42+
// Prints version information.
43+
func printVersion() {
44+
fmt.Printf("Azure CNI Version %v\n", version)
45+
}
46+
2647
// If report write succeeded, mark the report flag state to false.
2748
func markSendReport(reportManager *telemetry.ReportManager) {
2849
if err := reportManager.SetReportState(telemetry.CNITelemetryFile); err != nil {
@@ -48,8 +69,82 @@ func reportPluginError(reportManager *telemetry.ReportManager, err error) {
4869
}
4970
}
5071

72+
func validateConfig(jsonBytes []byte) error {
73+
var conf struct {
74+
Name string `json:"name"`
75+
}
76+
if err := json.Unmarshal(jsonBytes, &conf); err != nil {
77+
return fmt.Errorf("error reading network config: %s", err)
78+
}
79+
if conf.Name == "" {
80+
return fmt.Errorf("missing network name")
81+
}
82+
return nil
83+
}
84+
85+
func getCmdArgsFromEnv() (string, *skel.CmdArgs, error) {
86+
log.Printf("Going to read from stdin")
87+
stdinData, err := ioutil.ReadAll(os.Stdin)
88+
if err != nil {
89+
return "", nil, fmt.Errorf("error reading from stdin: %v", err)
90+
}
91+
92+
cmdArgs := &skel.CmdArgs{
93+
ContainerID: os.Getenv("CNI_CONTAINERID"),
94+
Netns: os.Getenv("CNI_NETNS"),
95+
IfName: os.Getenv("CNI_IFNAME"),
96+
Args: os.Getenv("CNI_ARGS"),
97+
Path: os.Getenv("CNI_PATH"),
98+
StdinData: stdinData,
99+
}
100+
101+
cmd := os.Getenv("CNI_COMMAND")
102+
return cmd, cmdArgs, nil
103+
}
104+
105+
func handleIfCniUpdate(update func(*skel.CmdArgs) error) (bool, error) {
106+
isupdate := true
107+
108+
if os.Getenv("CNI_COMMAND") != cni.CmdUpdate {
109+
return false, nil
110+
}
111+
112+
log.Printf("CNI UPDATE received.")
113+
114+
_, cmdArgs, err := getCmdArgsFromEnv()
115+
if err != nil {
116+
log.Printf("Received error while retrieving cmds from environment: %+v", err)
117+
return isupdate, err
118+
}
119+
120+
log.Printf("Retrieved command args for update +%v", cmdArgs)
121+
err = validateConfig(cmdArgs.StdinData)
122+
if err != nil {
123+
log.Printf("Failed to handle CNI UPDATE, err:%v.", err)
124+
return isupdate, err
125+
}
126+
127+
err = update(cmdArgs)
128+
if err != nil {
129+
log.Printf("Failed to handle CNI UPDATE, err:%v.", err)
130+
return isupdate, err
131+
}
132+
133+
return isupdate, nil
134+
}
135+
51136
// Main is the entry point for CNI network plugin.
52137
func main() {
138+
139+
// Initialize and parse command line arguments.
140+
acn.ParseArgs(&args, printVersion)
141+
vers := acn.GetArg(acn.OptVersion).(bool)
142+
143+
if vers {
144+
printVersion()
145+
os.Exit(0)
146+
}
147+
53148
var (
54149
config common.PluginConfig
55150
err error
@@ -109,7 +204,10 @@ func main() {
109204
panic("network plugin fatal error")
110205
}
111206

112-
if err = netPlugin.Execute(cni.PluginApi(netPlugin)); err != nil {
207+
handled, err := handleIfCniUpdate(netPlugin.Update)
208+
if handled == true {
209+
log.Printf("CNI UPDATE finished.")
210+
} else if err = netPlugin.Execute(cni.PluginApi(netPlugin)); err != nil {
113211
log.Printf("Failed to execute network plugin, err:%v.\n", err)
114212
reportPluginError(reportManager, err)
115213
}

0 commit comments

Comments
 (0)