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

Update Node's MAC address to the Node's annotation for direct routing #2161

Merged
merged 1 commit into from
May 21, 2021
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
1 change: 1 addition & 0 deletions build/yamls/antrea-aks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3052,6 +3052,7 @@ rules:
- get
- watch
- list
- patch
- apiGroups:
- ""
resources:
Expand Down
1 change: 1 addition & 0 deletions build/yamls/antrea-eks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3052,6 +3052,7 @@ rules:
- get
- watch
- list
- patch
- apiGroups:
- ""
resources:
Expand Down
1 change: 1 addition & 0 deletions build/yamls/antrea-gke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3052,6 +3052,7 @@ rules:
- get
- watch
- list
- patch
- apiGroups:
- ""
resources:
Expand Down
1 change: 1 addition & 0 deletions build/yamls/antrea-ipsec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3052,6 +3052,7 @@ rules:
- get
- watch
- list
- patch
- apiGroups:
- ""
resources:
Expand Down
1 change: 1 addition & 0 deletions build/yamls/antrea.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3052,6 +3052,7 @@ rules:
- get
- watch
- list
- patch
- apiGroups:
- ""
resources:
Expand Down
1 change: 1 addition & 0 deletions build/yamls/base/agent-rbac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ rules:
- get
- watch
- list
- patch
- apiGroups:
- ""
resources:
Expand Down
32 changes: 29 additions & 3 deletions pkg/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package agent

import (
"context"
"encoding/json"
"fmt"
"net"
"os"
Expand All @@ -25,8 +26,10 @@ import (

"github.com/containernetworking/plugins/pkg/ip"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apitypes "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"

"antrea.io/antrea/pkg/agent/cniserver"
Expand All @@ -53,6 +56,9 @@ const (
maxRetryForRoundNumSave = 5
)

// getIPNetDeviceFromIP is meant to be overridden for testing.
var getIPNetDeviceFromIP = util.GetIPNetDeviceFromIP

// Initializer knows how to setup host networking, OpenVSwitch, and Openflow.
type Initializer struct {
client clientset.Interface
Expand Down Expand Up @@ -637,11 +643,31 @@ func (i *Initializer) initNodeLocalConfig() error {

ipAddr, err := noderoute.GetNodeAddr(node)
if err != nil {
return fmt.Errorf("failed to obtain local IP address from k8s: %w", err)
return fmt.Errorf("failed to obtain local IP address from K8s: %w", err)
}
localAddr, localIntf, err := util.GetIPNetDeviceFromIP(ipAddr)
localAddr, localIntf, err := getIPNetDeviceFromIP(ipAddr)
if err != nil {
return fmt.Errorf("failed to get local IPNet: %v", err)
return fmt.Errorf("failed to get local IPNet device with IP %v: %v", ipAddr, err)
}

// Update the Node's MAC address in the annotations of the Node. The MAC address will be used for direct routing by
// OVS in noencap case on Windows Nodes. As a mixture of Linux and Windows nodes is possible, Linux Nodes' MAC
// addresses should be reported too to make them discoverable for Windows Nodes.
if i.networkConfig.TrafficEncapMode.SupportsNoEncap() {
klog.Infof("Updating Node MAC annotation")
patch, _ := json.Marshal(map[string]interface{}{
"metadata": map[string]interface{}{
"annotations": map[string]string{
types.NodeMACAddressAnnotationKey: localIntf.HardwareAddr.String(),
},
},
})
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
_, err := i.client.CoreV1().Nodes().Patch(context.TODO(), nodeName, apitypes.MergePatchType, patch, metav1.PatchOptions{})
return err
}); err != nil {
return err
}
}

i.nodeConfig = &config.NodeConfig{
Expand Down
125 changes: 125 additions & 0 deletions pkg/agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,27 @@
package agent

import (
"context"
"fmt"
"net"
"os"
"testing"

mock "github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"

"antrea.io/antrea/pkg/agent/cniserver"
"antrea.io/antrea/pkg/agent/config"
"antrea.io/antrea/pkg/agent/interfacestore"
"antrea.io/antrea/pkg/agent/types"
"antrea.io/antrea/pkg/ovs/ovsconfig"
ovsconfigtest "antrea.io/antrea/pkg/ovs/ovsconfig/testing"
"antrea.io/antrea/pkg/util/env"
)

func newAgentInitializer(ovsBridgeClient ovsconfig.OVSBridgeClient, ifaceStore interfacestore.InterfaceStore) *Initializer {
Expand Down Expand Up @@ -134,3 +142,120 @@ func TestGetRoundInfo(t *testing.T) {
roundInfo = getRoundInfo(mockOVSBridgeClient)
assert.Equal(t, uint64(initialRoundNum), roundInfo.RoundNum, "Unexpected round number")
}

func TestInitNodeLocalConfig(t *testing.T) {
nodeName := "node1"
ovsBridge := "br-int"
nodeIPStr := "192.168.10.10"
_, nodeIPNet, _ := net.ParseCIDR("192.168.10.10/24")
macAddr, _ := net.ParseMAC("00:00:5e:00:53:01")
ipDevice := &net.Interface{
Index: 10,
MTU: 1500,
Name: "ens160",
HardwareAddr: macAddr,
}
podCIDRStr := "172.16.10.0/24"
_, podCIDR, _ := net.ParseCIDR(podCIDRStr)
tests := []struct {
name string
trafficEncapMode config.TrafficEncapModeType
tunnelType ovsconfig.TunnelType
mtu int
expectedMTU int
expectedNodeAnnotation map[string]string
}{
{
name: "noencap mode",
trafficEncapMode: config.TrafficEncapModeNoEncap,
mtu: 0,
expectedMTU: 1500,
expectedNodeAnnotation: map[string]string{types.NodeMACAddressAnnotationKey: macAddr.String()},
},
{
name: "hybrid mode",
trafficEncapMode: config.TrafficEncapModeHybrid,
mtu: 0,
expectedMTU: 1500,
expectedNodeAnnotation: map[string]string{types.NodeMACAddressAnnotationKey: macAddr.String()},
},
{
name: "encap mode, geneve tunnel",
trafficEncapMode: config.TrafficEncapModeEncap,
tunnelType: ovsconfig.GeneveTunnel,
mtu: 0,
expectedMTU: 1450,
expectedNodeAnnotation: nil,
},
{
name: "encap mode, mtu specified",
trafficEncapMode: config.TrafficEncapModeEncap,
tunnelType: ovsconfig.GeneveTunnel,
mtu: 1400,
expectedMTU: 1400,
expectedNodeAnnotation: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
node := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: nodeName,
},
Spec: corev1.NodeSpec{
PodCIDR: podCIDRStr,
},
Status: corev1.NodeStatus{
Addresses: []corev1.NodeAddress{
{
Type: corev1.NodeInternalIP,
Address: nodeIPStr,
},
},
},
}
client := fake.NewSimpleClientset(node)
ifaceStore := interfacestore.NewInterfaceStore()
defer mockGetIPNetDeviceFromIP(nodeIPNet, ipDevice)()
defer mockNodeNameEnv(nodeName)()

initializer := &Initializer{
client: client,
ifaceStore: ifaceStore,
mtu: tt.mtu,
ovsBridge: ovsBridge,
networkConfig: &config.NetworkConfig{
TrafficEncapMode: tt.trafficEncapMode,
TunnelType: tt.tunnelType,
},
}
require.NoError(t, initializer.initNodeLocalConfig())
expectedNodeConfig := config.NodeConfig{
Name: nodeName,
OVSBridge: ovsBridge,
DefaultTunName: defaultTunInterfaceName,
PodIPv4CIDR: podCIDR,
NodeIPAddr: nodeIPNet,
NodeMTU: tt.expectedMTU,
UplinkNetConfig: new(config.AdapterNetConfig),
}
assert.Equal(t, expectedNodeConfig, *initializer.nodeConfig)
node, err := client.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
require.NoError(t, err)
assert.Equal(t, tt.expectedNodeAnnotation, node.Annotations)
})
}
}

func mockGetIPNetDeviceFromIP(ipNet *net.IPNet, ipDevice *net.Interface) func() {
prevGetIPNetDeviceFromIP := getIPNetDeviceFromIP
getIPNetDeviceFromIP = func(localIP net.IP) (*net.IPNet, *net.Interface, error) {
return ipNet, ipDevice, nil
}
return func() { getIPNetDeviceFromIP = prevGetIPNetDeviceFromIP }
}

func mockNodeNameEnv(name string) func() {
_ = os.Setenv(env.NodeNameEnvKey, name)
return func() { os.Unsetenv(env.NodeNameEnvKey) }
}
6 changes: 3 additions & 3 deletions pkg/agent/agent_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (i *Initializer) prepareHostNetwork() error {
// Create HNS network.
subnetCIDR := i.nodeConfig.PodIPv4CIDR
if subnetCIDR == nil {
return fmt.Errorf("Failed to find valid IPv4 PodCIDR")
return fmt.Errorf("failed to find valid IPv4 PodCIDR")
}
return util.PrepareHNSNetwork(subnetCIDR, i.nodeConfig.NodeIPAddr, adapter)
}
Expand All @@ -82,7 +82,7 @@ func (i *Initializer) prepareOVSBridge() error {
hnsNetwork, err := hcsshim.GetHNSNetworkByName(util.LocalHNSNetwork)
defer func() {
// prepareOVSBridge only works on windows platform. The operation has a chance to fail on the first time agent
// starts up when OVS bridge uplink and local inteface have not been configured. If the operation fails, the
// starts up when OVS bridge uplink and local interface have not been configured. If the operation fails, the
// host can not communicate with external network. To make sure the agent can connect to API server in
// next retry, this step deletes OVS bridge and HNS network created previously which will restore the
// host network.
Expand Down Expand Up @@ -144,7 +144,7 @@ func (i *Initializer) prepareOVSBridge() error {

// Move network configuration of uplink interface to OVS bridge local interface.
// - The net configuration of uplink will be restored by OS if the attached HNS network is deleted.
// - When ovs-switchd is down, antrea-agent will disable OVS Extension. The OVS bridge local interface will work
// - When ovs-vswitchd is down, antrea-agent will disable OVS Extension. The OVS bridge local interface will work
// like a normal interface on host and is responsible for forwarding host traffic.
if err = util.EnableHostInterface(brName); err != nil {
return err
Expand Down
20 changes: 20 additions & 0 deletions pkg/agent/types/annotations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2021 Antrea Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package types

const (
// NodeMACAddressAnnotationKey represents the key of the Node's MAC address in the Annotations of the Node.
NodeMACAddressAnnotationKey string = "node.antrea.io/mac-address"
)
2 changes: 1 addition & 1 deletion pkg/agent/util/net_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ func GetLocalBroadcastIP(ipNet *net.IPNet) net.IP {
return lastAddr
}

// GetDefaultGatewayByInterfaceIndex returns the default gateway configured on the speicified interface.
// GetDefaultGatewayByInterfaceIndex returns the default gateway configured on the specified interface.
func GetDefaultGatewayByInterfaceIndex(ifIndex int) (string, error) {
cmd := fmt.Sprintf("$(Get-NetRoute -InterfaceIndex %d -DestinationPrefix 0.0.0.0/0 ).NextHop", ifIndex)
defaultGW, err := CallPSCommand(cmd)
Expand Down
8 changes: 4 additions & 4 deletions pkg/util/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import (
"k8s.io/klog/v2"
)

// nodeNameEnvKey is environment variable.
// NodeNameEnvKey is environment variable.
const (
nodeNameEnvKey = "NODE_NAME"
NodeNameEnvKey = "NODE_NAME"
podNameEnvKey = "POD_NAME"
podNamespaceEnvKey = "POD_NAMESPACE"
svcAcctNameEnvKey = "SERVICEACCOUNT_NAME"
Expand All @@ -37,11 +37,11 @@ const (
// - Environment variable NODE_NAME, which should be set by Downward API
// - OS's hostname
func GetNodeName() (string, error) {
nodeName := os.Getenv(nodeNameEnvKey)
nodeName := os.Getenv(NodeNameEnvKey)
if nodeName != "" {
return nodeName, nil
}
klog.Infof("Environment variable %s not found, using hostname instead", nodeNameEnvKey)
klog.Infof("Environment variable %s not found, using hostname instead", NodeNameEnvKey)
var err error
nodeName, err = os.Hostname()
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions pkg/util/env/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ func TestGetNodeName(t *testing.T) {

func compareNodeName(k, v string, t *testing.T) {
if k != "" {
_ = os.Setenv(nodeNameEnvKey, k)
defer os.Unsetenv(nodeNameEnvKey)
_ = os.Setenv(NodeNameEnvKey, k)
defer os.Unsetenv(NodeNameEnvKey)
}
nodeName, err := GetNodeName()
if err != nil {
Expand Down