From 8ac8a917df6aeb6ea11862b6322a3297b01b0862 Mon Sep 17 00:00:00 2001 From: Jianjun Shen Date: Mon, 7 Aug 2023 16:11:52 -0700 Subject: [PATCH] Support OVS bridge creation for secondary network (#5279) Add OVS bridge configuration to the secondary network configuration in antrea-agent.conf, which specifies the OVS bridges for Pod secondary networks and also physical interfaces of the bridges. At the moment, only a single bridge is supported and at most one physical interface can be configured on the bridge. antrea-agent will automatically create the OVS bridge and connects the physical interface (if specified) to the bridge, when the bridge is specified in the secondary network configuration and does not exist on the host. Signed-off-by: Jianjun Shen --- build/charts/antrea/README.md | 5 +- build/charts/antrea/conf/antrea-agent.conf | 22 ++- build/charts/antrea/values.yaml | 22 ++- build/yamls/antrea-aks.yml | 15 +- build/yamls/antrea-eks.yml | 15 +- build/yamls/antrea-gke.yml | 15 +- build/yamls/antrea-ipsec.yml | 15 +- build/yamls/antrea.yml | 15 +- cmd/antrea-agent/agent.go | 24 ++- cmd/antrea-agent/options.go | 26 ++++ cmd/antrea-agent/options_test.go | 68 +++++++++ pkg/agent/agent.go | 9 +- pkg/agent/agent_linux.go | 23 ++- pkg/agent/agent_test.go | 6 +- pkg/agent/agent_windows.go | 15 +- pkg/agent/secondarynetwork/init.go | 125 ++++++++++++++++ pkg/agent/secondarynetwork/init_test.go | 162 +++++++++++++++++++++ pkg/agent/util/net.go | 10 ++ pkg/agent/util/net_test.go | 28 ++++ pkg/config/agent/config.go | 26 +++- pkg/ovs/ovsconfig/ovs_client.go | 2 +- pkg/util/k8s/client.go | 19 +-- test/integration/ovs/ovs_client_test.go | 3 +- 23 files changed, 553 insertions(+), 117 deletions(-) create mode 100644 pkg/agent/secondarynetwork/init.go create mode 100644 pkg/agent/secondarynetwork/init_test.go diff --git a/build/charts/antrea/README.md b/build/charts/antrea/README.md index c395784f5b7..e26efaa27d8 100644 --- a/build/charts/antrea/README.md +++ b/build/charts/antrea/README.md @@ -72,9 +72,9 @@ Kubernetes: `>= 1.16.0-0` | controller.tolerations | list | `[{"key":"CriticalAddonsOnly","operator":"Exists"},{"effect":"NoSchedule","key":"node-role.kubernetes.io/master"},{"effect":"NoSchedule","key":"node-role.kubernetes.io/control-plane"}]` | Tolerations for the antrea-controller Pod. | | defaultMTU | int | `0` | Default MTU to use for the host gateway interface and the network interface of each Pod. By default, antrea-agent will discover the MTU of the Node's primary interface and adjust it to accommodate for tunnel encapsulation overhead if applicable. | | disableTXChecksumOffload | bool | `false` | Disable TX checksum offloading for container network interfaces. It's supposed to be set to true when the datapath doesn't support TX checksum offloading, which causes packets to be dropped due to bad checksum. It affects Pods running on Linux Nodes only. | -| dnsServerOverride | string | `""` | Address of DNS server, to override the kube-dns service. It's used to resolve hostname in FQDN policy. | +| dnsServerOverride | string | `""` | Address of DNS server, to override the kube-dns Service. It's used to resolve hostnames in a FQDN policy. | | egress.exceptCIDRs | list | `[]` | CIDR ranges to which outbound Pod traffic will not be SNAT'd by Egresses. | -| egress.maxEgressIPsPerNode | int | `255` | The maximum number of Egress IPs that can be assigned to a Node. It's useful when the Node network restricts the number of secondary IPs a Node can have, e.g. EKS. It must not be greater than 255. | +| egress.maxEgressIPsPerNode | int | `255` | The maximum number of Egress IPs that can be assigned to a Node. It is useful when the Node network restricts the number of secondary IPs a Node can have, e.g. EKS. It must not be greater than 255. | | enableBridgingMode | bool | `false` | Enable bridging mode of Pod network on Nodes, in which the Node's transport interface is connected to the OVS bridge. | | featureGates | object | `{}` | To explicitly enable or disable a FeatureGate and bypass the Antrea defaults, add an entry to the dictionary with the FeatureGate's name as the key and a boolean as the value. | | flowExporter.activeFlowExportTimeout | string | `"5s"` | timeout after which a flow record is sent to the collector for active flows. | @@ -111,6 +111,7 @@ Kubernetes: `>= 1.16.0-0` | nodePortLocal.portRange | string | `"61000-62000"` | Port range used by NodePortLocal when creating Pod port mappings. | | ovs.bridgeName | string | `"br-int"` | Name of the OVS bridge antrea-agent will create and use. | | ovs.hwOffload | bool | `false` | Enable hardware offload for the OVS bridge (required additional configuration). | +| secondaryNetwork.ovsBridges | list | `[]` | Configuration of OVS bridges for secondary network. At the moment, at most one OVS bridge can be specified. If the specified bridge does not exist on the Node, antrea-agent will create it based on the configuration. The following configuration specifies an OVS bridge with name "br1" and a physical interface "eth1": [{bridgeName: "br1", physicalInterfaces: ["eth1"]}] | | serviceCIDR | string | `""` | IPv4 CIDR range used for Services. Required when AntreaProxy is disabled. | | serviceCIDRv6 | string | `""` | IPv6 CIDR range used for Services. Required when AntreaProxy is disabled. | | testing.coverage | bool | `false` | Enable code coverage measurement (used when testing Antrea only). | diff --git a/build/charts/antrea/conf/antrea-agent.conf b/build/charts/antrea/conf/antrea-agent.conf index 2aaab4b43f7..515680e88dd 100644 --- a/build/charts/antrea/conf/antrea-agent.conf +++ b/build/charts/antrea/conf/antrea-agent.conf @@ -254,14 +254,15 @@ nodePortLocal: portRange: {{ .portRange | quote }} {{- end }} -# Provide the address of Kubernetes apiserver, to override any value provided in kubeconfig or InClusterConfig. -# It is typically used when kube-proxy is not deployed (replaced by AntreaProxy). +# Provide the address of Kubernetes apiserver, to override any value provided in kubeconfig or +# InClusterConfig. It is typically used when kube-proxy is not deployed (replaced by AntreaProxy). # Defaults to "". It must be a host string, a host:port pair, or a URL to the base of the apiserver. kubeAPIServerOverride: {{ .Values.kubeAPIServerOverride | quote }} -# Provide the address of DNS server, to override the kube-dns service. It's used to resolve hostname in FQDN policy. -# Defaults to "". It must be a host string or a host:port pair of the DNS server (e.g. 10.96.0.10, 10.96.0.10:53, -# [fd00:10:96::a]:53). +# Provide the address of DNS server, to override the kube-dns Service. It's used to resolve +# hostnames in a FQDN policy. +# Defaults to "". It must be a host string or a host:port pair of the DNS server (e.g. 10.96.0.10, +# 10.96.0.10:53, [fd00:10:96::a]:53). dnsServerOverride: {{ .Values.dnsServerOverride | quote }} # Comma-separated list of Cipher Suites. If omitted, the default Go Cipher Suites will be used. @@ -413,3 +414,14 @@ auditLogging: # Compress enables gzip compression on rotated files. compress: {{ .compress }} {{- end }} + +{{- if .Values.featureGates.SecondaryNetwork }} + +secondaryNetwork: +{{- with .Values.secondaryNetwork }} + # Configuration of OVS bridges for secondary network. + ovsBridges: + {{- toYaml .ovsBridges | trim | nindent 6 }} +{{- end }} + +{{- end }} diff --git a/build/charts/antrea/values.yaml b/build/charts/antrea/values.yaml index f5fcf8e8103..d65bbc038a9 100644 --- a/build/charts/antrea/values.yaml +++ b/build/charts/antrea/values.yaml @@ -93,8 +93,9 @@ ipsec: egress: # -- CIDR ranges to which outbound Pod traffic will not be SNAT'd by Egresses. exceptCIDRs: [] - # -- The maximum number of Egress IPs that can be assigned to a Node. It's useful when the Node network restricts - # the number of secondary IPs a Node can have, e.g. EKS. It must not be greater than 255. + # -- The maximum number of Egress IPs that can be assigned to a Node. It is + # useful when the Node network restricts the number of secondary IPs a Node + # can have, e.g. EKS. It must not be greater than 255. maxEgressIPsPerNode: 255 nodePortLocal: @@ -121,8 +122,8 @@ antreaProxy: # will only handle Services without the "service.kubernetes.io/service-proxy-name" # label, but ignore Services with the label no matter what is the value. serviceProxyName: "" - # -- Determines how external traffic is processed when it's load balanced across Nodes by default. It must be one of "nat" or - # "dsr". + # -- Determines how external traffic is processed when it's load balanced + # across Nodes by default. It must be one of "nat" or "dsr". defaultLoadBalancerMode: "nat" nodeIPAM: @@ -155,8 +156,8 @@ auditLogging: # -- Address of Kubernetes apiserver, to override any value provided in # kubeconfig or InClusterConfig. kubeAPIServerOverride: "" -# -- Address of DNS server, to override the kube-dns service. It's used to -# resolve hostname in FQDN policy. +# -- Address of DNS server, to override the kube-dns Service. It's used to +# resolve hostnames in a FQDN policy. dnsServerOverride: "" # -- IPv4 CIDR range used for Services. Required when AntreaProxy is disabled. serviceCIDR: "" @@ -180,6 +181,15 @@ clientCAFile: "" # key and a boolean as the value. featureGates: {} +secondaryNetwork: + # -- Configuration of OVS bridges for secondary network. At the moment, at + # most one OVS bridge can be specified. If the specified bridge does not exist + # on the Node, antrea-agent will create it based on the configuration. + # The following configuration specifies an OVS bridge with name "br1" and a + # physical interface "eth1": + # [{bridgeName: "br1", physicalInterfaces: ["eth1"]}] + ovsBridges: [] + agent: # -- Port for the antrea-agent APIServer to serve on. apiPort: 10350 diff --git a/build/yamls/antrea-aks.yml b/build/yamls/antrea-aks.yml index 1d6237f08b3..9945ec9001e 100644 --- a/build/yamls/antrea-aks.yml +++ b/build/yamls/antrea-aks.yml @@ -5698,14 +5698,15 @@ data: # directed to that port will be forwarded to the Pod. portRange: "61000-62000" - # Provide the address of Kubernetes apiserver, to override any value provided in kubeconfig or InClusterConfig. - # It is typically used when kube-proxy is not deployed (replaced by AntreaProxy). + # Provide the address of Kubernetes apiserver, to override any value provided in kubeconfig or + # InClusterConfig. It is typically used when kube-proxy is not deployed (replaced by AntreaProxy). # Defaults to "". It must be a host string, a host:port pair, or a URL to the base of the apiserver. kubeAPIServerOverride: "" - # Provide the address of DNS server, to override the kube-dns service. It's used to resolve hostname in FQDN policy. - # Defaults to "". It must be a host string or a host:port pair of the DNS server (e.g. 10.96.0.10, 10.96.0.10:53, - # [fd00:10:96::a]:53). + # Provide the address of DNS server, to override the kube-dns Service. It's used to resolve + # hostnames in a FQDN policy. + # Defaults to "". It must be a host string or a host:port pair of the DNS server (e.g. 10.96.0.10, + # 10.96.0.10:53, [fd00:10:96::a]:53). dnsServerOverride: "" # Comma-separated list of Cipher Suites. If omitted, the default Go Cipher Suites will be used. @@ -6818,7 +6819,7 @@ spec: kubectl.kubernetes.io/default-container: antrea-agent # Automatically restart Pods with a RollingUpdate if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: e982ae7bedfa361f13e134516243f3c8d566b9297abc58f51c9cd1b637739790 + checksum/config: a3168b9ac447a8852280ded74b420b5afa9cc2f6fca169e3e2da6e44b9e96428 labels: app: antrea component: antrea-agent @@ -7059,7 +7060,7 @@ spec: annotations: # Automatically restart Pod if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: e982ae7bedfa361f13e134516243f3c8d566b9297abc58f51c9cd1b637739790 + checksum/config: a3168b9ac447a8852280ded74b420b5afa9cc2f6fca169e3e2da6e44b9e96428 labels: app: antrea component: antrea-controller diff --git a/build/yamls/antrea-eks.yml b/build/yamls/antrea-eks.yml index c4b587323c5..7c337384344 100644 --- a/build/yamls/antrea-eks.yml +++ b/build/yamls/antrea-eks.yml @@ -5698,14 +5698,15 @@ data: # directed to that port will be forwarded to the Pod. portRange: "61000-62000" - # Provide the address of Kubernetes apiserver, to override any value provided in kubeconfig or InClusterConfig. - # It is typically used when kube-proxy is not deployed (replaced by AntreaProxy). + # Provide the address of Kubernetes apiserver, to override any value provided in kubeconfig or + # InClusterConfig. It is typically used when kube-proxy is not deployed (replaced by AntreaProxy). # Defaults to "". It must be a host string, a host:port pair, or a URL to the base of the apiserver. kubeAPIServerOverride: "" - # Provide the address of DNS server, to override the kube-dns service. It's used to resolve hostname in FQDN policy. - # Defaults to "". It must be a host string or a host:port pair of the DNS server (e.g. 10.96.0.10, 10.96.0.10:53, - # [fd00:10:96::a]:53). + # Provide the address of DNS server, to override the kube-dns Service. It's used to resolve + # hostnames in a FQDN policy. + # Defaults to "". It must be a host string or a host:port pair of the DNS server (e.g. 10.96.0.10, + # 10.96.0.10:53, [fd00:10:96::a]:53). dnsServerOverride: "" # Comma-separated list of Cipher Suites. If omitted, the default Go Cipher Suites will be used. @@ -6818,7 +6819,7 @@ spec: kubectl.kubernetes.io/default-container: antrea-agent # Automatically restart Pods with a RollingUpdate if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: e982ae7bedfa361f13e134516243f3c8d566b9297abc58f51c9cd1b637739790 + checksum/config: a3168b9ac447a8852280ded74b420b5afa9cc2f6fca169e3e2da6e44b9e96428 labels: app: antrea component: antrea-agent @@ -7060,7 +7061,7 @@ spec: annotations: # Automatically restart Pod if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: e982ae7bedfa361f13e134516243f3c8d566b9297abc58f51c9cd1b637739790 + checksum/config: a3168b9ac447a8852280ded74b420b5afa9cc2f6fca169e3e2da6e44b9e96428 labels: app: antrea component: antrea-controller diff --git a/build/yamls/antrea-gke.yml b/build/yamls/antrea-gke.yml index e7429fb773a..a3ebde5a915 100644 --- a/build/yamls/antrea-gke.yml +++ b/build/yamls/antrea-gke.yml @@ -5698,14 +5698,15 @@ data: # directed to that port will be forwarded to the Pod. portRange: "61000-62000" - # Provide the address of Kubernetes apiserver, to override any value provided in kubeconfig or InClusterConfig. - # It is typically used when kube-proxy is not deployed (replaced by AntreaProxy). + # Provide the address of Kubernetes apiserver, to override any value provided in kubeconfig or + # InClusterConfig. It is typically used when kube-proxy is not deployed (replaced by AntreaProxy). # Defaults to "". It must be a host string, a host:port pair, or a URL to the base of the apiserver. kubeAPIServerOverride: "" - # Provide the address of DNS server, to override the kube-dns service. It's used to resolve hostname in FQDN policy. - # Defaults to "". It must be a host string or a host:port pair of the DNS server (e.g. 10.96.0.10, 10.96.0.10:53, - # [fd00:10:96::a]:53). + # Provide the address of DNS server, to override the kube-dns Service. It's used to resolve + # hostnames in a FQDN policy. + # Defaults to "". It must be a host string or a host:port pair of the DNS server (e.g. 10.96.0.10, + # 10.96.0.10:53, [fd00:10:96::a]:53). dnsServerOverride: "" # Comma-separated list of Cipher Suites. If omitted, the default Go Cipher Suites will be used. @@ -6818,7 +6819,7 @@ spec: kubectl.kubernetes.io/default-container: antrea-agent # Automatically restart Pods with a RollingUpdate if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: 977cc5c6179f9ac01800457f4549f2783876dd94f2eaf165085808b742019cc1 + checksum/config: 1be8ab6f39c7b1d3742d49f9614a5fae317932ce2cc7b2473cc12a920f13641d labels: app: antrea component: antrea-agent @@ -7057,7 +7058,7 @@ spec: annotations: # Automatically restart Pod if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: 977cc5c6179f9ac01800457f4549f2783876dd94f2eaf165085808b742019cc1 + checksum/config: 1be8ab6f39c7b1d3742d49f9614a5fae317932ce2cc7b2473cc12a920f13641d labels: app: antrea component: antrea-controller diff --git a/build/yamls/antrea-ipsec.yml b/build/yamls/antrea-ipsec.yml index 11cfa843ba6..a432cd781df 100644 --- a/build/yamls/antrea-ipsec.yml +++ b/build/yamls/antrea-ipsec.yml @@ -5711,14 +5711,15 @@ data: # directed to that port will be forwarded to the Pod. portRange: "61000-62000" - # Provide the address of Kubernetes apiserver, to override any value provided in kubeconfig or InClusterConfig. - # It is typically used when kube-proxy is not deployed (replaced by AntreaProxy). + # Provide the address of Kubernetes apiserver, to override any value provided in kubeconfig or + # InClusterConfig. It is typically used when kube-proxy is not deployed (replaced by AntreaProxy). # Defaults to "". It must be a host string, a host:port pair, or a URL to the base of the apiserver. kubeAPIServerOverride: "" - # Provide the address of DNS server, to override the kube-dns service. It's used to resolve hostname in FQDN policy. - # Defaults to "". It must be a host string or a host:port pair of the DNS server (e.g. 10.96.0.10, 10.96.0.10:53, - # [fd00:10:96::a]:53). + # Provide the address of DNS server, to override the kube-dns Service. It's used to resolve + # hostnames in a FQDN policy. + # Defaults to "". It must be a host string or a host:port pair of the DNS server (e.g. 10.96.0.10, + # 10.96.0.10:53, [fd00:10:96::a]:53). dnsServerOverride: "" # Comma-separated list of Cipher Suites. If omitted, the default Go Cipher Suites will be used. @@ -6831,7 +6832,7 @@ spec: kubectl.kubernetes.io/default-container: antrea-agent # Automatically restart Pods with a RollingUpdate if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: 754b9e45b24d9a03a6be907d1dda1966a84598841e871bfa624932e11aeb739f + checksum/config: 9d2ce5aebdb9b1a668615a90578317745bafc6052b7c29431fd009a0ba65d62a checksum/ipsec-secret: d0eb9c52d0cd4311b6d252a951126bf9bea27ec05590bed8a394f0f792dcb2a4 labels: app: antrea @@ -7116,7 +7117,7 @@ spec: annotations: # Automatically restart Pod if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: 754b9e45b24d9a03a6be907d1dda1966a84598841e871bfa624932e11aeb739f + checksum/config: 9d2ce5aebdb9b1a668615a90578317745bafc6052b7c29431fd009a0ba65d62a labels: app: antrea component: antrea-controller diff --git a/build/yamls/antrea.yml b/build/yamls/antrea.yml index 9ca402b0088..228cc4c89b6 100644 --- a/build/yamls/antrea.yml +++ b/build/yamls/antrea.yml @@ -5698,14 +5698,15 @@ data: # directed to that port will be forwarded to the Pod. portRange: "61000-62000" - # Provide the address of Kubernetes apiserver, to override any value provided in kubeconfig or InClusterConfig. - # It is typically used when kube-proxy is not deployed (replaced by AntreaProxy). + # Provide the address of Kubernetes apiserver, to override any value provided in kubeconfig or + # InClusterConfig. It is typically used when kube-proxy is not deployed (replaced by AntreaProxy). # Defaults to "". It must be a host string, a host:port pair, or a URL to the base of the apiserver. kubeAPIServerOverride: "" - # Provide the address of DNS server, to override the kube-dns service. It's used to resolve hostname in FQDN policy. - # Defaults to "". It must be a host string or a host:port pair of the DNS server (e.g. 10.96.0.10, 10.96.0.10:53, - # [fd00:10:96::a]:53). + # Provide the address of DNS server, to override the kube-dns Service. It's used to resolve + # hostnames in a FQDN policy. + # Defaults to "". It must be a host string or a host:port pair of the DNS server (e.g. 10.96.0.10, + # 10.96.0.10:53, [fd00:10:96::a]:53). dnsServerOverride: "" # Comma-separated list of Cipher Suites. If omitted, the default Go Cipher Suites will be used. @@ -6818,7 +6819,7 @@ spec: kubectl.kubernetes.io/default-container: antrea-agent # Automatically restart Pods with a RollingUpdate if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: 7218db7d40a2ba0043ebc518f71ed1e5c38a715360a732492ae762964e11c884 + checksum/config: cc9b64c3b915bfd5a500d76f39a5b650f86819e144dbccea5019b4ecd4192292 labels: app: antrea component: antrea-agent @@ -7057,7 +7058,7 @@ spec: annotations: # Automatically restart Pod if the ConfigMap changes # See https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments - checksum/config: 7218db7d40a2ba0043ebc518f71ed1e5c38a715360a732492ae762964e11c884 + checksum/config: cc9b64c3b915bfd5a500d76f39a5b650f86819e144dbccea5019b4ecd4192292 labels: app: antrea component: antrea-controller diff --git a/cmd/antrea-agent/agent.go b/cmd/antrea-agent/agent.go index eddaa4bf81e..196eb8a417e 100644 --- a/cmd/antrea-agent/agent.go +++ b/cmd/antrea-agent/agent.go @@ -58,8 +58,8 @@ import ( proxytypes "antrea.io/antrea/pkg/agent/proxy/types" "antrea.io/antrea/pkg/agent/querier" "antrea.io/antrea/pkg/agent/route" + "antrea.io/antrea/pkg/agent/secondarynetwork" "antrea.io/antrea/pkg/agent/secondarynetwork/cnipodcache" - "antrea.io/antrea/pkg/agent/secondarynetwork/podwatch" "antrea.io/antrea/pkg/agent/servicecidr" "antrea.io/antrea/pkg/agent/stats" support "antrea.io/antrea/pkg/agent/supportbundlecollection" @@ -691,21 +691,15 @@ func run(o *Options) error { } if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) { - // Create the NetworkAttachmentDefinition client, which handles access to secondary network object definition from the API Server. - netAttachDefClient, err := k8s.CreateNetworkAttachDefClient(o.config.ClientConnection, o.config.KubeAPIServerOverride) - if err != nil { - return fmt.Errorf("NetworkAttachmentDefinition client creation failed. %v", err) - } - // Create podController to handle secondary network configuration for Pods with k8s.v1.cni.cncf.io/networks Annotation defined. - podWatchController := podwatch.NewPodController( - k8sClient, - netAttachDefClient, - localPodInformer, - nodeConfig.Name, - cniPodInfoStore, + if err := secondarynetwork.Initialize( + o.config.ClientConnection, o.config.KubeAPIServerOverride, + k8sClient, localPodInformer, nodeConfig.Name, cniPodInfoStore, // safe to call given that cniServer.Initialize has been called already. - cniServer.GetPodConfigurator()) - go podWatchController.Run(stopCh) + cniServer.GetPodConfigurator(), + stopCh, + &o.config.SecondaryNetwork, ovsdbConnection); err != nil { + return fmt.Errorf("failed to initialize secondary network: %v", err) + } } if features.DefaultFeatureGate.Enabled(features.TrafficControl) { diff --git a/cmd/antrea-agent/options.go b/cmd/antrea-agent/options.go index 1ab8c08b049..f106cd73a03 100644 --- a/cmd/antrea-agent/options.go +++ b/cmd/antrea-agent/options.go @@ -620,6 +620,10 @@ func (o *Options) validateK8sNodeOptions() error { o.dnsServerOverride = hostPort } + if err := o.validateSecondaryNetworkConfig(); err != nil { + return fmt.Errorf("failed to validate secondary network config: %v", err) + } + return nil } @@ -707,3 +711,25 @@ func (o *Options) setMulticlusterDefaultOptions() { } } } + +func (o *Options) validateSecondaryNetworkConfig() error { + if !features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) { + return nil + } + + if len(o.config.SecondaryNetwork.OVSBridges) == 0 { + return nil + } + if len(o.config.SecondaryNetwork.OVSBridges) > 1 { + return fmt.Errorf("only one OVS bridge can be specified for secondary network") + } + brConfig := o.config.SecondaryNetwork.OVSBridges[0] + if brConfig.BridgeName == "" { + return fmt.Errorf("bridge name is not provided for the secondary network OVS bridge") + } + if len(brConfig.PhysicalInterfaces) > 1 { + return fmt.Errorf("at most one physical interface can be specified for the secondary network OVS bridge") + } + + return nil +} diff --git a/cmd/antrea-agent/options_test.go b/cmd/antrea-agent/options_test.go index e41bd0acede..421dd1a6fa2 100644 --- a/cmd/antrea-agent/options_test.go +++ b/cmd/antrea-agent/options_test.go @@ -242,3 +242,71 @@ func TestOptionsValidateMulticastConfig(t *testing.T) { }) } } + +func TestOptionsValidateSecondaryNetworkConfig(t *testing.T) { + tests := []struct { + name string + featureGateValue bool + ovsBridges []string + physicalInterfaces []string + expectedErr string + }{ + { + name: "featureGate off", + ovsBridges: []string{"br1"}, + }, + { + name: "no bridge", + featureGateValue: true, + }, + { + name: "one bridge", + featureGateValue: true, + ovsBridges: []string{"br1"}, + }, + { + name: "one interface", + featureGateValue: true, + ovsBridges: []string{"br1"}, + physicalInterfaces: []string{"eth1"}, + }, + { + name: "two bridges", + featureGateValue: true, + ovsBridges: []string{"br1", "br2"}, + expectedErr: "only one OVS bridge can be specified for secondary network", + }, + { + name: "no bridge name", + featureGateValue: true, + ovsBridges: []string{""}, + expectedErr: "bridge name is not provided for the secondary network OVS bridge", + }, + { + name: "two interfaces", + featureGateValue: true, + ovsBridges: []string{"br1"}, + physicalInterfaces: []string{"eth1", "eth2"}, + expectedErr: "at most one physical interface can be specified for the secondary network OVS bridge", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, features.DefaultFeatureGate, features.SecondaryNetwork, tc.featureGateValue)() + + o := &Options{config: &agentconfig.AgentConfig{}} + for _, brName := range tc.ovsBridges { + br := agentconfig.OVSBridgeConfig{BridgeName: brName} + br.PhysicalInterfaces = tc.physicalInterfaces + o.config.SecondaryNetwork.OVSBridges = append(o.config.SecondaryNetwork.OVSBridges, br) + } + + err := o.validateSecondaryNetworkConfig() + if tc.expectedErr == "" { + require.NoError(t, err) + } else { + require.Error(t, err, tc.expectedErr) + } + }) + } +} diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index f0799fe43d5..32b43e8cba6 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -78,8 +78,8 @@ var ( // getIPNetDeviceByV4CIDR is meant to be overridden for testing. getIPNetDeviceByCIDRs = util.GetIPNetDeviceByCIDRs - // getTransportIPNetDeviceByName is meant to be overridden for testing. - getTransportIPNetDeviceByName = GetTransportIPNetDeviceByName + // getTransportIPNetDeviceByNameFn is meant to be overridden for testing. + getTransportIPNetDeviceByNameFn = getTransportIPNetDeviceByName // setLinkUp is meant to be overridden for testing setLinkUp = util.SetLinkUp @@ -954,7 +954,7 @@ func (i *Initializer) initK8sNodeLocalConfig(nodeName string) error { transportInterface = nodeInterface if i.networkConfig.TransportIface != "" { // Find the configured transport interface, and update its IP address in Node's annotation. - transportIPv4Addr, transportIPv6Addr, transportInterface, err = getTransportIPNetDeviceByName(i.networkConfig.TransportIface, i.ovsBridge) + transportIPv4Addr, transportIPv6Addr, transportInterface, err = getTransportIPNetDeviceByNameFn(i.networkConfig.TransportIface, i.ovsBridge) if err != nil { return fmt.Errorf("failed to get local IPNet device with transport interface %s: %v", i.networkConfig.TransportIface, err) } @@ -1347,8 +1347,7 @@ func (i *Initializer) setOVSDatapath() error { if _, exists := otherConfig[ovsconfig.OVSOtherConfigDatapathIDKey]; exists { return nil } - randMAC := util.GenerateRandomMAC() - datapathID := "0000" + strings.Replace(randMAC.String(), ":", "", -1) + datapathID := util.GenerateOVSDatapathID("") if err := i.ovsBridgeClient.SetDatapathID(datapathID); err != nil { klog.ErrorS(err, "Failed to set OVS bridge datapath_id", "datapathID", datapathID) return err diff --git a/pkg/agent/agent_linux.go b/pkg/agent/agent_linux.go index eebc2851f3a..a797c21349f 100644 --- a/pkg/agent/agent_linux.go +++ b/pkg/agent/agent_linux.go @@ -20,7 +20,6 @@ package agent import ( "fmt" "net" - "strings" "time" "github.com/vishvananda/netlink" @@ -75,10 +74,8 @@ func (i *Initializer) prepareOVSBridgeForK8sNode() error { // Set datapathID of OVS bridge. // If no datapathID configured explicitly, the reconfiguration operation will change OVS bridge datapathID // and break the OpenFlow channel. - // The length of datapathID is 64 bits, the lower 48-bits are for a MAC address, while the upper 16-bits are - // implementer-defined. Antrea uses "0x0000" for the upper 16-bits. - datapathID := strings.Replace(uplinkNetConfig.MAC.String(), ":", "", -1) - datapathID = "0000" + datapathID + datapathID := util.GenerateOVSDatapathID(uplinkNetConfig.MAC.String()) + if err = i.ovsBridgeClient.SetDatapathID(datapathID); err != nil { return fmt.Errorf("failed to set datapath_id %s: err=%w", datapathID, err) } @@ -123,14 +120,14 @@ func (i *Initializer) getTunnelPortLocalIP() net.IP { return nil } -func GetTransportIPNetDeviceByName(ifaceName string, ovsBridgeName string) (*net.IPNet, *net.IPNet, *net.Interface, error) { +func getTransportIPNetDeviceByName(ifaceName string, ovsBridgeName string) (*net.IPNet, *net.IPNet, *net.Interface, error) { return util.GetIPNetDeviceByName(ifaceName) } -// saveHostRoutes saves routes which are configured on uplink interface before -// the interface the configured as the uplink of antrea network. -// The routes will be restored on OVS bridge interface after the IP configuration -// is moved to the OVS bridge. +// saveHostRoutes saves the routes which were configured on the uplink interface +// before the interface is configured as the OVS brdige uplink. These routes +// will be moved to the bridge interface together with the interface IP +// configuration. func (i *Initializer) saveHostRoutes() error { routes, err := netlink.RouteList(nil, netlink.FAMILY_V4) if err != nil { @@ -155,8 +152,8 @@ func (i *Initializer) saveHostRoutes() error { // restoreHostRoutes restores the host routes which are lost when moving the IP // configuration of uplink interface to the OVS bridge interface during -// the antrea network initialize stage. -// The backup routes are restored after the IP configuration change. +// the Antrea bridge initialization stage. +// The backup routes are restored after the IP configuration changes. func (i *Initializer) restoreHostRoutes() error { return i.restoreHostRoutesToInterface(i.nodeConfig.UplinkNetConfig.Name) } @@ -188,7 +185,7 @@ func (i *Initializer) ConnectUplinkToOVSBridge() error { uplinkName := uplinkNetConfig.Name bridgedUplinkName := util.GenerateUplinkInterfaceName(uplinkNetConfig.Name) - // If uplink is already exists, return. + // If the uplink port already exists, just return. if uplinkOFPort, err := i.ovsBridgeClient.GetOFPort(bridgedUplinkName, false); err == nil { klog.InfoS("Uplink already exists, skip the configuration", "uplink", bridgedUplinkName, "port", uplinkOFPort) return nil diff --git a/pkg/agent/agent_test.go b/pkg/agent/agent_test.go index e8d8e228d32..aa63173faa4 100644 --- a/pkg/agent/agent_test.go +++ b/pkg/agent/agent_test.go @@ -489,11 +489,11 @@ func mockGetNodeTimeout(timeout time.Duration) func() { } func mockGetTransportIPNetDeviceByName(ipV4Net, ipV6Net *net.IPNet, ipDevice *net.Interface) func() { - prevGetIPNetDeviceByName := getTransportIPNetDeviceByName - getTransportIPNetDeviceByName = func(ifName, brName string) (*net.IPNet, *net.IPNet, *net.Interface, error) { + prevGetIPNetDeviceByName := getTransportIPNetDeviceByNameFn + getTransportIPNetDeviceByNameFn = func(ifName, brName string) (*net.IPNet, *net.IPNet, *net.Interface, error) { return ipV4Net, ipV6Net, ipDevice, nil } - return func() { getTransportIPNetDeviceByName = prevGetIPNetDeviceByName } + return func() { getTransportIPNetDeviceByNameFn = prevGetIPNetDeviceByName } } func mockGetIPNetDeviceByCIDRs(ipV4Net, ipV6Net *net.IPNet, ipDevice *net.Interface) func() { diff --git a/pkg/agent/agent_windows.go b/pkg/agent/agent_windows.go index 00034fe18cb..1b80e6d848a 100644 --- a/pkg/agent/agent_windows.go +++ b/pkg/agent/agent_windows.go @@ -213,10 +213,7 @@ func (i *Initializer) prepareOVSBridgeOnHNSNetwork() error { // Set datapathID of OVS bridge. // If no datapathID configured explicitly, the reconfiguration operation will change OVS bridge datapathID // and break the OpenFlow channel. - // The length of datapathID is 64 bits, the lower 48-bits are for a MAC address, while the upper 16-bits are - // implementer-defined. Antrea uses "0x0000" for the upper 16-bits. - datapathID := strings.Replace(hnsNetwork.SourceMac, ":", "", -1) - datapathID = "0000" + datapathID + datapathID := util.GenerateOVSDatapathID(hnsNetwork.SourceMac) if err = i.ovsBridgeClient.SetDatapathID(datapathID); err != nil { klog.ErrorS(err, "Failed to set OVS bridge datapath_id", "datapathID", datapathID) return err @@ -364,10 +361,10 @@ func (i *Initializer) getTunnelPortLocalIP() net.IP { return i.nodeConfig.NodeTransportIPv4Addr.IP } -// saveHostRoutes saves routes which are configured on uplink interface before -// the interface the configured as the uplink of antrea HNS network. -// The routes will be restored on OVS bridge interface after the IP configuration -// is moved to the OVS bridge. +// saveHostRoutes saves routes configured on the uplink interface before the +// interface is configured as the uplink of Antrea HNS network. +// The routes will be restored on the OVS bridge interface after the IP +// configuration is moved to the OVS bridge. func (i *Initializer) saveHostRoutes() error { routes, err := util.GetNetRoutesAll() if err != nil { @@ -395,7 +392,7 @@ func (i *Initializer) saveHostRoutes() error { return nil } -func GetTransportIPNetDeviceByName(ifaceName string, ovsBridgeName string) (*net.IPNet, *net.IPNet, *net.Interface, error) { +func getTransportIPNetDeviceByName(ifaceName string, ovsBridgeName string) (*net.IPNet, *net.IPNet, *net.Interface, error) { // Find transport Interface in the order: ifaceName -> br-int. Return immediately if // an interface using the specified name exists. Using br-int is for restart agent case. for _, name := range []string{ifaceName, ovsBridgeName} { diff --git a/pkg/agent/secondarynetwork/init.go b/pkg/agent/secondarynetwork/init.go new file mode 100644 index 00000000000..c943f7de40b --- /dev/null +++ b/pkg/agent/secondarynetwork/init.go @@ -0,0 +1,125 @@ +// Copyright 2023 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 secondarynetwork + +import ( + "fmt" + "net" + + "github.com/TomCodeLV/OVSDB-golang-lib/pkg/ovsdb" + netdefclient "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned/typed/k8s.cni.cncf.io/v1" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + componentbaseconfig "k8s.io/component-base/config" + "k8s.io/klog/v2" + + "antrea.io/antrea/pkg/agent/interfacestore" + "antrea.io/antrea/pkg/agent/secondarynetwork/cnipodcache" + "antrea.io/antrea/pkg/agent/secondarynetwork/podwatch" + agentconfig "antrea.io/antrea/pkg/config/agent" + "antrea.io/antrea/pkg/ovs/ovsconfig" + "antrea.io/antrea/pkg/util/k8s" +) + +var ( + // Funcs which will be orridden with mock funcs in tests. + interfaceByNameFn = net.InterfaceByName + newOVSBridgeFn = ovsconfig.NewOVSBridge +) + +// Initialize sets up OVS bridges and starts the Pod controller for secondary networks. +func Initialize( + clientConnectionConfig componentbaseconfig.ClientConnectionConfiguration, + kubeAPIServerOverride string, + k8sClient clientset.Interface, + podInformer cache.SharedIndexInformer, + nodeName string, + podCache cnipodcache.CNIPodInfoStore, + interfaceConfigurator podwatch.InterfaceConfigurator, + stopCh <-chan struct{}, + config *agentconfig.SecondaryNetworkConfig, ovsdb *ovsdb.OVSDB) error { + if err := createOVSBridge(config.OVSBridges, ovsdb); err != nil { + return err + } + + // Create the NetworkAttachmentDefinition client, which handles access to secondary network object + // definition from the API Server. + netAttachDefClient, err := createNetworkAttachDefClient(clientConnectionConfig, kubeAPIServerOverride) + if err != nil { + return fmt.Errorf("NetworkAttachmentDefinition client creation failed: %v", err) + } + + // Create podController to handle secondary network configuration for Pods with + // k8s.v1.cni.cncf.io/networks Annotation defined. + podWatchController := podwatch.NewPodController( + k8sClient, + netAttachDefClient, + podInformer, + nodeName, + podCache, + interfaceConfigurator) + go podWatchController.Run(stopCh) + return nil +} + +// TODO: check and update bridge configuration. +func createOVSBridge(bridges []agentconfig.OVSBridgeConfig, ovsdb *ovsdb.OVSDB) error { + if len(bridges) == 0 { + return nil + } + // Only one OVS bridge is supported. + bridgeConfig := bridges[0] + + ovsBridgeClient := newOVSBridgeFn(bridgeConfig.BridgeName, ovsconfig.OVSDatapathSystem, ovsdb) + if err := ovsBridgeClient.Create(); err != nil { + return fmt.Errorf("failed to create OVS bridge %s: %v", bridgeConfig.BridgeName, err) + } + klog.InfoS("OVS bridge created", "bridge", bridgeConfig.BridgeName) + + if len(bridgeConfig.PhysicalInterfaces) == 0 { + return nil + } + phyInterface := bridgeConfig.PhysicalInterfaces[0] + if _, err := interfaceByNameFn(phyInterface); err != nil { + return fmt.Errorf("failed to get interface %s: %v", phyInterface, err) + } + + if _, err := ovsBridgeClient.GetOFPort(phyInterface, false); err == nil { + klog.V(2).InfoS("Physical interface already connected to OVS bridge, skip the configuration", "device", phyInterface, "bridge", bridgeConfig.BridgeName) + return nil + } + + _, err := ovsBridgeClient.CreateUplinkPort(phyInterface, 0, map[string]interface{}{interfacestore.AntreaInterfaceTypeKey: interfacestore.AntreaUplink}) + if err != nil { + return fmt.Errorf("failed to create OVS uplink port %s: %v", phyInterface, err) + } + klog.InfoS("Physical interface added to OVS bridge", "device", phyInterface, "bridge", bridgeConfig.BridgeName) + + return nil +} + +// CreateNetworkAttachDefClient creates net-attach-def client handle from the given config. +func createNetworkAttachDefClient(config componentbaseconfig.ClientConnectionConfiguration, kubeAPIServerOverride string) (netdefclient.K8sCniCncfIoV1Interface, error) { + kubeConfig, err := k8s.CreateRestConfig(config, kubeAPIServerOverride) + if err != nil { + return nil, err + } + + netAttachDefClient, err := netdefclient.NewForConfig(kubeConfig) + if err != nil { + return nil, err + } + return netAttachDefClient, nil +} diff --git a/pkg/agent/secondarynetwork/init_test.go b/pkg/agent/secondarynetwork/init_test.go new file mode 100644 index 00000000000..b9538e055dc --- /dev/null +++ b/pkg/agent/secondarynetwork/init_test.go @@ -0,0 +1,162 @@ +// Copyright 2023 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 secondarynetwork + +import ( + "errors" + "net" + "testing" + + "github.com/TomCodeLV/OVSDB-golang-lib/pkg/ovsdb" + mock "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + agentconfig "antrea.io/antrea/pkg/config/agent" + "antrea.io/antrea/pkg/ovs/ovsconfig" + ovsconfigtest "antrea.io/antrea/pkg/ovs/ovsconfig/testing" +) + +const nonExistingInterface = "non-existing" + +func TestCreateOVSBridge(t *testing.T) { + tests := []struct { + name string + ovsBridges []string + physicalInterfaces []string + expectedErr string + expectedCalls func(m *ovsconfigtest.MockOVSBridgeClient) + }{ + { + name: "no bridge", + }, + { + name: "no interface", + ovsBridges: []string{"br1"}, + expectedCalls: func(m *ovsconfigtest.MockOVSBridgeClient) { + m.EXPECT().Create().Return(nil) + }, + }, + { + name: "two bridges", + ovsBridges: []string{"br1", "br2"}, + expectedCalls: func(m *ovsconfigtest.MockOVSBridgeClient) { + m.EXPECT().Create().Return(nil) + }, + }, + { + name: "create br error", + ovsBridges: []string{"br1", "br2"}, + expectedErr: "create error", + expectedCalls: func(m *ovsconfigtest.MockOVSBridgeClient) { + m.EXPECT().Create().Return(ovsconfig.InvalidArgumentsError("create error")) + }, + }, + { + name: "one interface", + ovsBridges: []string{"br1"}, + physicalInterfaces: []string{"eth1"}, + expectedCalls: func(m *ovsconfigtest.MockOVSBridgeClient) { + m.EXPECT().Create().Return(nil) + m.EXPECT().GetOFPort("eth1", false).Return(int32(0), ovsconfig.InvalidArgumentsError("port not found")) + m.EXPECT().CreateUplinkPort("eth1", int32(0), map[string]interface{}{"antrea-type": "uplink"}).Return("", nil) + }, + }, + { + name: "two interfaces", + ovsBridges: []string{"br1", "br2"}, + physicalInterfaces: []string{"eth1", "eth2"}, + expectedCalls: func(m *ovsconfigtest.MockOVSBridgeClient) { + m.EXPECT().Create().Return(nil) + m.EXPECT().GetOFPort("eth1", false).Return(int32(0), ovsconfig.InvalidArgumentsError("port not found")) + m.EXPECT().CreateUplinkPort("eth1", int32(0), map[string]interface{}{"antrea-type": "uplink"}).Return("", nil) + }, + }, + { + name: "interface already attached", + ovsBridges: []string{"br1"}, + physicalInterfaces: []string{"eth1"}, + expectedCalls: func(m *ovsconfigtest.MockOVSBridgeClient) { + m.EXPECT().Create().Return(nil) + m.EXPECT().GetOFPort("eth1", false).Return(int32(0), nil) + }, + }, + { + name: "non-existing interface", + ovsBridges: []string{"br1"}, + physicalInterfaces: []string{nonExistingInterface, "eth2"}, + expectedErr: "failed to get interface", + expectedCalls: func(m *ovsconfigtest.MockOVSBridgeClient) { + m.EXPECT().Create().Return(nil) + }, + }, + { + name: "create port error", + ovsBridges: []string{"br1"}, + physicalInterfaces: []string{"eth1"}, + expectedErr: "create error", + expectedCalls: func(m *ovsconfigtest.MockOVSBridgeClient) { + m.EXPECT().Create().Return(nil) + m.EXPECT().GetOFPort("eth1", false).Return(int32(0), ovsconfig.InvalidArgumentsError("port not found")) + m.EXPECT().CreateUplinkPort("eth1", int32(0), map[string]interface{}{"antrea-type": "uplink"}).Return("", ovsconfig.InvalidArgumentsError("create error")) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var bridges []agentconfig.OVSBridgeConfig + for _, brName := range tc.ovsBridges { + br := agentconfig.OVSBridgeConfig{BridgeName: brName} + br.PhysicalInterfaces = tc.physicalInterfaces + bridges = append(bridges, br) + } + + controller := mock.NewController(t) + mockOVSBridgeClient := ovsconfigtest.NewMockOVSBridgeClient(controller) + + mockNewOVSBridge(t, mockOVSBridgeClient) + mockInterfaceByName(t) + if tc.expectedCalls != nil { + tc.expectedCalls(mockOVSBridgeClient) + } + + err := createOVSBridge(bridges, nil) + if tc.expectedErr != "" { + assert.ErrorContains(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + } + }) + } +} + +func mockInterfaceByName(t *testing.T) { + prevFunc := interfaceByNameFn + interfaceByNameFn = func(name string) (*net.Interface, error) { + if name == nonExistingInterface { + return nil, errors.New("interface not found") + } + return nil, nil + } + t.Cleanup(func() { interfaceByNameFn = prevFunc }) +} + +func mockNewOVSBridge(t *testing.T, brClient ovsconfig.OVSBridgeClient) { + prevFunc := newOVSBridgeFn + newOVSBridgeFn = func(bridgeName string, ovsDatapathType ovsconfig.OVSDatapathType, ovsdb *ovsdb.OVSDB) ovsconfig.OVSBridgeClient { + return brClient + } + t.Cleanup(func() { newOVSBridgeFn = prevFunc }) +} diff --git a/pkg/agent/util/net.go b/pkg/agent/util/net.go index 1f5b96cedac..7e08780c104 100644 --- a/pkg/agent/util/net.go +++ b/pkg/agent/util/net.go @@ -436,3 +436,13 @@ func GetIPNetsByLink(link *net.Interface) ([]*net.IPNet, error) { } return addrs, nil } + +// GenerateOVSDatapathID generates an OVS datapath ID string. +func GenerateOVSDatapathID(macString string) string { + // The length of datapathID is 64 bits, the lower 48-bits are for a MAC address, while the + // upper 16-bits are implementer-defined. Antrea uses "0x0000" for the upper 16-bits. + if macString == "" { + macString = GenerateRandomMAC().String() + } + return "0000" + strings.Replace(macString, ":", "", -1) +} diff --git a/pkg/agent/util/net_test.go b/pkg/agent/util/net_test.go index c4d51896509..42bc4187e01 100644 --- a/pkg/agent/util/net_test.go +++ b/pkg/agent/util/net_test.go @@ -459,6 +459,34 @@ func TestGetIPNetsByLink(t *testing.T) { } } +func TestGenerateOVSDatapathID(t *testing.T) { + tests := []struct { + name string + mac net.HardwareAddr + expectedID string + }{ + { + name: "valid MAC", + mac: net.HardwareAddr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}, + expectedID: "001122334455", + }, + { + name: "empty MAC", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + id := GenerateOVSDatapathID(tc.mac.String()) + if tc.expectedID != "" { + assert.Equal(t, "0000"+tc.expectedID, id) + } else { + assert.Equal(t, 16, len(id)) + assert.True(t, strings.HasPrefix(id, "0000")) + } + }) + } +} + func generateNetInterfaceAddrs(idx int) []net.Addr { netAddrsIPv4 := []net.Addr{&ipv4PublicIPNet} netAddrsIPv6 := []net.Addr{ diff --git a/pkg/config/agent/config.go b/pkg/config/agent/config.go index 952674c01fe..76ec4c33e1c 100644 --- a/pkg/config/agent/config.go +++ b/pkg/config/agent/config.go @@ -147,13 +147,14 @@ type AgentConfig struct { NodePortLocal NodePortLocalConfig `yaml:"nodePortLocal,omitempty"` // FlowExporter configuration options. FlowExporter FlowExporterConfig `yaml:"flowExporter,omitempty"` - // Provide the address of Kubernetes apiserver, to override any value provided in kubeconfig or InClusterConfig. - // It is typically used when kube-proxy is not deployed (replaced by AntreaProxy). + // Provide the address of Kubernetes apiserver, to override any value provided in kubeconfig or + // InClusterConfig. It is typically used when kube-proxy is not deployed (replaced by AntreaProxy). // Defaults to "". It must be a host string, a host:port pair, or a URL to the base of the apiserver. KubeAPIServerOverride string `yaml:"kubeAPIServerOverride,omitempty"` - // Provide the address of DNS server, to override the kube-dns service. It's used to resolve hostname in FQDN policy. - // Defaults to "". It must be a host string or a host:port pair of the DNS server (e.g. 10.96.0.10, 10.96.0.10:53, - // [fd00:10:96::a]:53). + // Provide the address of DNS server, to override the kube-dns Service. It's used to resolve + // hostnames in a FQDN policy. + // Defaults to "". It must be a host string or a host:port pair of the DNS server (e.g. 10.96.0.10, + // 10.96.0.10:53, [fd00:10:96::a]:53). DNSServerOverride string `yaml:"dnsServerOverride,omitempty"` // Cipher suites to use. TLSCipherSuites string `yaml:"tlsCipherSuites,omitempty"` @@ -196,6 +197,8 @@ type AgentConfig struct { ExternalNode ExternalNodeConfig `yaml:"externalNode,omitempty"` // AuditLogging supports configuring log rotation for audit logs. AuditLogging AuditLoggingConfig `yaml:"auditLogging,omitempty"` + // Antrea's native secondary network configuration. + SecondaryNetwork SecondaryNetworkConfig `yaml:"secondaryNetwork,omitempty"` } type AntreaProxyConfig struct { @@ -381,3 +384,16 @@ type AuditLoggingConfig struct { // Compress enables gzip compression on rotated files. Defaults to true. Compress *bool `yaml:"compress,omitempty"` } + +type SecondaryNetworkConfig struct { + // Configuration of OVS bridges for secondary networks. At the moment, only a + // single OVS bridge is supported. + OVSBridges []OVSBridgeConfig `yaml:"ovsBridges,omitempty"` +} + +type OVSBridgeConfig struct { + BridgeName string `yaml:"bridgeName"` + // Names of physical interfaces to be connected to the bridge. At the moment, + // only a single physical interface is supported. + PhysicalInterfaces []string `yaml:"physicalInterfaces,omitempty"` +} diff --git a/pkg/ovs/ovsconfig/ovs_client.go b/pkg/ovs/ovsconfig/ovs_client.go index 72a0760b67e..b243e656011 100644 --- a/pkg/ovs/ovsconfig/ovs_client.go +++ b/pkg/ovs/ovsconfig/ovs_client.go @@ -98,7 +98,7 @@ func NewOVSDBConnectionUDS(address string) (*ovsdb.OVSDB, Error) { } // NewOVSBridge creates and returns a new OVSBridge struct. -func NewOVSBridge(bridgeName string, ovsDatapathType OVSDatapathType, ovsdb *ovsdb.OVSDB) *OVSBridge { +func NewOVSBridge(bridgeName string, ovsDatapathType OVSDatapathType, ovsdb *ovsdb.OVSDB) OVSBridgeClient { return &OVSBridge{ovsdb, bridgeName, ovsDatapathType, "", false, []int32{}} } diff --git a/pkg/util/k8s/client.go b/pkg/util/k8s/client.go index 0fd36629436..7ab555f170b 100644 --- a/pkg/util/k8s/client.go +++ b/pkg/util/k8s/client.go @@ -20,7 +20,6 @@ import ( "os" "strings" - netdefclient "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned/typed/k8s.cni.cncf.io/v1" discovery "k8s.io/api/discovery/v1" apiextensionclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/apimachinery/pkg/api/errors" @@ -44,7 +43,7 @@ const ( // CreateClients creates kube clients from the given config. func CreateClients(config componentbaseconfig.ClientConnectionConfiguration, kubeAPIServerOverride string) ( clientset.Interface, aggregatorclientset.Interface, crdclientset.Interface, apiextensionclientset.Interface, mcclientset.Interface, policyclient.Interface, error) { - kubeConfig, err := createRestConfig(config, kubeAPIServerOverride) + kubeConfig, err := CreateRestConfig(config, kubeAPIServerOverride) if err != nil { return nil, nil, nil, nil, nil, nil, err } @@ -82,21 +81,7 @@ func CreateClients(config componentbaseconfig.ClientConnectionConfiguration, kub return client, aggregatorClient, crdClient, apiExtensionClient, mcClient, policyClient, nil } -// CreateNetworkAttachDefClient creates net-attach-def client handle from the given config. -func CreateNetworkAttachDefClient(config componentbaseconfig.ClientConnectionConfiguration, kubeAPIServerOverride string) (netdefclient.K8sCniCncfIoV1Interface, error) { - kubeConfig, err := createRestConfig(config, kubeAPIServerOverride) - if err != nil { - return nil, err - } - - netAttachDefClient, err := netdefclient.NewForConfig(kubeConfig) - if err != nil { - return nil, err - } - return netAttachDefClient, nil -} - -func createRestConfig(config componentbaseconfig.ClientConnectionConfiguration, kubeAPIServerOverride string) (*rest.Config, error) { +func CreateRestConfig(config componentbaseconfig.ClientConnectionConfiguration, kubeAPIServerOverride string) (*rest.Config, error) { var kubeConfig *rest.Config var err error diff --git a/test/integration/ovs/ovs_client_test.go b/test/integration/ovs/ovs_client_test.go index 9907a91a533..e07c718d397 100644 --- a/test/integration/ovs/ovs_client_test.go +++ b/test/integration/ovs/ovs_client_test.go @@ -63,7 +63,8 @@ func (data *testData) setup(t *testing.T) { // using the netdev datapath type does not impact test coverage but // ensures that the integration tests can be run with Docker Desktop on // macOS. - data.br = ovsconfig.NewOVSBridge(bridgeName, "netdev", data.ovsdb) + brClient := ovsconfig.NewOVSBridge(bridgeName, "netdev", data.ovsdb) + data.br = brClient.(*ovsconfig.OVSBridge) err = data.br.Create() require.Nil(t, err, "Failed to create bridge %s", bridgeName) }