diff --git a/cni/cni.go b/cni/cni.go index d7b98cb0e7..239eb6301d 100644 --- a/cni/cni.go +++ b/cni/cni.go @@ -9,10 +9,11 @@ import ( const ( // CNI commands. - Cmd = "CNI_COMMAND" - CmdAdd = "ADD" - CmdGet = "GET" - CmdDel = "DEL" + Cmd = "CNI_COMMAND" + CmdAdd = "ADD" + CmdGet = "GET" + CmdDel = "DEL" + CmdUpdate = "UPDATE" // CNI errors. ErrRuntime = 100 @@ -29,4 +30,5 @@ type PluginApi interface { Add(args *cniSkel.CmdArgs) error Get(args *cniSkel.CmdArgs) error Delete(args *cniSkel.CmdArgs) error + Update(args *cniSkel.CmdArgs) error } diff --git a/cni/ipam/ipam.go b/cni/ipam/ipam.go index a9eef8419a..c7171e1441 100644 --- a/cni/ipam/ipam.go +++ b/cni/ipam/ipam.go @@ -290,3 +290,8 @@ func (plugin *ipamPlugin) Delete(args *cniSkel.CmdArgs) error { return nil } + +// Update handles CNI update command. +func (plugin *ipamPlugin) Update(args *cniSkel.CmdArgs) error { + return nil +} diff --git a/cni/netconfig.go b/cni/netconfig.go index 4b3137371c..5fe49ecd74 100644 --- a/cni/netconfig.go +++ b/cni/netconfig.go @@ -48,6 +48,7 @@ type NetworkConfig struct { MultiTenancy bool `json:"multiTenancy,omitempty"` EnableSnatOnHost bool `json:"enableSnatOnHost,omitempty"` EnableExactMatchForPodName bool `json:"enableExactMatchForPodName,omitempty"` + CNSUrl string `json:"cnsurl,omitempty"` Ipam struct { Type string `json:"type"` Environment string `json:"environment,omitempty"` diff --git a/cni/network/mutlitenancy.go b/cni/network/mutlitenancy.go index 123a20581f..0e6781a6d7 100644 --- a/cni/network/mutlitenancy.go +++ b/cni/network/mutlitenancy.go @@ -205,6 +205,7 @@ func checkIfSubnetOverlaps(enableInfraVnet bool, nwCfg *cni.NetworkConfig, cnsNe return false } +// GetMultiTenancyCNIResult retrieves network goal state of a container from CNS func GetMultiTenancyCNIResult( enableInfraVnet bool, nwCfg *cni.NetworkConfig, @@ -214,7 +215,7 @@ func GetMultiTenancyCNIResult( ifName string) (*cniTypesCurr.Result, *cns.GetNetworkContainerResponse, net.IPNet, *cniTypesCurr.Result, error) { if nwCfg.MultiTenancy { - result, cnsNetworkConfig, subnetPrefix, err := getContainerNetworkConfiguration(nwCfg, "", k8sPodName, k8sNamespace, ifName) + result, cnsNetworkConfig, subnetPrefix, err := getContainerNetworkConfiguration(nwCfg, nwCfg.CNSUrl, k8sPodName, k8sNamespace, ifName) if err != nil { log.Printf("GetContainerNetworkConfiguration failed for podname %v namespace %v with error %v", k8sPodName, k8sNamespace, err) return nil, nil, net.IPNet{}, nil, err diff --git a/cni/network/network.go b/cni/network/network.go index 2e9b4868f9..3cb056b03a 100644 --- a/cni/network/network.go +++ b/cni/network/network.go @@ -4,11 +4,13 @@ package network import ( + "encoding/json" "fmt" "net" "github.com/Azure/azure-container-networking/cni" "github.com/Azure/azure-container-networking/cns" + "github.com/Azure/azure-container-networking/cns/cnsclient" "github.com/Azure/azure-container-networking/common" "github.com/Azure/azure-container-networking/log" "github.com/Azure/azure-container-networking/network" @@ -397,17 +399,20 @@ func (plugin *netPlugin) Add(args *cniSkel.CmdArgs) error { } epInfo = &network.EndpointInfo{ - Id: endpointId, - ContainerID: args.ContainerID, - NetNsPath: args.Netns, - IfName: args.IfName, - EnableSnatOnHost: nwCfg.EnableSnatOnHost, - EnableInfraVnet: enableInfraVnet, - Data: make(map[string]interface{}), - DNS: epDNSInfo, - Policies: policies, - } - + Id: endpointId, + ContainerID: args.ContainerID, + NetNsPath: args.Netns, + IfName: args.IfName, + Data: make(map[string]interface{}), + DNS: epDNSInfo, + Policies: policies, + EnableSnatOnHost: nwCfg.EnableSnatOnHost, + EnableMultiTenancy: nwCfg.MultiTenancy, + EnableInfraVnet: enableInfraVnet, + PODName: k8sPodName, + PODNameSpace: k8sNamespace, + } + epPolicies := getPoliciesFromRuntimeCfg(nwCfg) for _, epPolicy := range epPolicies { epInfo.Policies = append(epInfo.Policies, epPolicy) @@ -599,3 +604,159 @@ func (plugin *netPlugin) Delete(args *cniSkel.CmdArgs) error { return nil } + +// Update handles CNI update commands. +// Update is only supported for multitenancy and to update routes. +func (plugin *netPlugin) Update(args *cniSkel.CmdArgs) error { + var ( + result *cniTypesCurr.Result + err error + nwCfg *cni.NetworkConfig + existingEpInfo *network.EndpointInfo + ) + + log.Printf("[cni-net] Processing UPDATE command with args {Netns:%v Args:%v Path:%v}.", + args.Netns, args.Args, args.Path) + + // Parse network configuration from stdin. + nwCfg, err = cni.ParseNetworkConfig(args.StdinData) + if err != nil { + err = plugin.Errorf("Failed to parse network configuration: %v.", err) + return err + } + + log.Printf("[cni-net] Read network configuration %+v.", nwCfg) + + defer func() { + if result == nil { + result = &cniTypesCurr.Result{} + } + + // Convert result to the requested CNI version. + res, vererr := result.GetAsVersion(nwCfg.CNIVersion) + if vererr != nil { + log.Printf("GetAsVersion failed with error %v", vererr) + plugin.Error(vererr) + } + + if err == nil && res != nil { + // Output the result to stdout. + res.Print() + } + + log.Printf("[cni-net] UPDATE command completed with result:%+v err:%v.", result, err) + }() + + // Parse Pod arguments. + podCfg, err := cni.ParseCniArgs(args.Args) + if err != nil { + log.Printf("Error while parsing CNI Args during UPDATE %v", err) + return err + } + + k8sNamespace := string(podCfg.K8S_POD_NAMESPACE) + if len(k8sNamespace) == 0 { + errMsg := "Required parameter Pod Namespace not specified in CNI Args during UPDATE" + log.Printf(errMsg) + return plugin.Errorf(errMsg) + } + + k8sPodName := string(podCfg.K8S_POD_NAME) + if len(k8sPodName) == 0 { + errMsg := "Required parameter Pod Name not specified in CNI Args during UPDATE" + log.Printf(errMsg) + return plugin.Errorf(errMsg) + } + + // Initialize values from network config. + networkID := nwCfg.Name + + // Query the network. + _, err = plugin.nm.GetNetworkInfo(networkID) + if err != nil { + errMsg := fmt.Sprintf("Failed to query network during CNI UPDATE: %v", err) + log.Printf(errMsg) + return plugin.Errorf(errMsg) + } + + // Query the existing endpoint since this is an update. + // Right now, we do not support updating pods that have multiple endpoints. + existingEpInfo, err = plugin.nm.GetEndpointInfoBasedOnPODDetails(networkID, k8sPodName, k8sNamespace) + if err != nil { + plugin.Errorf("Failed to retrieve target endpoint for CNI UPDATE [name=%v, namespace=%v]: %v", k8sPodName, k8sNamespace, err) + return err + } else { + log.Printf("Retrieved existing endpoint from state that may get update: %+v", existingEpInfo) + } + + // now query CNS to get the target routes that should be there in the networknamespace (as a result of update) + log.Printf("Going to collect target routes for [name=%v, namespace=%v] from CNS.", k8sPodName, k8sNamespace) + cnsClient, err := cnsclient.NewCnsClient(nwCfg.CNSUrl) + if err != nil { + log.Printf("Initializing CNS client error in CNI Update%v", err) + log.Printf(err.Error()) + return plugin.Errorf(err.Error()) + } + + // create struct with info for target POD + podInfo := cns.KubernetesPodInfo{PodName: k8sPodName, PodNamespace: k8sNamespace} + orchestratorContext, err := json.Marshal(podInfo) + if err != nil { + log.Printf("Marshalling KubernetesPodInfo failed with %v", err) + return plugin.Errorf(err.Error()) + } + + targetNetworkConfig, err := cnsClient.GetNetworkConfiguration(orchestratorContext) + if err != nil { + log.Printf("GetNetworkConfiguration failed with %v", err) + return plugin.Errorf(err.Error()) + } + + log.Printf("Network config received from cns for [name=%v, namespace=%v] is as follows -> %+v", k8sPodName, k8sNamespace, targetNetworkConfig) + targetEpInfo := &network.EndpointInfo{} + + // get the target routes that should replace existingEpInfo.Routes inside the network namespace + log.Printf("Going to collect target routes for [name=%v, namespace=%v] from targetNetworkConfig.", k8sPodName, k8sNamespace) + if targetNetworkConfig.Routes != nil && len(targetNetworkConfig.Routes) > 0 { + for _, route := range targetNetworkConfig.Routes { + log.Printf("Adding route from routes to targetEpInfo %+v", route) + _, dstIPNet, _ := net.ParseCIDR(route.IPAddress) + gwIP := net.ParseIP(route.GatewayIPAddress) + targetEpInfo.Routes = append(targetEpInfo.Routes, network.RouteInfo{Dst: *dstIPNet, Gw: gwIP, DevName: existingEpInfo.IfName}) + log.Printf("Successfully added route from routes to targetEpInfo %+v", route) + } + } + + log.Printf("Going to collect target routes based on Cnetaddressspace for [name=%v, namespace=%v] from targetNetworkConfig.", k8sPodName, k8sNamespace) + ipconfig := targetNetworkConfig.IPConfiguration + for _, ipRouteSubnet := range targetNetworkConfig.CnetAddressSpace { + log.Printf("Adding route from cnetAddressspace to targetEpInfo %+v", ipRouteSubnet) + dstIPNet := net.IPNet{IP: net.ParseIP(ipRouteSubnet.IPAddress), Mask: net.CIDRMask(int(ipRouteSubnet.PrefixLength), 32)} + gwIP := net.ParseIP(ipconfig.GatewayIPAddress) + route := network.RouteInfo{Dst: dstIPNet, Gw: gwIP, DevName: existingEpInfo.IfName} + targetEpInfo.Routes = append(targetEpInfo.Routes, route) + log.Printf("Successfully added route from cnetAddressspace to targetEpInfo %+v", ipRouteSubnet) + } + + log.Printf("Finished collecting new routes in targetEpInfo as follows: %+v", targetEpInfo.Routes) + log.Printf("Now saving existing infravnetaddress space if needed.") + for _, ns := range nwCfg.PodNamespaceForDualNetwork { + if k8sNamespace == ns { + targetEpInfo.EnableInfraVnet = true + targetEpInfo.InfraVnetAddressSpace = nwCfg.InfraVnetAddressSpace + log.Printf("Saving infravnet address space %s for [%s-%s]", + targetEpInfo.InfraVnetAddressSpace, existingEpInfo.PODNameSpace, existingEpInfo.PODName) + break + } + } + + // Update the endpoint. + log.Printf("Now updating existing endpoint %v with targetNetworkConfig %+v.", existingEpInfo.Id, targetNetworkConfig) + err = plugin.nm.UpdateEndpoint(networkID, existingEpInfo, targetEpInfo) + if err != nil { + err = plugin.Errorf("Failed to update endpoint: %v", err) + return err + } + + return nil +} diff --git a/cni/network/plugin/main.go b/cni/network/plugin/main.go index a50ef3ad1c..4c216f2d3f 100644 --- a/cni/network/plugin/main.go +++ b/cni/network/plugin/main.go @@ -4,14 +4,19 @@ package main import ( + "encoding/json" + "fmt" + "io/ioutil" "os" "reflect" "github.com/Azure/azure-container-networking/cni" "github.com/Azure/azure-container-networking/cni/network" "github.com/Azure/azure-container-networking/common" + acn "github.com/Azure/azure-container-networking/common" "github.com/Azure/azure-container-networking/log" "github.com/Azure/azure-container-networking/telemetry" + "github.com/containernetworking/cni/pkg/skel" ) const ( @@ -23,6 +28,22 @@ const ( // Version is populated by make during build. var version string +// Command line arguments for CNI plugin. +var args = acn.ArgumentList{ + { + Name: acn.OptVersion, + Shorthand: acn.OptVersionAlias, + Description: "Print version information", + Type: "bool", + DefaultValue: false, + }, +} + +// Prints version information. +func printVersion() { + fmt.Printf("Azure CNI Version %v\n", version) +} + // If report write succeeded, mark the report flag state to false. func markSendReport(reportManager *telemetry.ReportManager) { if err := reportManager.SetReportState(telemetry.CNITelemetryFile); err != nil { @@ -48,8 +69,82 @@ func reportPluginError(reportManager *telemetry.ReportManager, err error) { } } +func validateConfig(jsonBytes []byte) error { + var conf struct { + Name string `json:"name"` + } + if err := json.Unmarshal(jsonBytes, &conf); err != nil { + return fmt.Errorf("error reading network config: %s", err) + } + if conf.Name == "" { + return fmt.Errorf("missing network name") + } + return nil +} + +func getCmdArgsFromEnv() (string, *skel.CmdArgs, error) { + log.Printf("Going to read from stdin") + stdinData, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return "", nil, fmt.Errorf("error reading from stdin: %v", err) + } + + cmdArgs := &skel.CmdArgs{ + ContainerID: os.Getenv("CNI_CONTAINERID"), + Netns: os.Getenv("CNI_NETNS"), + IfName: os.Getenv("CNI_IFNAME"), + Args: os.Getenv("CNI_ARGS"), + Path: os.Getenv("CNI_PATH"), + StdinData: stdinData, + } + + cmd := os.Getenv("CNI_COMMAND") + return cmd, cmdArgs, nil +} + +func handleIfCniUpdate(update func(*skel.CmdArgs) error) (bool, error) { + isupdate := true + + if os.Getenv("CNI_COMMAND") != cni.CmdUpdate { + return false, nil + } + + log.Printf("CNI UPDATE received.") + + _, cmdArgs, err := getCmdArgsFromEnv() + if err != nil { + log.Printf("Received error while retrieving cmds from environment: %+v", err) + return isupdate, err + } + + log.Printf("Retrieved command args for update +%v", cmdArgs) + err = validateConfig(cmdArgs.StdinData) + if err != nil { + log.Printf("Failed to handle CNI UPDATE, err:%v.", err) + return isupdate, err + } + + err = update(cmdArgs) + if err != nil { + log.Printf("Failed to handle CNI UPDATE, err:%v.", err) + return isupdate, err + } + + return isupdate, nil +} + // Main is the entry point for CNI network plugin. func main() { + + // Initialize and parse command line arguments. + acn.ParseArgs(&args, printVersion) + vers := acn.GetArg(acn.OptVersion).(bool) + + if vers { + printVersion() + os.Exit(0) + } + var ( config common.PluginConfig err error @@ -109,7 +204,10 @@ func main() { panic("network plugin fatal error") } - if err = netPlugin.Execute(cni.PluginApi(netPlugin)); err != nil { + handled, err := handleIfCniUpdate(netPlugin.Update) + if handled == true { + log.Printf("CNI UPDATE finished.") + } else if err = netPlugin.Execute(cni.PluginApi(netPlugin)); err != nil { log.Printf("Failed to execute network plugin, err:%v.\n", err) reportPluginError(reportManager, err) } diff --git a/cns/service/main.go b/cns/service/main.go index 4a4ee0a98d..0b93318082 100644 --- a/cns/service/main.go +++ b/cns/service/main.go @@ -28,7 +28,7 @@ const ( // Version is populated by make during build. var version string -// Command line arguments for CNM plugin. +// Command line arguments for CNS. var args = acn.ArgumentList{ { Name: acn.OptEnvironment, diff --git a/network/api.go b/network/api.go index 4b6b93ba81..7e13a12835 100644 --- a/network/api.go +++ b/network/api.go @@ -9,12 +9,14 @@ import ( var ( // Error responses returned by NetworkManager. - errSubnetNotFound = fmt.Errorf("Subnet not found") - errNetworkModeInvalid = fmt.Errorf("Network mode is invalid") - errNetworkExists = fmt.Errorf("Network already exists") - errNetworkNotFound = fmt.Errorf("Network not found") - errEndpointExists = fmt.Errorf("Endpoint already exists") - errEndpointNotFound = fmt.Errorf("Endpoint not found") - errEndpointInUse = fmt.Errorf("Endpoint is already joined to a sandbox") - errEndpointNotInUse = fmt.Errorf("Endpoint is not joined to a sandbox") + errSubnetNotFound = fmt.Errorf("Subnet not found") + errNetworkModeInvalid = fmt.Errorf("Network mode is invalid") + errNetworkExists = fmt.Errorf("Network already exists") + errNetworkNotFound = fmt.Errorf("Network not found") + errEndpointExists = fmt.Errorf("Endpoint already exists") + errEndpointNotFound = fmt.Errorf("Endpoint not found") + errNamespaceNotFound = fmt.Errorf("Namespace not found") + errMultipleEndpointsFound = fmt.Errorf("Multiple endpoints found") + errEndpointInUse = fmt.Errorf("Endpoint is already joined to a sandbox") + errEndpointNotInUse = fmt.Errorf("Endpoint is not joined to a sandbox") ) diff --git a/network/endpoint.go b/network/endpoint.go index 1a10d9277b..763f27a655 100644 --- a/network/endpoint.go +++ b/network/endpoint.go @@ -16,40 +16,50 @@ const ( // Endpoint represents a container network interface. type endpoint struct { - Id string - HnsId string `json:",omitempty"` - SandboxKey string - IfName string - HostIfName string - MacAddress net.HardwareAddr - InfraVnetIP net.IPNet - IPAddresses []net.IPNet - Gateways []net.IP - DNS DNSInfo - Routes []RouteInfo - VlanID int - EnableSnatOnHost bool - EnableInfraVnet bool + Id string + HnsId string `json:",omitempty"` + SandboxKey string + IfName string + HostIfName string + MacAddress net.HardwareAddr + InfraVnetIP net.IPNet + IPAddresses []net.IPNet + Gateways []net.IP + DNS DNSInfo + Routes []RouteInfo + VlanID int + EnableSnatOnHost bool + EnableInfraVnet bool + EnableMultitenancy bool + NetworkNameSpace string `json:",omitempty"` + ContainerID string + PODName string `json:",omitempty"` + PODNameSpace string `json:",omitempty"` + InfraVnetAddressSpace string `json:",omitempty"` } // EndpointInfo contains read-only information about an endpoint. type EndpointInfo struct { - Id string - ContainerID string - NetNsPath string - IfName string - SandboxKey string - IfIndex int - MacAddress net.HardwareAddr - DNS DNSInfo - IPAddresses []net.IPNet - InfraVnetIP net.IPNet - Routes []RouteInfo - Policies []policy.Policy - Gateways []net.IP - EnableSnatOnHost bool - EnableInfraVnet bool - Data map[string]interface{} + Id string + ContainerID string + NetNsPath string + IfName string + SandboxKey string + IfIndex int + MacAddress net.HardwareAddr + DNS DNSInfo + IPAddresses []net.IPNet + InfraVnetIP net.IPNet + Routes []RouteInfo + Policies []policy.Policy + Gateways []net.IP + EnableSnatOnHost bool + EnableInfraVnet bool + EnableMultiTenancy bool + PODName string + PODNameSpace string + Data map[string]interface{} + InfraVnetAddressSpace string } // RouteInfo contains information about an IP route. @@ -128,6 +138,29 @@ func (nw *network) getEndpoint(endpointId string) (*endpoint, error) { return ep, nil } +// GetEndpointByPOD returns the endpoint with the given ID. +func (nw *network) getEndpointByPOD(podName string, podNameSpace string) (*endpoint, error) { + log.Printf("Trying to retrieve endpoint for pod name: %v in namespace: %v", podName, podNameSpace) + + var ep *endpoint + + for _, endpoint := range nw.Endpoints { + if endpoint.PODName == podName && endpoint.PODNameSpace == podNameSpace { + if ep == nil { + ep = endpoint + } else { + return nil, errMultipleEndpointsFound + } + } + } + + if ep == nil { + return nil, errEndpointNotFound + } + + return ep, nil +} + // // Endpoint // @@ -135,16 +168,22 @@ func (nw *network) getEndpoint(endpointId string) (*endpoint, error) { // GetInfo returns information about the endpoint. func (ep *endpoint) getInfo() *EndpointInfo { info := &EndpointInfo{ - Id: ep.Id, - IPAddresses: ep.IPAddresses, - InfraVnetIP: ep.InfraVnetIP, - Data: make(map[string]interface{}), - MacAddress: ep.MacAddress, - SandboxKey: ep.SandboxKey, - IfIndex: 0, // Azure CNI supports only one interface - DNS: ep.DNS, - EnableSnatOnHost: ep.EnableSnatOnHost, - EnableInfraVnet: ep.EnableInfraVnet, + Id: ep.Id, + IPAddresses: ep.IPAddresses, + InfraVnetIP: ep.InfraVnetIP, + Data: make(map[string]interface{}), + MacAddress: ep.MacAddress, + SandboxKey: ep.SandboxKey, + IfIndex: 0, // Azure CNI supports only one interface + DNS: ep.DNS, + EnableSnatOnHost: ep.EnableSnatOnHost, + EnableInfraVnet: ep.EnableInfraVnet, + EnableMultiTenancy: ep.EnableMultitenancy, + IfName: ep.IfName, + ContainerID: ep.ContainerID, + NetNsPath: ep.NetworkNameSpace, + PODName: ep.PODName, + PODNameSpace: ep.PODNameSpace, } for _, route := range ep.Routes { @@ -186,3 +225,35 @@ func (ep *endpoint) detach() error { return nil } + +// updateEndpoint updates an existing endpoint in the network. +func (nw *network) updateEndpoint(exsitingEpInfo *EndpointInfo, targetEpInfo *EndpointInfo) (*endpoint, error) { + var err error + + log.Printf("[net] Updating existing endpoint [%+v] in network %v to target [%+v].", exsitingEpInfo, nw.Id, targetEpInfo) + defer func() { + if err != nil { + log.Printf("[net] Failed to update endpoint %v, err:%v.", exsitingEpInfo.Id, err) + } + }() + + log.Printf("Trying to retrieve endpoint id %v", exsitingEpInfo.Id) + + ep := nw.Endpoints[exsitingEpInfo.Id] + if ep == nil { + return nil, errEndpointNotFound + } + + log.Printf("[net] Retrieved endpoint to update %+v.", ep) + + // Call the platform implementation. + ep, err = nw.updateEndpointImpl(exsitingEpInfo, targetEpInfo) + if err != nil { + return nil, err + } + + // Update routes for existing endpoint + nw.Endpoints[exsitingEpInfo.Id].Routes = ep.Routes + + return ep, nil +} diff --git a/network/endpoint_linux.go b/network/endpoint_linux.go index cf85a5ad1a..0bfd5df969 100644 --- a/network/endpoint_linux.go +++ b/network/endpoint_linux.go @@ -96,14 +96,15 @@ func (nw *network) newEndpointImpl(epInfo *EndpointInfo) (*endpoint, error) { if err != nil { log.Printf("CNI error. Delete Endpoint %v and rules that are created.", contIfName) endpt := &endpoint{ - Id: epInfo.Id, - IfName: contIfName, - HostIfName: hostIfName, - IPAddresses: epInfo.IPAddresses, - Gateways: []net.IP{nw.extIf.IPv4Gateway}, - DNS: epInfo.DNS, - VlanID: vlanid, - EnableSnatOnHost: epInfo.EnableSnatOnHost, + Id: epInfo.Id, + IfName: contIfName, + HostIfName: hostIfName, + IPAddresses: epInfo.IPAddresses, + Gateways: []net.IP{nw.extIf.IPv4Gateway}, + DNS: epInfo.DNS, + VlanID: vlanid, + EnableSnatOnHost: epInfo.EnableSnatOnHost, + EnableMultitenancy: epInfo.EnableMultiTenancy, } if containerIf != nil { @@ -171,17 +172,22 @@ func (nw *network) newEndpointImpl(epInfo *EndpointInfo) (*endpoint, error) { // Create the endpoint object. ep = &endpoint{ - Id: epInfo.Id, - IfName: epInfo.IfName, - HostIfName: hostIfName, - MacAddress: containerIf.HardwareAddr, - InfraVnetIP: epInfo.InfraVnetIP, - IPAddresses: epInfo.IPAddresses, - Gateways: []net.IP{nw.extIf.IPv4Gateway}, - DNS: epInfo.DNS, - VlanID: vlanid, - EnableSnatOnHost: epInfo.EnableSnatOnHost, - EnableInfraVnet: epInfo.EnableInfraVnet, + Id: epInfo.Id, + IfName: epInfo.IfName, + HostIfName: hostIfName, + MacAddress: containerIf.HardwareAddr, + InfraVnetIP: epInfo.InfraVnetIP, + IPAddresses: epInfo.IPAddresses, + Gateways: []net.IP{nw.extIf.IPv4Gateway}, + DNS: epInfo.DNS, + VlanID: vlanid, + EnableSnatOnHost: epInfo.EnableSnatOnHost, + EnableInfraVnet: epInfo.EnableInfraVnet, + EnableMultitenancy: epInfo.EnableMultiTenancy, + NetworkNameSpace: epInfo.NetNsPath, + ContainerID: epInfo.ContainerID, + PODName: epInfo.PODName, + PODNameSpace: epInfo.PODNameSpace, } for _, route := range epInfo.Routes { @@ -253,7 +259,7 @@ func deleteRoutes(interfaceName string, routes []RouteInfo) error { interfaceIf, _ := net.InterfaceByName(interfaceName) for _, route := range routes { - log.Printf("[ovs] Adding IP route %+v to link %v.", route, interfaceName) + log.Printf("[ovs] Deleting IP route %+v from link %v.", route, interfaceName) if route.DevName != "" { devIf, _ := net.InterfaceByName(route.DevName) @@ -276,3 +282,137 @@ func deleteRoutes(interfaceName string, routes []RouteInfo) error { return nil } + +// updateEndpointImpl updates an existing endpoint in the network. +func (nw *network) updateEndpointImpl(existingEpInfo *EndpointInfo, targetEpInfo *EndpointInfo) (*endpoint, error) { + var ns *Namespace + var ep *endpoint + var err error + + existingEpFromRepository := nw.Endpoints[existingEpInfo.Id] + log.Printf("[updateEndpointImpl] Going to retrieve endpoint with Id %+v to update.", existingEpInfo.Id) + if existingEpFromRepository == nil { + log.Printf("[updateEndpointImpl] Endpoint cannot be updated as it does not exist.") + err = errEndpointNotFound + return nil, err + } + + netns := existingEpFromRepository.NetworkNameSpace + // Network namespace for the container interface has to be specified + if netns != "" { + // Open the network namespace. + log.Printf("[updateEndpointImpl] Opening netns %v.", netns) + ns, err = OpenNamespace(netns) + if err != nil { + return nil, err + } + defer ns.Close() + + // Enter the container network namespace. + log.Printf("[updateEndpointImpl] Entering netns %v.", netns) + if err = ns.Enter(); err != nil { + return nil, err + } + + // Return to host network namespace. + defer func() { + log.Printf("[updateEndpointImpl] Exiting netns %v.", netns) + if err := ns.Exit(); err != nil { + log.Printf("[updateEndpointImpl] Failed to exit netns, err:%v.", err) + } + }() + } else { + log.Printf("[updateEndpointImpl] Endpoint cannot be updated as the network namespace does not exist: Epid: %v", existingEpInfo.Id) + err = errNamespaceNotFound + return nil, err + } + + log.Printf("[updateEndpointImpl] Going to update routes in netns %v.", netns) + if err = updateRoutes(existingEpInfo, targetEpInfo); err != nil { + return nil, err + } + + // Create the endpoint object. + ep = &endpoint{ + Id: existingEpInfo.Id, + } + + // Update existing endpoint state with the new routes to persist + for _, route := range targetEpInfo.Routes { + ep.Routes = append(ep.Routes, route) + } + + return ep, nil +} + +func updateRoutes(existingEp *EndpointInfo, targetEp *EndpointInfo) error { + log.Printf("Updating routes for the endpoint %+v.", existingEp) + log.Printf("Target endpoint is %+v", targetEp) + + existingRoutes := make(map[string]RouteInfo) + targetRoutes := make(map[string]RouteInfo) + var tobeDeletedRoutes []RouteInfo + var tobeAddedRoutes []RouteInfo + + // we should not remove default route from container if it exists + // we do not support enable/disable snat for now + defaultDst := net.ParseIP("0.0.0.0") + + log.Printf("Going to collect routes and skip default and infravnet routes if applicable.") + log.Printf("Key for default route: %+v", defaultDst.String()) + + infraVnetKey := "" + if targetEp.EnableInfraVnet { + infraVnetSubnet := targetEp.InfraVnetAddressSpace + if infraVnetSubnet != "" { + infraVnetKey = strings.Split(infraVnetSubnet, "/")[0] + } + } + + log.Printf("Key for route to infra vnet: %+v", infraVnetKey) + for _, route := range existingEp.Routes { + destination := route.Dst.IP.String() + log.Printf("Checking destination as %+v to skip or not", destination) + isDefaultRoute := destination == defaultDst.String() + isInfraVnetRoute := targetEp.EnableInfraVnet && (destination == infraVnetKey) + if !isDefaultRoute && !isInfraVnetRoute { + existingRoutes[route.Dst.String()] = route + log.Printf("%+v was skipped", destination) + } + } + + for _, route := range targetEp.Routes { + targetRoutes[route.Dst.String()] = route + } + + for _, existingRoute := range existingRoutes { + dst := existingRoute.Dst.String() + if _, ok := targetRoutes[dst]; !ok { + tobeDeletedRoutes = append(tobeDeletedRoutes, existingRoute) + log.Printf("Adding following route to the tobeDeleted list: %+v", existingRoute) + } + } + + for _, targetRoute := range targetRoutes { + dst := targetRoute.Dst.String() + if _, ok := existingRoutes[dst]; !ok { + tobeAddedRoutes = append(tobeAddedRoutes, targetRoute) + log.Printf("Adding following route to the tobeAdded list: %+v", targetRoute) + } + + } + + err := deleteRoutes(existingEp.IfName, tobeDeletedRoutes) + if err != nil { + return err + } + + err = addRoutes(existingEp.IfName, tobeAddedRoutes) + if err != nil { + return err + } + + log.Printf("Successfully updated routes for the endpoint %+v using target: %+v", existingEp, targetEp) + + return nil +} diff --git a/network/endpoint_windows.go b/network/endpoint_windows.go index 5b2b75610b..1d894ca143 100644 --- a/network/endpoint_windows.go +++ b/network/endpoint_windows.go @@ -128,3 +128,8 @@ func (nw *network) deleteEndpointImpl(ep *endpoint) error { func (ep *endpoint) getInfoImpl(epInfo *EndpointInfo) { epInfo.Data["hnsid"] = ep.HnsId } + +// updateEndpointImpl in windows does nothing for now +func (nw *network) updateEndpointImpl(existingEpInfo *EndpointInfo, targetEpInfo *EndpointInfo) (*endpoint, error) { + return nil, nil +} diff --git a/network/manager.go b/network/manager.go index e40ca78a7f..cdf03befab 100644 --- a/network/manager.go +++ b/network/manager.go @@ -61,8 +61,10 @@ type NetworkManager interface { CreateEndpoint(networkId string, epInfo *EndpointInfo) error DeleteEndpoint(networkId string, endpointId string) error GetEndpointInfo(networkId string, endpointId string) (*EndpointInfo, error) + GetEndpointInfoBasedOnPODDetails(networkId string, podName string, podNameSpace string) (*EndpointInfo, error) AttachEndpoint(networkId string, endpointId string, sandboxKey string) (*endpoint, error) DetachEndpoint(networkId string, endpointId string) error + UpdateEndpoint(networkId string, existingEpInfo *EndpointInfo, targetEpInfo *EndpointInfo) error } // Creates a new network manager. @@ -153,11 +155,11 @@ func (nm *networkManager) restore() error { log.Printf("[net] Restored state, %+v\n", nm) for _, extIf := range nm.ExternalInterfaces { - log.Printf("External Interface %v", extIf) + log.Printf("External Interface %+v", extIf) for _, nw := range extIf.Networks { - log.Printf("network %v", nw) + log.Printf("network %+v", nw) for _, ep := range nw.Endpoints { - log.Printf("endpoint %v", ep) + log.Printf("endpoint %+v", ep) } } } @@ -341,6 +343,25 @@ func (nm *networkManager) GetEndpointInfo(networkId string, endpointId string) ( return ep.getInfo(), nil } +// GetEndpointInfoBasedOnPODDetails returns information about the given endpoint. +// It returns an error if a single pod has multiple endpoints. +func (nm *networkManager) GetEndpointInfoBasedOnPODDetails(networkID string, podName string, podNameSpace string) (*EndpointInfo, error) { + nm.Lock() + defer nm.Unlock() + + nw, err := nm.getNetwork(networkID) + if err != nil { + return nil, err + } + + ep, err := nw.getEndpointByPOD(podName, podNameSpace) + if err != nil { + return nil, err + } + + return ep.getInfo(), nil +} + // AttachEndpoint attaches an endpoint to a sandbox. func (nm *networkManager) AttachEndpoint(networkId string, endpointId string, sandboxKey string) (*endpoint, error) { nm.Lock() @@ -396,3 +417,26 @@ func (nm *networkManager) DetachEndpoint(networkId string, endpointId string) er return nil } + +// UpdateEndpoint updates an existing container endpoint. +func (nm *networkManager) UpdateEndpoint(networkID string, existingEpInfo *EndpointInfo, targetEpInfo *EndpointInfo) error { + nm.Lock() + defer nm.Unlock() + + nw, err := nm.getNetwork(networkID) + if err != nil { + return err + } + + _, err = nw.updateEndpoint(existingEpInfo, targetEpInfo) + if err != nil { + return err + } + + err = nm.save() + if err != nil { + return err + } + + return nil +}