From e16c364eeec02b89d6f1a804360779eee5fc7fe0 Mon Sep 17 00:00:00 2001 From: chengzw <40051120+cr7258@users.noreply.github.com> Date: Thu, 21 Sep 2023 06:25:19 +0800 Subject: [PATCH] Improve antctl get featuregates output (#5314) Fixes #5173 Here are the major changes in this PR: 1. Keep the information about which component is using each FeatureGate in antrea_features.go. We use this information to ensure correctness when retrieving Feature Gate values: no FeatureGate should be missing, FeatureGates should be displayed for the relevant component only. 2. Add support for antrea-agent-windows. Signed-off-by: chengzw Signed-off-by: Antonin Bas Co-authored-by: Antonin Bas --- .../handlers/featuregates/handler.go | 35 +- .../handlers/featuregates/handler_test.go | 54 ++ pkg/antctl/raw/featuregates/command.go | 39 +- pkg/antctl/raw/featuregates/command_test.go | 665 ++++++++++++++++-- .../handlers/featuregates/handler.go | 125 ++-- .../handlers/featuregates/handler_test.go | 157 +++-- pkg/features/antrea_features.go | 45 ++ pkg/features/antrea_features_test.go | 8 + 8 files changed, 936 insertions(+), 192 deletions(-) create mode 100644 pkg/agent/apiserver/handlers/featuregates/handler_test.go diff --git a/pkg/agent/apiserver/handlers/featuregates/handler.go b/pkg/agent/apiserver/handlers/featuregates/handler.go index a5fb75a4ac5..f905864df82 100644 --- a/pkg/agent/apiserver/handlers/featuregates/handler.go +++ b/pkg/agent/apiserver/handlers/featuregates/handler.go @@ -17,6 +17,7 @@ package featuregates import ( "encoding/json" "net/http" + "sort" "k8s.io/klog/v2" @@ -32,31 +33,33 @@ type Response struct { // HandleFunc returns the function which can handle queries issued by 'antctl get featuregates' command. // The handler function populates Antrea Agent feature gates information to the response. -// For now, it will return all feature gates included in features.DefaultAntreaFeatureGates for agent -// We need to exclude any new feature gates which is not consumed by agent in the future. func HandleFunc() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var featureGates []Response - status, version := "", "" for df := range features.DefaultAntreaFeatureGates { - if features.DefaultMutableFeatureGate.Enabled(df) { - status = "Enabled" - } else { - status = "Disabled" + if features.AgentGates.Has(df) { + featureGates = append(featureGates, Response{ + Component: "agent", + Name: string(df), + Status: getStatus(features.DefaultFeatureGate.Enabled(df)), + Version: string(features.DefaultAntreaFeatureGates[df].PreRelease), + }) } - version = string(features.DefaultAntreaFeatureGates[df].PreRelease) - featureGates = append(featureGates, Response{ - Component: "agent", - Name: string(df), - Status: status, - Version: version, - }) } - + sort.Slice(featureGates, func(i, j int) bool { + return featureGates[i].Name < featureGates[j].Name + }) err := json.NewEncoder(w).Encode(featureGates) if err != nil { w.WriteHeader(http.StatusInternalServerError) - klog.Errorf("Error when encoding FeatureGates to json: %v", err) + klog.ErrorS(err, "Error when encoding FeatureGates to json") } } } + +func getStatus(status bool) string { + if status { + return "Enabled" + } + return "Disabled" +} diff --git a/pkg/agent/apiserver/handlers/featuregates/handler_test.go b/pkg/agent/apiserver/handlers/featuregates/handler_test.go new file mode 100644 index 00000000000..7abb692d182 --- /dev/null +++ b/pkg/agent/apiserver/handlers/featuregates/handler_test.go @@ -0,0 +1,54 @@ +// 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 featuregates + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/component-base/featuregate" + + "antrea.io/antrea/pkg/features" +) + +func TestGetStatus(t *testing.T) { + assert.Equal(t, "Enabled", getStatus(true)) + assert.Equal(t, "Disabled", getStatus(false)) +} + +func TestHandleFunc(t *testing.T) { + handler := HandleFunc() + req, err := http.NewRequest(http.MethodGet, "", nil) + require.Nil(t, err) + + recorder := httptest.NewRecorder() + handler.ServeHTTP(recorder, req) + require.Equal(t, http.StatusOK, recorder.Code) + + var resp []Response + err = json.Unmarshal(recorder.Body.Bytes(), &resp) + require.Nil(t, err) + + for _, v := range resp { + df, ok := features.DefaultAntreaFeatureGates[featuregate.Feature(v.Name)] + require.True(t, ok) + assert.Equal(t, v.Status, getStatus(df.Default)) + assert.Equal(t, v.Version, string(df.PreRelease)) + } +} diff --git a/pkg/antctl/raw/featuregates/command.go b/pkg/antctl/raw/featuregates/command.go index de9a84be1e1..1b646d70f08 100644 --- a/pkg/antctl/raw/featuregates/command.go +++ b/pkg/antctl/raw/featuregates/command.go @@ -87,20 +87,26 @@ func featureGateRequest(cmd *cobra.Command, mode string) error { return err } var agentGates []featuregates.Response + var agentWindowsGates []featuregates.Response var controllerGates []featuregates.Response for _, v := range resp { - if v.Component == "agent" { + switch v.Component { + case featuregates.AgentMode: agentGates = append(agentGates, v) - } else { + case featuregates.AgentWindowsMode: + agentWindowsGates = append(agentWindowsGates, v) + case featuregates.ControllerMode: controllerGates = append(controllerGates, v) } } if len(agentGates) > 0 { - output(agentGates, runtime.ModeAgent, cmd.OutOrStdout()) + output(agentGates, featuregates.AgentMode, cmd.OutOrStdout()) + } + if len(agentWindowsGates) > 0 { + output(agentWindowsGates, featuregates.AgentWindowsMode, cmd.OutOrStdout()) } if len(controllerGates) > 0 { - fmt.Println() - output(controllerGates, runtime.ModeController, cmd.OutOrStdout()) + output(controllerGates, featuregates.ControllerMode, cmd.OutOrStdout()) } return nil } @@ -165,14 +171,27 @@ func getFeatureGatesRequest(client *rest.RESTClient) ([]featuregates.Response, e return resp, nil } -func output(resps []featuregates.Response, runtimeMode string, output io.Writer) { - switch runtimeMode { - case runtime.ModeAgent: +func output(resps []featuregates.Response, component string, output io.Writer) { + switch component { + case featuregates.AgentMode: output.Write([]byte("Antrea Agent Feature Gates\n")) - case runtime.ModeController: + case featuregates.AgentWindowsMode: + output.Write([]byte("\n")) + output.Write([]byte("Antrea Agent Feature Gates (Windows)\n")) + case featuregates.ControllerMode: + output.Write([]byte("\n")) output.Write([]byte("Antrea Controller Feature Gates\n")) } - formatter := "%-25s%-15s%-10s\n" + + maxNameLen := len("FEATUREGATE") + maxStatusLen := len("STATUS") + + for _, r := range resps { + maxNameLen = max(maxNameLen, len(r.Name)) + maxStatusLen = max(maxStatusLen, len(r.Status)) + } + + formatter := fmt.Sprintf("%%-%ds%%-%ds%%-s\n", maxNameLen+5, maxStatusLen+5) output.Write([]byte(fmt.Sprintf(formatter, "FEATUREGATE", "STATUS", "VERSION"))) for _, r := range resps { fmt.Fprintf(output, formatter, r.Name, r.Status, r.Version) diff --git a/pkg/antctl/raw/featuregates/command_test.go b/pkg/antctl/raw/featuregates/command_test.go index 36355792412..0c384ddfcdc 100644 --- a/pkg/antctl/raw/featuregates/command_test.go +++ b/pkg/antctl/raw/featuregates/command_test.go @@ -72,77 +72,143 @@ var ( ) func TestGetFeatureGates(t *testing.T) { - fullResponse := []byte(`[ + controllerRemoteResponse := []byte(`[ { "component": "agent", - "name": "EndpointSlice", + "name": "AntreaIPAM", "status": "Disabled", "version": "ALPHA" }, { "component": "agent", - "name": "AntreaIPAM", - "status": "Disabled", - "version": "ALPHA" + "name": "AntreaPolicy", + "status": "Enabled", + "version": "BETA" }, { "component": "agent", - "name": "FlowExporter", + "name": "AntreaProxy", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent", + "name": "CleanupStaleUDPSvcConntrack", "status": "Disabled", "version": "ALPHA" }, { "component": "agent", - "name": "NetworkPolicyStats", + "name": "Egress", "status": "Enabled", "version": "BETA" }, { "component": "agent", - "name": "AntreaPolicy", + "name": "EndpointSlice", "status": "Enabled", "version": "BETA" }, { "component": "agent", - "name": "Multicast", + "name": "ExternalNode", "status": "Disabled", "version": "ALPHA" }, { "component": "agent", - "name": "Traceflow", + "name": "FlowExporter", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "IPsecCertAuth", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "L7NetworkPolicy", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "LoadBalancerModeDSR", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "Multicast", "status": "Enabled", "version": "BETA" }, { "component": "agent", - "name": "ServiceExternalIP", + "name": "Multicluster", "status": "Disabled", "version": "ALPHA" }, { "component": "agent", - "name": "AntreaProxy", + "name": "NetworkPolicyStats", "status": "Enabled", "version": "BETA" }, { "component": "agent", - "name": "Egress", + "name": "NodePortLocal", "status": "Enabled", "version": "BETA" }, { "component": "agent", - "name": "NodePortLocal", + "name": "SecondaryNetwork", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "ServiceExternalIP", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "SupportBundleCollection", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "TopologyAwareHints", "status": "Enabled", "version": "BETA" }, { "component": "agent", - "name": "Multicluster", + "name": "Traceflow", "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent", + "name": "TrafficControl", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "controller", + "name": "AdminNetworkPolicy", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "controller", + "name": "AntreaIPAM", + "status": "Disabled", "version": "ALPHA" }, { @@ -153,31 +219,61 @@ func TestGetFeatureGates(t *testing.T) { }, { "component": "controller", - "name": "NodeIPAM", + "name": "Egress", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "controller", + "name": "IPsecCertAuth", "status": "Disabled", "version": "ALPHA" }, { "component": "controller", - "name": "Traceflow", + "name": "L7NetworkPolicy", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "controller", + "name": "Multicast", "status": "Enabled", "version": "BETA" }, { "component": "controller", - "name": "ServiceExternalIP", + "name": "Multicluster", "status": "Disabled", "version": "ALPHA" }, { "component": "controller", - "name": "Egress", + "name": "NetworkPolicyStats", "status": "Enabled", "version": "BETA" }, { "component": "controller", - "name": "NetworkPolicyStats", + "name": "NodeIPAM", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "controller", + "name": "ServiceExternalIP", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "controller", + "name": "SupportBundleCollection", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "controller", + "name": "Traceflow", "status": "Enabled", "version": "BETA" } @@ -186,13 +282,43 @@ func TestGetFeatureGates(t *testing.T) { agentResponse := []byte(`[ { "component": "agent", - "name": "EndpointSlice", + "name": "AntreaIPAM", "status": "Disabled", "version": "ALPHA" }, { "component": "agent", - "name": "AntreaIPAM", + "name": "AntreaPolicy", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent", + "name": "AntreaProxy", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent", + "name": "CleanupStaleUDPSvcConntrack", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "Egress", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent", + "name": "EndpointSlice", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent", + "name": "ExternalNode", "status": "Disabled", "version": "ALPHA" }, @@ -202,6 +328,36 @@ func TestGetFeatureGates(t *testing.T) { "status": "Disabled", "version": "ALPHA" }, + { + "component": "agent", + "name": "IPsecCertAuth", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "L7NetworkPolicy", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "LoadBalancerModeDSR", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "Multicast", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent", + "name": "Multicluster", + "status": "Disabled", + "version": "ALPHA" + }, { "component": "agent", "name": "NetworkPolicyStats", @@ -210,16 +366,34 @@ func TestGetFeatureGates(t *testing.T) { }, { "component": "agent", - "name": "AntreaPolicy", + "name": "NodePortLocal", "status": "Enabled", "version": "BETA" }, { "component": "agent", - "name": "Multicast", + "name": "SecondaryNetwork", "status": "Disabled", "version": "ALPHA" }, + { + "component": "agent", + "name": "ServiceExternalIP", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "SupportBundleCollection", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "TopologyAwareHints", + "status": "Enabled", + "version": "BETA" + }, { "component": "agent", "name": "Traceflow", @@ -228,22 +402,97 @@ func TestGetFeatureGates(t *testing.T) { }, { "component": "agent", - "name": "ServiceExternalIP", + "name": "TrafficControl", + "status": "Disabled", + "version": "ALPHA" + } + ]`) + + controllerRemoteWithWindowsAgentResponse := []byte(`[ + { + "component": "agent", + "name": "AntreaIPAM", "status": "Disabled", "version": "ALPHA" }, + { + "component": "agent", + "name": "AntreaPolicy", + "status": "Enabled", + "version": "BETA" + }, { "component": "agent", "name": "AntreaProxy", "status": "Enabled", "version": "BETA" }, + { + "component": "agent", + "name": "CleanupStaleUDPSvcConntrack", + "status": "Disabled", + "version": "ALPHA" + }, { "component": "agent", "name": "Egress", "status": "Enabled", "version": "BETA" }, + { + "component": "agent", + "name": "EndpointSlice", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent", + "name": "ExternalNode", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "FlowExporter", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "IPsecCertAuth", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "L7NetworkPolicy", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "LoadBalancerModeDSR", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "Multicast", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent", + "name": "Multicluster", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "NetworkPolicyStats", + "status": "Enabled", + "version": "BETA" + }, { "component": "agent", "name": "NodePortLocal", @@ -252,9 +501,183 @@ func TestGetFeatureGates(t *testing.T) { }, { "component": "agent", + "name": "SecondaryNetwork", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "ServiceExternalIP", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "SupportBundleCollection", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent", + "name": "TopologyAwareHints", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent", + "name": "Traceflow", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent", + "name": "TrafficControl", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent-windows", + "name": "AntreaPolicy", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent-windows", + "name": "AntreaProxy", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent-windows", + "name": "EndpointSlice", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent-windows", + "name": "ExternalNode", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent-windows", + "name": "FlowExporter", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent-windows", + "name": "NetworkPolicyStats", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent-windows", + "name": "NodePortLocal", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent-windows", + "name": "SupportBundleCollection", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "agent-windows", + "name": "TopologyAwareHints", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent-windows", + "name": "Traceflow", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "agent-windows", + "name": "TrafficControl", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "controller", + "name": "AdminNetworkPolicy", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "controller", + "name": "AntreaIPAM", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "controller", + "name": "AntreaPolicy", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "controller", + "name": "Egress", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "controller", + "name": "IPsecCertAuth", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "controller", + "name": "L7NetworkPolicy", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "controller", + "name": "Multicast", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "controller", "name": "Multicluster", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "controller", + "name": "NetworkPolicyStats", "status": "Enabled", + "version": "BETA" + }, + { + "component": "controller", + "name": "NodeIPAM", + "status": "Enabled", + "version": "BETA" + }, + { + "component": "controller", + "name": "ServiceExternalIP", + "status": "Disabled", "version": "ALPHA" + }, + { + "component": "controller", + "name": "SupportBundleCollection", + "status": "Disabled", + "version": "ALPHA" + }, + { + "component": "controller", + "name": "Traceflow", + "status": "Enabled", + "version": "BETA" } ]`) @@ -268,22 +691,182 @@ func TestGetFeatureGates(t *testing.T) { expectedOutput string }{ { - name: "get featuregates out of Pod", - runE: controllerRemoteRunE, - expectedOutput: "Antrea Agent Feature Gates\nFEATUREGATE STATUS VERSION \nEndpointSlice Disabled ALPHA \nAntreaIPAM Disabled ALPHA \nFlowExporter Disabled ALPHA \nNetworkPolicyStats Enabled BETA \nAntreaPolicy Enabled BETA \nMulticast Disabled ALPHA \nTraceflow Enabled BETA \nServiceExternalIP Disabled ALPHA \nAntreaProxy Enabled BETA \nEgress Enabled BETA \nNodePortLocal Enabled BETA \nMulticluster Enabled ALPHA \nAntrea Controller Feature Gates\nFEATUREGATE STATUS VERSION \nAntreaPolicy Enabled BETA \nNodeIPAM Disabled ALPHA \nTraceflow Enabled BETA \nServiceExternalIP Disabled ALPHA \nEgress Enabled BETA \nNetworkPolicyStats Enabled BETA \n", - response: fullResponse, - }, - { - name: "get featuregates in agent Pod", - runE: agentRunE, - expectedOutput: "Antrea Agent Feature Gates\nFEATUREGATE STATUS VERSION \nEndpointSlice Disabled ALPHA \nAntreaIPAM Disabled ALPHA \nFlowExporter Disabled ALPHA \nNetworkPolicyStats Enabled BETA \nAntreaPolicy Enabled BETA \nMulticast Disabled ALPHA \nTraceflow Enabled BETA \nServiceExternalIP Disabled ALPHA \nAntreaProxy Enabled BETA \nEgress Enabled BETA \nNodePortLocal Enabled BETA \nMulticluster Enabled ALPHA \n", - response: agentResponse, - }, - { - name: "get featuregates in controller Pod", - runE: controllerLocalRunE, - expectedOutput: "Antrea Agent Feature Gates\nFEATUREGATE STATUS VERSION \nEndpointSlice Disabled ALPHA \nAntreaIPAM Disabled ALPHA \nFlowExporter Disabled ALPHA \nNetworkPolicyStats Enabled BETA \nAntreaPolicy Enabled BETA \nMulticast Disabled ALPHA \nTraceflow Enabled BETA \nServiceExternalIP Disabled ALPHA \nAntreaProxy Enabled BETA \nEgress Enabled BETA \nNodePortLocal Enabled BETA \nMulticluster Enabled ALPHA \nAntrea Controller Feature Gates\nFEATUREGATE STATUS VERSION \nAntreaPolicy Enabled BETA \nNodeIPAM Disabled ALPHA \nTraceflow Enabled BETA \nServiceExternalIP Disabled ALPHA \nEgress Enabled BETA \nNetworkPolicyStats Enabled BETA \n", - response: fullResponse, + name: "get featuregates out of Pod", + runE: controllerRemoteRunE, + expectedOutput: `Antrea Agent Feature Gates +FEATUREGATE STATUS VERSION +AntreaIPAM Disabled ALPHA +AntreaPolicy Enabled BETA +AntreaProxy Enabled BETA +CleanupStaleUDPSvcConntrack Disabled ALPHA +Egress Enabled BETA +EndpointSlice Enabled BETA +ExternalNode Disabled ALPHA +FlowExporter Disabled ALPHA +IPsecCertAuth Disabled ALPHA +L7NetworkPolicy Disabled ALPHA +LoadBalancerModeDSR Disabled ALPHA +Multicast Enabled BETA +Multicluster Disabled ALPHA +NetworkPolicyStats Enabled BETA +NodePortLocal Enabled BETA +SecondaryNetwork Disabled ALPHA +ServiceExternalIP Disabled ALPHA +SupportBundleCollection Disabled ALPHA +TopologyAwareHints Enabled BETA +Traceflow Enabled BETA +TrafficControl Disabled ALPHA + +Antrea Controller Feature Gates +FEATUREGATE STATUS VERSION +AdminNetworkPolicy Disabled ALPHA +AntreaIPAM Disabled ALPHA +AntreaPolicy Enabled BETA +Egress Enabled BETA +IPsecCertAuth Disabled ALPHA +L7NetworkPolicy Disabled ALPHA +Multicast Enabled BETA +Multicluster Disabled ALPHA +NetworkPolicyStats Enabled BETA +NodeIPAM Enabled BETA +ServiceExternalIP Disabled ALPHA +SupportBundleCollection Disabled ALPHA +Traceflow Enabled BETA +`, + response: controllerRemoteResponse, + }, + { + name: "get featuregates in agent Pod", + runE: agentRunE, + expectedOutput: `Antrea Agent Feature Gates +FEATUREGATE STATUS VERSION +AntreaIPAM Disabled ALPHA +AntreaPolicy Enabled BETA +AntreaProxy Enabled BETA +CleanupStaleUDPSvcConntrack Disabled ALPHA +Egress Enabled BETA +EndpointSlice Enabled BETA +ExternalNode Disabled ALPHA +FlowExporter Disabled ALPHA +IPsecCertAuth Disabled ALPHA +L7NetworkPolicy Disabled ALPHA +LoadBalancerModeDSR Disabled ALPHA +Multicast Enabled BETA +Multicluster Disabled ALPHA +NetworkPolicyStats Enabled BETA +NodePortLocal Enabled BETA +SecondaryNetwork Disabled ALPHA +ServiceExternalIP Disabled ALPHA +SupportBundleCollection Disabled ALPHA +TopologyAwareHints Enabled BETA +Traceflow Enabled BETA +TrafficControl Disabled ALPHA +`, + response: agentResponse, + }, + { + name: "get featuregates in controller Pod", + runE: controllerLocalRunE, + expectedOutput: `Antrea Agent Feature Gates +FEATUREGATE STATUS VERSION +AntreaIPAM Disabled ALPHA +AntreaPolicy Enabled BETA +AntreaProxy Enabled BETA +CleanupStaleUDPSvcConntrack Disabled ALPHA +Egress Enabled BETA +EndpointSlice Enabled BETA +ExternalNode Disabled ALPHA +FlowExporter Disabled ALPHA +IPsecCertAuth Disabled ALPHA +L7NetworkPolicy Disabled ALPHA +LoadBalancerModeDSR Disabled ALPHA +Multicast Enabled BETA +Multicluster Disabled ALPHA +NetworkPolicyStats Enabled BETA +NodePortLocal Enabled BETA +SecondaryNetwork Disabled ALPHA +ServiceExternalIP Disabled ALPHA +SupportBundleCollection Disabled ALPHA +TopologyAwareHints Enabled BETA +Traceflow Enabled BETA +TrafficControl Disabled ALPHA + +Antrea Controller Feature Gates +FEATUREGATE STATUS VERSION +AdminNetworkPolicy Disabled ALPHA +AntreaIPAM Disabled ALPHA +AntreaPolicy Enabled BETA +Egress Enabled BETA +IPsecCertAuth Disabled ALPHA +L7NetworkPolicy Disabled ALPHA +Multicast Enabled BETA +Multicluster Disabled ALPHA +NetworkPolicyStats Enabled BETA +NodeIPAM Enabled BETA +ServiceExternalIP Disabled ALPHA +SupportBundleCollection Disabled ALPHA +Traceflow Enabled BETA +`, + response: controllerRemoteResponse, + }, + { + name: "get featuregates in controller Pod with Windows agent", + runE: controllerLocalRunE, + expectedOutput: `Antrea Agent Feature Gates +FEATUREGATE STATUS VERSION +AntreaIPAM Disabled ALPHA +AntreaPolicy Enabled BETA +AntreaProxy Enabled BETA +CleanupStaleUDPSvcConntrack Disabled ALPHA +Egress Enabled BETA +EndpointSlice Enabled BETA +ExternalNode Disabled ALPHA +FlowExporter Disabled ALPHA +IPsecCertAuth Disabled ALPHA +L7NetworkPolicy Disabled ALPHA +LoadBalancerModeDSR Disabled ALPHA +Multicast Enabled BETA +Multicluster Disabled ALPHA +NetworkPolicyStats Enabled BETA +NodePortLocal Enabled BETA +SecondaryNetwork Disabled ALPHA +ServiceExternalIP Disabled ALPHA +SupportBundleCollection Disabled ALPHA +TopologyAwareHints Enabled BETA +Traceflow Enabled BETA +TrafficControl Disabled ALPHA + +Antrea Agent Feature Gates (Windows) +FEATUREGATE STATUS VERSION +AntreaPolicy Enabled BETA +AntreaProxy Enabled BETA +EndpointSlice Enabled BETA +ExternalNode Disabled ALPHA +FlowExporter Disabled ALPHA +NetworkPolicyStats Enabled BETA +NodePortLocal Enabled BETA +SupportBundleCollection Disabled ALPHA +TopologyAwareHints Enabled BETA +Traceflow Enabled BETA +TrafficControl Disabled ALPHA + +Antrea Controller Feature Gates +FEATUREGATE STATUS VERSION +AdminNetworkPolicy Disabled ALPHA +AntreaIPAM Disabled ALPHA +AntreaPolicy Enabled BETA +Egress Enabled BETA +IPsecCertAuth Disabled ALPHA +L7NetworkPolicy Disabled ALPHA +Multicast Enabled BETA +Multicluster Disabled ALPHA +NetworkPolicyStats Enabled BETA +NodeIPAM Enabled BETA +ServiceExternalIP Disabled ALPHA +SupportBundleCollection Disabled ALPHA +Traceflow Enabled BETA +`, + response: controllerRemoteWithWindowsAgentResponse, }, } diff --git a/pkg/apiserver/handlers/featuregates/handler.go b/pkg/apiserver/handlers/featuregates/handler.go index 39b7c14dc8d..ec461979022 100644 --- a/pkg/apiserver/handlers/featuregates/handler.go +++ b/pkg/apiserver/handlers/featuregates/handler.go @@ -18,10 +18,12 @@ import ( "context" "encoding/json" "net/http" + "sort" + "strings" "gopkg.in/yaml.v2" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/sets" clientset "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" @@ -29,10 +31,6 @@ import ( "antrea.io/antrea/pkg/util/env" ) -var controllerGates = sets.New[string]("Traceflow", "AntreaPolicy", "Egress", "NetworkPolicyStats", "NodeIPAM", "ServiceExternalIP", "Multicluster", "Multicast") -var agentGates = sets.New[string]("AntreaPolicy", "AntreaProxy", "Egress", "EndpointSlice", "Traceflow", "FlowExporter", "NetworkPolicyStats", - "NodePortLocal", "AntreaIPAM", "Multicast", "ServiceExternalIP", "Multicluster") - type ( Config struct { // FeatureGates is a map of feature names to bools that enable or disable experimental features. @@ -48,80 +46,109 @@ type ( ) const ( - controllerMode = "controller" - agentMode = "agent" + AgentMode = "agent" + AgentWindowsMode = "agent-windows" + ControllerMode = "controller" + agentConfigName = "antrea-agent.conf" + controllerConfigName = "antrea-controller.conf" ) // HandleFunc returns the function which can handle queries issued by 'antctl get featuregates' command. // The handler function populates Antrea featuregates information to the response. -// For now, it will return all feature gates included in features.DefaultAntreaFeatureGates for agent -// We need to exclude any new feature gates which is not consumed by agent in the future. func HandleFunc(k8sclient clientset.Interface) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - antreaConfigName := env.GetAntreaConfigMapName() - antreaConfig, err := k8sclient.CoreV1().ConfigMaps(env.GetAntreaNamespace()).Get(context.TODO(), antreaConfigName, metav1.GetOptions{}) + antreaConfigMapName := env.GetAntreaConfigMapName() + antreaNamespace := env.GetAntreaNamespace() + antreaConfig, err := k8sclient.CoreV1().ConfigMaps(antreaNamespace).Get(context.TODO(), antreaConfigMapName, metav1.GetOptions{}) if err != nil { w.WriteHeader(http.StatusInternalServerError) - klog.Errorf("Error when getting config map %s: %v", antreaConfigName, err) + klog.ErrorS(err, "Error when getting config map", "ConfigMap", klog.KRef(antreaNamespace, antreaConfigMapName)) return } + configMaps, err := k8sclient.CoreV1().ConfigMaps(antreaNamespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: "app=antrea", + }) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + klog.ErrorS(err, "Error when listing all Antrea ConfigMaps by label") + return + } + antreaWindowsConfigMaps := []v1.ConfigMap{} + for _, cm := range configMaps.Items { + if strings.HasPrefix(cm.Name, "antrea-windows-config") { + antreaWindowsConfigMaps = append(antreaWindowsConfigMaps, cm) + } + } agentConfig := &Config{} - err = yaml.Unmarshal([]byte(antreaConfig.Data["antrea-agent.conf"]), agentConfig) - if err != nil { + if err = yaml.Unmarshal([]byte(antreaConfig.Data[agentConfigName]), agentConfig); err != nil { + w.WriteHeader(http.StatusInternalServerError) + klog.ErrorS(err, "Failed to unmarshal Antrea agent config", + "ConfigMap", klog.KRef(antreaNamespace, antreaConfigMapName), "file", agentConfigName) + return + } + + controllerConfig := &Config{} + if err = yaml.Unmarshal([]byte(antreaConfig.Data[controllerConfigName]), controllerConfig); err != nil { w.WriteHeader(http.StatusInternalServerError) - klog.Errorf("Failed to unmarshal Antrea antrea-agent.conf: %v", err) + klog.ErrorS(err, "Failed to unmarshal Antrea controller config", + "ConfigMap", klog.KRef(antreaNamespace, antreaConfigMapName), "file", controllerConfigName) return } - agentfeatureGates := getAgentGatesResponse(agentConfig) - controllerfeatureGates := getControllerGatesResponse() + agentfeatureGates := getFeatureGatesResponse(agentConfig, AgentMode) + controllerfeatureGates := getFeatureGatesResponse(controllerConfig, ControllerMode) result := append(agentfeatureGates, controllerfeatureGates...) + + if len(antreaWindowsConfigMaps) > 0 { + sort.Slice(antreaWindowsConfigMaps, func(i, j int) bool { + return antreaWindowsConfigMaps[i].CreationTimestamp.After(antreaWindowsConfigMaps[j].CreationTimestamp.Time) + }) + agentWindowsConfig := &Config{} + antreaWindowsConfigMap := antreaWindowsConfigMaps[0] + if err = yaml.Unmarshal([]byte(antreaWindowsConfigMap.Data[agentConfigName]), agentWindowsConfig); err != nil { + w.WriteHeader(http.StatusInternalServerError) + klog.ErrorS(err, "Failed to unmarshal Antrea agent windows config", + "ConfigMap", klog.KRef(antreaNamespace, antreaWindowsConfigMap.Name), "file", agentConfigName) + return + } + + agentWindowsfeatureGates := getFeatureGatesResponse(agentWindowsConfig, AgentWindowsMode) + result = append(result, agentWindowsfeatureGates...) + } + err = json.NewEncoder(w).Encode(result) if err != nil { w.WriteHeader(http.StatusInternalServerError) - klog.Errorf("Error when encoding FeatureGates to json: %v", err) + klog.ErrorS(err, "Error when encoding FeatureGates to json") return } } } -func getAgentGatesResponse(cfg *Config) []Response { +func getFeatureGatesResponse(cfg *Config, component string) []Response { gatesResp := []Response{} for df := range features.DefaultAntreaFeatureGates { - dfs := string(df) - if !agentGates.Has(dfs) { - continue - } - status, ok := cfg.FeatureGates[dfs] - if !ok { - status = features.DefaultMutableFeatureGate.Enabled(df) - } - featureStatus := getStatus(status) - gatesResp = append(gatesResp, Response{ - Component: agentMode, - Name: dfs, - Status: featureStatus, - Version: string(features.DefaultAntreaFeatureGates[df].PreRelease), - }) - } - return gatesResp -} + if component == AgentMode && features.AgentGates.Has(df) || + component == AgentWindowsMode && features.AgentGates.Has(df) && features.SupportedOnWindows(df) || + component == ControllerMode && features.ControllerGates.Has(df) { -func getControllerGatesResponse() []Response { - gatesResp := []Response{} - for df := range features.DefaultAntreaFeatureGates { - dfs := string(df) - if !controllerGates.Has(dfs) { - continue + status, ok := cfg.FeatureGates[string(df)] + if !ok { + status = features.DefaultFeatureGate.Enabled(df) + } + featureStatus := getStatus(status) + gatesResp = append(gatesResp, Response{ + Component: component, + Name: string(df), + Status: featureStatus, + Version: string(features.DefaultAntreaFeatureGates[df].PreRelease), + }) } - gatesResp = append(gatesResp, Response{ - Component: controllerMode, - Name: dfs, - Status: getStatus(features.DefaultMutableFeatureGate.Enabled(df)), - Version: string(features.DefaultAntreaFeatureGates[df].PreRelease), - }) } + sort.Slice(gatesResp, func(i, j int) bool { + return gatesResp[i].Name < gatesResp[j].Name + }) return gatesResp } diff --git a/pkg/apiserver/handlers/featuregates/handler_test.go b/pkg/apiserver/handlers/featuregates/handler_test.go index 38d9e002cfb..6a368501635 100644 --- a/pkg/apiserver/handlers/featuregates/handler_test.go +++ b/pkg/apiserver/handlers/featuregates/handler_test.go @@ -19,14 +19,14 @@ import ( "net/http" "net/http/httptest" "os" - "reflect" - "sort" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" + "k8s.io/component-base/featuregate" "antrea.io/antrea/pkg/features" "antrea.io/antrea/pkg/util/runtime" @@ -51,63 +51,79 @@ func Test_getGatesResponse(t *testing.T) { }, }, want: []Response{ + {Component: "agent", Name: "AntreaIPAM", Status: "Disabled", Version: "ALPHA"}, {Component: "agent", Name: "AntreaPolicy", Status: "Disabled", Version: "BETA"}, {Component: "agent", Name: "AntreaProxy", Status: "Enabled", Version: "BETA"}, + {Component: "agent", Name: "CleanupStaleUDPSvcConntrack", Status: "Disabled", Version: "ALPHA"}, {Component: "agent", Name: "Egress", Status: egressStatus, Version: "BETA"}, {Component: "agent", Name: "EndpointSlice", Status: "Enabled"}, - {Component: "agent", Name: "AntreaIPAM", Status: "Disabled", Version: "ALPHA"}, - {Component: "agent", Name: "Traceflow", Status: "Enabled", Version: "BETA"}, + {Component: "agent", Name: "ExternalNode", Status: "Disabled", Version: "ALPHA"}, {Component: "agent", Name: "FlowExporter", Status: "Disabled", Version: "ALPHA"}, + {Component: "agent", Name: "IPsecCertAuth", Status: "Disabled", Version: "ALPHA"}, + {Component: "agent", Name: "L7NetworkPolicy", Status: "Disabled", Version: "ALPHA"}, + {Component: "agent", Name: "LoadBalancerModeDSR", Status: "Disabled", Version: "ALPHA"}, + {Component: "agent", Name: "Multicast", Status: multicastStatus, Version: "BETA"}, + {Component: "agent", Name: "Multicluster", Status: "Disabled", Version: "ALPHA"}, {Component: "agent", Name: "NetworkPolicyStats", Status: "Enabled", Version: "BETA"}, {Component: "agent", Name: "NodePortLocal", Status: "Enabled", Version: "BETA"}, - {Component: "agent", Name: "Multicast", Status: multicastStatus, Version: "BETA"}, + {Component: "agent", Name: "SecondaryNetwork", Status: "Disabled", Version: "ALPHA"}, {Component: "agent", Name: "ServiceExternalIP", Status: "Disabled", Version: "ALPHA"}, - {Component: "agent", Name: "Multicluster", Status: "Disabled", Version: "ALPHA"}, + {Component: "agent", Name: "SupportBundleCollection", Status: "Disabled", Version: "ALPHA"}, + {Component: "agent", Name: "TopologyAwareHints", Status: "Enabled", Version: "BETA"}, + {Component: "agent", Name: "Traceflow", Status: "Enabled", Version: "BETA"}, + {Component: "agent", Name: "TrafficControl", Status: "Disabled", Version: "ALPHA"}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := getAgentGatesResponse(tt.cfg) - sort.SliceStable(got, func(i, j int) bool { - return got[i].Name < got[j].Name - }) - sort.SliceStable(tt.want, func(i, j int) bool { - return tt.want[i].Name < tt.want[j].Name - }) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("getAgentGatesResponse() = %v, want %v", got, tt.want) - } + got := getFeatureGatesResponse(tt.cfg, AgentMode) + assert.Equal(t, got, tt.want, "The feature gates for Antrea agent are not correct") }) } } -func Test_getStatus(t *testing.T) { +func Test_getGatesWindowsResponse(t *testing.T) { tests := []struct { - name string - status bool - want string + name string + cfg *Config + want []Response }{ { - name: "Enabled case", - status: true, - want: "Enabled", - }, - { - name: "Disabled case", - status: false, - want: "Disabled", + name: "mutated AntreaPolicy feature gate, agent windows mode", + cfg: &Config{ + FeatureGates: map[string]bool{ + "AntreaPolicy": false, + }, + }, + want: []Response{ + {Component: "agent-windows", Name: "AntreaPolicy", Status: "Disabled", Version: "BETA"}, + {Component: "agent-windows", Name: "AntreaProxy", Status: "Enabled", Version: "BETA"}, + {Component: "agent-windows", Name: "EndpointSlice", Status: "Enabled"}, + {Component: "agent-windows", Name: "ExternalNode", Status: "Disabled", Version: "ALPHA"}, + {Component: "agent-windows", Name: "FlowExporter", Status: "Disabled", Version: "ALPHA"}, + {Component: "agent-windows", Name: "NetworkPolicyStats", Status: "Enabled", Version: "BETA"}, + {Component: "agent-windows", Name: "NodePortLocal", Status: "Enabled", Version: "BETA"}, + {Component: "agent-windows", Name: "SupportBundleCollection", Status: "Disabled", Version: "ALPHA"}, + {Component: "agent-windows", Name: "TopologyAwareHints", Status: "Enabled", Version: "BETA"}, + {Component: "agent-windows", Name: "Traceflow", Status: "Enabled", Version: "BETA"}, + {Component: "agent-windows", Name: "TrafficControl", Status: "Disabled", Version: "ALPHA"}, + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := getStatus(tt.status); got != tt.want { - t.Errorf("getStatus() = %v, want %v", got, tt.want) - } + got := getFeatureGatesResponse(tt.cfg, AgentWindowsMode) + assert.Equal(t, got, tt.want, "The feature gates for Antrea agent windows are not correct") }) } } +func TestGetStatus(t *testing.T) { + assert.Equal(t, "Enabled", getStatus(true)) + assert.Equal(t, "Disabled", getStatus(false)) +} + func TestHandleFunc(t *testing.T) { fakeClient := fake.NewSimpleClientset( &v1.Pod{ @@ -132,42 +148,34 @@ func TestHandleFunc(t *testing.T) { "antrea-controller.conf": "#configmap-value", }, }, + &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Namespace: "kube-system", Name: "antrea-windows-config-xqwiwuv", Labels: map[string]string{"app": "antrea"}}, + Data: map[string]string{ + "antrea-agent.conf": "#configmap-value", + }, + }, ) - tests := []struct { - name string - expectedStatus int - }{ - { - name: "good path", - expectedStatus: http.StatusOK, - }, - } os.Setenv("POD_NAME", "antrea-controller-wotqiwth") os.Setenv("ANTREA_CONFIG_MAP_NAME", "antrea-config-aswieut") - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - handler := HandleFunc(fakeClient) - req, err := http.NewRequest(http.MethodGet, "", nil) - assert.Nil(t, err) - recorder := httptest.NewRecorder() - handler.ServeHTTP(recorder, req) - assert.Equal(t, tt.expectedStatus, recorder.Code) - if tt.expectedStatus != http.StatusOK { - return - } - var resp []Response - err = json.Unmarshal(recorder.Body.Bytes(), &resp) - assert.Nil(t, err) - for _, v := range resp { - for n, f := range features.DefaultAntreaFeatureGates { - if v.Name == string(n) { - assert.Equal(t, v.Status, getStatus(f.Default)) - assert.Equal(t, v.Version, string(f.PreRelease)) - } - } - } - }) + + handler := HandleFunc(fakeClient) + req, err := http.NewRequest(http.MethodGet, "", nil) + require.Nil(t, err) + + recorder := httptest.NewRecorder() + handler.ServeHTTP(recorder, req) + require.Equal(t, http.StatusOK, recorder.Code) + + var resp []Response + err = json.Unmarshal(recorder.Body.Bytes(), &resp) + require.Nil(t, err) + + for _, v := range resp { + df, ok := features.DefaultAntreaFeatureGates[featuregate.Feature(v.Name)] + require.True(t, ok) + assert.Equal(t, v.Status, getStatus(df.Default)) + assert.Equal(t, v.Version, string(df.PreRelease)) } } @@ -179,29 +187,26 @@ func Test_getControllerGatesResponse(t *testing.T) { { name: "good path", want: []Response{ + {Component: "controller", Name: "AdminNetworkPolicy", Status: "Disabled", Version: "ALPHA"}, + {Component: "controller", Name: "AntreaIPAM", Status: "Disabled", Version: "ALPHA"}, {Component: "controller", Name: "AntreaPolicy", Status: "Enabled", Version: "BETA"}, {Component: "controller", Name: "Egress", Status: egressStatus, Version: "BETA"}, - {Component: "controller", Name: "Traceflow", Status: "Enabled", Version: "BETA"}, + {Component: "controller", Name: "IPsecCertAuth", Status: "Disabled", Version: "ALPHA"}, + {Component: "controller", Name: "L7NetworkPolicy", Status: "Disabled", Version: "ALPHA"}, + {Component: "controller", Name: "Multicast", Status: multicastStatus, Version: "BETA"}, + {Component: "controller", Name: "Multicluster", Status: "Disabled", Version: "ALPHA"}, {Component: "controller", Name: "NetworkPolicyStats", Status: "Enabled", Version: "BETA"}, {Component: "controller", Name: "NodeIPAM", Status: "Enabled", Version: "BETA"}, {Component: "controller", Name: "ServiceExternalIP", Status: "Disabled", Version: "ALPHA"}, - {Component: "controller", Name: "Multicluster", Status: "Disabled", Version: "ALPHA"}, - {Component: "controller", Name: "Multicast", Status: multicastStatus, Version: "BETA"}, + {Component: "controller", Name: "SupportBundleCollection", Status: "Disabled", Version: "ALPHA"}, + {Component: "controller", Name: "Traceflow", Status: "Enabled", Version: "BETA"}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := getControllerGatesResponse() - sort.SliceStable(got, func(i, j int) bool { - return got[i].Name < got[j].Name - }) - sort.SliceStable(tt.want, func(i, j int) bool { - return tt.want[i].Name < tt.want[j].Name - }) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("getControllerGatesResponse() = %v, want %v", got, tt.want) - } + got := getFeatureGatesResponse(&Config{}, ControllerMode) + assert.Equal(t, got, tt.want, "The feature gates for Antrea Controller are not correct") }) } } diff --git a/pkg/features/antrea_features.go b/pkg/features/antrea_features.go index 6691ce90d46..68689618d2d 100644 --- a/pkg/features/antrea_features.go +++ b/pkg/features/antrea_features.go @@ -16,6 +16,7 @@ package features import ( k8sruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/component-base/featuregate" "antrea.io/antrea/pkg/util/runtime" @@ -178,6 +179,50 @@ var ( AdminNetworkPolicy: {Default: false, PreRelease: featuregate.Alpha}, } + // AgentGates consists of all known feature gates for the Antrea Agent. + // When adding a new feature gate that applies to the Antrea Agent, please also add it here. + AgentGates = sets.New[featuregate.Feature]( + AntreaIPAM, + AntreaPolicy, + AntreaProxy, + CleanupStaleUDPSvcConntrack, + Egress, + EndpointSlice, + ExternalNode, + FlowExporter, + IPsecCertAuth, + L7NetworkPolicy, + LoadBalancerModeDSR, + Multicast, + Multicluster, + NetworkPolicyStats, + NodePortLocal, + SecondaryNetwork, + ServiceExternalIP, + SupportBundleCollection, + TopologyAwareHints, + Traceflow, + TrafficControl, + ) + + // ControllerGates consists of all known feature gates for the Antrea Controller. + // When adding a new feature gate that applies to the Antrea Controller, please also add it here. + ControllerGates = sets.New[featuregate.Feature]( + AdminNetworkPolicy, + AntreaIPAM, + AntreaPolicy, + Egress, + IPsecCertAuth, + L7NetworkPolicy, + Multicast, + Multicluster, + NetworkPolicyStats, + NodeIPAM, + ServiceExternalIP, + SupportBundleCollection, + Traceflow, + ) + // UnsupportedFeaturesOnWindows records the features not supported on // a Windows Node. Antrea Agent on a Windows Node checks the enabled // features, and fails the startup if an unsupported feature is enabled. diff --git a/pkg/features/antrea_features_test.go b/pkg/features/antrea_features_test.go index b96523dbf33..666cba48356 100644 --- a/pkg/features/antrea_features_test.go +++ b/pkg/features/antrea_features_test.go @@ -82,3 +82,11 @@ func TestSupportedOnExternalNode(t *testing.T) { }) } } + +func TestDefaultAntreaFeatureGates(t *testing.T) { + for df := range DefaultAntreaFeatureGates { + if !AgentGates.Has(df) && !ControllerGates.Has(df) { + t.Errorf("Feature gate %s is not present in AgentGates and ControllerGates", df) + } + } +}