Skip to content

Commit eef3b6f

Browse files
migrate experimental-stop-grpc-service-on-defrag flag to feature gate.
Signed-off-by: Siyuan Zhang <[email protected]>
1 parent 9f59ef8 commit eef3b6f

File tree

14 files changed

+380
-57
lines changed

14 files changed

+380
-57
lines changed

pkg/featuregate/feature_gate.go

+10
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ type MutableFeatureGate interface {
122122
// overriding its default to true for a limited number of components without simultaneously
123123
// changing its default for all consuming components.
124124
OverrideDefault(name Feature, override bool) error
125+
// ForceAddForTest adds features to the featureGate, and it works even after AddFlag is called.
126+
// It should only be used in tests.
127+
ForceAddForTest(features map[Feature]FeatureSpec) error
125128
}
126129

127130
// featureGate implements FeatureGate as well as pflag.Value for flag parsing.
@@ -267,6 +270,13 @@ func (f *featureGate) Type() string {
267270
return "mapStringBool"
268271
}
269272

273+
func (f *featureGate) ForceAddForTest(features map[Feature]FeatureSpec) error {
274+
f.lock.Lock()
275+
f.closed = false
276+
f.lock.Unlock()
277+
return f.Add(features)
278+
}
279+
270280
// Add adds features to the featureGate.
271281
func (f *featureGate) Add(features map[Feature]FeatureSpec) error {
272282
f.lock.Lock()

pkg/flags/flag.go

+14
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"flag"
2020
"fmt"
2121
"os"
22+
"strconv"
2223
"strings"
2324

2425
"github.com/spf13/pflag"
@@ -130,3 +131,16 @@ func IsSet(fs *flag.FlagSet, name string) bool {
130131
})
131132
return set
132133
}
134+
135+
// GetBoolFlagVal returns the value of the a given bool flag is explicitly set in the cmd line arguments,
136+
// and returns nil if it is not explicitly set.
137+
func GetBoolFlagVal(fs *flag.FlagSet, flagName string) (*bool, error) {
138+
if !IsSet(fs, flagName) {
139+
return nil, nil
140+
}
141+
flagVal, parseErr := strconv.ParseBool(fs.Lookup(flagName).Value.String())
142+
if parseErr != nil {
143+
return nil, parseErr
144+
}
145+
return &flagVal, nil
146+
}

server/config/config.go

-3
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,6 @@ type ServerConfig struct {
193193
// a shared buffer in its readonly check operations.
194194
ExperimentalTxnModeWriteWithSharedBuffer bool `json:"experimental-txn-mode-write-with-shared-buffer"`
195195

196-
// ExperimentalStopGRPCServiceOnDefrag enables etcd gRPC service to stop serving client requests on defragmentation.
197-
ExperimentalStopGRPCServiceOnDefrag bool `json:"experimental-stop-grpc-service-on-defrag"`
198-
199196
// ExperimentalBootstrapDefragThresholdMegabytes is the minimum number of megabytes needed to be freed for etcd server to
200197
// consider running defrag during bootstrap. Needs to be set to non-zero value to take effect.
201198
ExperimentalBootstrapDefragThresholdMegabytes uint `json:"experimental-bootstrap-defrag-threshold-megabytes"`

server/embed/config.go

+57
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,21 @@ func ConfigFromFile(path string) (*Config, error) {
785785
return &cfg.Config, nil
786786
}
787787

788+
// getBoolFlagValFromFile parses the yaml bytes to raw map, and get the top level bool flag value.
789+
func getBoolFlagValFromFile(yamlBytes []byte, flagName string) (*bool, error) {
790+
var cfgMap map[string]interface{}
791+
err := yaml.Unmarshal(yamlBytes, &cfgMap)
792+
if err != nil {
793+
return nil, err
794+
}
795+
flagVal, ok := cfgMap[flagName]
796+
if !ok {
797+
return nil, nil
798+
}
799+
boolVal := flagVal.(bool)
800+
return &boolVal, nil
801+
}
802+
788803
func (cfg *configYAML) configFromFile(path string) error {
789804
b, err := os.ReadFile(path)
790805
if err != nil {
@@ -805,6 +820,18 @@ func (cfg *configYAML) configFromFile(path string) error {
805820
}
806821
}
807822

823+
getBoolFlagVal := func(flagName string) *bool {
824+
boolVal, parseErr := getBoolFlagValFromFile(b, flagName)
825+
if parseErr != nil {
826+
panic(parseErr)
827+
}
828+
return boolVal
829+
}
830+
err = SetFeatureGatesFromExperimentalFlags(cfg.ServerFeatureGate, getBoolFlagVal, ServerFeatureGateFlagName, cfg.configJSON.ServerFeatureGatesJSON)
831+
if err != nil {
832+
return err
833+
}
834+
808835
if cfg.configJSON.ListenPeerURLs != "" {
809836
u, err := types.NewURLs(strings.Split(cfg.configJSON.ListenPeerURLs, ","))
810837
if err != nil {
@@ -897,6 +924,36 @@ func (cfg *configYAML) configFromFile(path string) error {
897924
return cfg.Validate()
898925
}
899926

927+
// SetFeatureGatesFromExperimentalFlags sets the feature gate values if the feature gate is not explicitly set
928+
// while their corresponding experimental flags are explicitly set, for all the features in ExperimentalFlagToFeatureMap.
929+
// TODO: remove after all experimental flags are deprecated.
930+
func SetFeatureGatesFromExperimentalFlags(fg featuregate.FeatureGate, getExperimentalFlagVal func(string) *bool, featureGatesFlagName, featureGatesVal string) error {
931+
m := make(map[featuregate.Feature]bool)
932+
// verify that the feature gate and its experimental flag are not both set at the same time.
933+
for expFlagName, featureName := range features.ExperimentalFlagToFeatureMap {
934+
flagVal := getExperimentalFlagVal(expFlagName)
935+
if flagVal == nil {
936+
continue
937+
}
938+
if strings.Contains(featureGatesVal, string(featureName)) {
939+
return fmt.Errorf("cannot specify both flags: --%s=%v and --%s=%s=%v at the same time, please just use --%s=%s=%v",
940+
expFlagName, *flagVal, featureGatesFlagName, featureName, fg.Enabled(featureName), featureGatesFlagName, featureName, fg.Enabled(featureName))
941+
}
942+
m[featureName] = *flagVal
943+
}
944+
945+
// filter out unknown features for fg, because we could use SetFeatureGatesFromExperimentalFlags both for
946+
// server and cluster level feature gates.
947+
allFeatures := fg.(featuregate.MutableFeatureGate).GetAll()
948+
mFiltered := make(map[string]bool)
949+
for k, v := range m {
950+
if _, ok := allFeatures[k]; ok {
951+
mFiltered[string(k)] = v
952+
}
953+
}
954+
return fg.(featuregate.MutableFeatureGate).SetFromMap(mFiltered)
955+
}
956+
900957
func updateCipherSuites(tls *transport.TLSInfo, ss []string) error {
901958
if len(tls.CipherSuites) > 0 && len(ss) > 0 {
902959
return fmt.Errorf("TLSInfo.CipherSuites is already specified (given %v)", ss)

server/embed/config_test.go

+132-13
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"net"
2222
"net/url"
2323
"os"
24+
"strconv"
2425
"testing"
2526
"time"
2627

@@ -93,10 +94,11 @@ func TestConfigFileOtherFields(t *testing.T) {
9394

9495
func TestConfigFileFeatureGates(t *testing.T) {
9596
testCases := []struct {
96-
name string
97-
serverFeatureGatesJSON string
98-
expectErr bool
99-
expectedFeatures map[featuregate.Feature]bool
97+
name string
98+
serverFeatureGatesJSON string
99+
experimentalStopGRPCServiceOnDefrag string
100+
expectErr bool
101+
expectedFeatures map[featuregate.Feature]bool
100102
}{
101103
{
102104
name: "default",
@@ -106,25 +108,51 @@ func TestConfigFileFeatureGates(t *testing.T) {
106108
},
107109
},
108110
{
109-
name: "set StopGRPCServiceOnDefrag to true",
110-
serverFeatureGatesJSON: "StopGRPCServiceOnDefrag=true",
111+
name: "cannot set both experimental flag and feature gate flag",
112+
serverFeatureGatesJSON: "StopGRPCServiceOnDefrag=true",
113+
experimentalStopGRPCServiceOnDefrag: "false",
114+
expectErr: true,
115+
},
116+
{
117+
name: "ok to set different experimental flag and feature gate flag",
118+
serverFeatureGatesJSON: "DistributedTracing=true",
119+
experimentalStopGRPCServiceOnDefrag: "true",
111120
expectedFeatures: map[featuregate.Feature]bool{
112-
features.DistributedTracing: false,
121+
features.DistributedTracing: true,
113122
features.StopGRPCServiceOnDefrag: true,
114123
},
115124
},
116125
{
117-
name: "set both features to true",
118-
serverFeatureGatesJSON: "DistributedTracing=true,StopGRPCServiceOnDefrag=true",
126+
name: "can set feature gate to true from experimental flag",
127+
experimentalStopGRPCServiceOnDefrag: "true",
119128
expectedFeatures: map[featuregate.Feature]bool{
120-
features.DistributedTracing: true,
121129
features.StopGRPCServiceOnDefrag: true,
130+
features.DistributedTracing: false,
122131
},
123132
},
124133
{
125-
name: "error setting unrecognized feature",
126-
serverFeatureGatesJSON: "DistributedTracing=true,StopGRPCServiceOnDefragExp=true",
127-
expectErr: true,
134+
name: "can set feature gate to false from experimental flag",
135+
experimentalStopGRPCServiceOnDefrag: "false",
136+
expectedFeatures: map[featuregate.Feature]bool{
137+
features.StopGRPCServiceOnDefrag: false,
138+
features.DistributedTracing: false,
139+
},
140+
},
141+
{
142+
name: "can set feature gate to true from feature gate flag",
143+
serverFeatureGatesJSON: "StopGRPCServiceOnDefrag=true",
144+
expectedFeatures: map[featuregate.Feature]bool{
145+
features.StopGRPCServiceOnDefrag: true,
146+
features.DistributedTracing: false,
147+
},
148+
},
149+
{
150+
name: "can set feature gate to false from feature gate flag",
151+
serverFeatureGatesJSON: "StopGRPCServiceOnDefrag=false",
152+
expectedFeatures: map[featuregate.Feature]bool{
153+
features.StopGRPCServiceOnDefrag: false,
154+
features.DistributedTracing: false,
155+
},
128156
},
129157
}
130158
for _, tc := range testCases {
@@ -136,6 +164,13 @@ func TestConfigFileFeatureGates(t *testing.T) {
136164
ServerFeatureGatesJSON: tc.serverFeatureGatesJSON,
137165
}
138166

167+
if tc.experimentalStopGRPCServiceOnDefrag != "" {
168+
experimentalStopGRPCServiceOnDefrag, err := strconv.ParseBool(tc.experimentalStopGRPCServiceOnDefrag)
169+
if err != nil {
170+
t.Fatal(err)
171+
}
172+
yc.ExperimentalStopGRPCServiceOnDefrag = &experimentalStopGRPCServiceOnDefrag
173+
}
139174
b, err := yaml.Marshal(&yc)
140175
if err != nil {
141176
t.Fatal(err)
@@ -743,3 +778,87 @@ func TestUndefinedAutoCompactionModeValidate(t *testing.T) {
743778
err := cfg.Validate()
744779
require.Error(t, err)
745780
}
781+
782+
func TestSetFeatureGatesFromExperimentalFlags(t *testing.T) {
783+
testCases := []struct {
784+
name string
785+
featureGatesFlag string
786+
experimentalStopGRPCServiceOnDefrag string
787+
expectErr bool
788+
expectedFeatures map[featuregate.Feature]bool
789+
}{
790+
{
791+
name: "default",
792+
expectedFeatures: map[featuregate.Feature]bool{
793+
features.DistributedTracing: false,
794+
features.StopGRPCServiceOnDefrag: false,
795+
},
796+
},
797+
{
798+
name: "cannot set experimental flag and feature gate to true at the same time",
799+
featureGatesFlag: "StopGRPCServiceOnDefrag=true",
800+
experimentalStopGRPCServiceOnDefrag: "true",
801+
expectErr: true,
802+
},
803+
{
804+
name: "cannot set experimental flag and feature gate to false at the same time",
805+
featureGatesFlag: "StopGRPCServiceOnDefrag=false",
806+
experimentalStopGRPCServiceOnDefrag: "false",
807+
expectErr: true,
808+
},
809+
{
810+
name: "cannot set experimental flag and feature gate to different values at the same time",
811+
featureGatesFlag: "StopGRPCServiceOnDefrag=true",
812+
experimentalStopGRPCServiceOnDefrag: "false",
813+
expectErr: true,
814+
},
815+
{
816+
name: "can set experimental flag",
817+
featureGatesFlag: "DistributedTracing=true",
818+
experimentalStopGRPCServiceOnDefrag: "true",
819+
expectedFeatures: map[featuregate.Feature]bool{
820+
features.DistributedTracing: true,
821+
features.StopGRPCServiceOnDefrag: true,
822+
},
823+
},
824+
{
825+
name: "can set feature gate",
826+
featureGatesFlag: "DistributedTracing=true,StopGRPCServiceOnDefrag=true",
827+
expectedFeatures: map[featuregate.Feature]bool{
828+
features.DistributedTracing: true,
829+
features.StopGRPCServiceOnDefrag: true,
830+
},
831+
},
832+
}
833+
for _, tc := range testCases {
834+
t.Run(tc.name, func(t *testing.T) {
835+
fg := features.NewDefaultServerFeatureGate("test", nil)
836+
fg.(featuregate.MutableFeatureGate).Set(tc.featureGatesFlag)
837+
getExperimentalFlagVal := func(flagName string) *bool {
838+
if flagName != "experimental-stop-grpc-service-on-defrag" || tc.experimentalStopGRPCServiceOnDefrag == "" {
839+
return nil
840+
}
841+
flagVal, err := strconv.ParseBool(tc.experimentalStopGRPCServiceOnDefrag)
842+
if err != nil {
843+
t.Fatal(err)
844+
}
845+
return &flagVal
846+
}
847+
err := SetFeatureGatesFromExperimentalFlags(fg, getExperimentalFlagVal, "feature-gates", tc.featureGatesFlag)
848+
if tc.expectErr {
849+
if err == nil {
850+
t.Fatal("expect error")
851+
}
852+
return
853+
}
854+
if err != nil {
855+
t.Fatal(err)
856+
}
857+
for k, v := range tc.expectedFeatures {
858+
if fg.Enabled(k) != v {
859+
t.Errorf("expected feature gate %s=%v, got %v", k, v, fg.Enabled(k))
860+
}
861+
}
862+
})
863+
}
864+
}

server/embed/etcd.go

-1
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,6 @@ func StartEtcd(inCfg *Config) (e *Etcd, err error) {
223223
WarningUnaryRequestDuration: cfg.WarningUnaryRequestDuration,
224224
ExperimentalMemoryMlock: cfg.ExperimentalMemoryMlock,
225225
ExperimentalTxnModeWriteWithSharedBuffer: cfg.ExperimentalTxnModeWriteWithSharedBuffer,
226-
ExperimentalStopGRPCServiceOnDefrag: cfg.ExperimentalStopGRPCServiceOnDefrag,
227226
ExperimentalBootstrapDefragThresholdMegabytes: cfg.ExperimentalBootstrapDefragThresholdMegabytes,
228227
ExperimentalMaxLearners: cfg.ExperimentalMaxLearners,
229228
V2Deprecation: cfg.V2DeprecationEffective(),

server/etcdmain/config.go

+15
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,21 @@ func (cfg *config) configFromCmdLine() error {
238238
cfg.ec.InitialCluster = ""
239239
}
240240

241+
getBoolFlagVal := func(flagName string) *bool {
242+
boolVal, parseErr := flags.GetBoolFlagVal(cfg.cf.flagSet, flagName)
243+
if parseErr != nil {
244+
panic(parseErr)
245+
}
246+
return boolVal
247+
}
248+
249+
// SetFeatureGatesFromExperimentalFlags validates that cmd line flags for experimental feature and their feature gates are not explicitly set simultaneously,
250+
// and passes the values of cmd line flags for experimental feature to the server feature gate.
251+
err = embed.SetFeatureGatesFromExperimentalFlags(cfg.ec.ServerFeatureGate, getBoolFlagVal, embed.ServerFeatureGateFlagName, cfg.cf.flagSet.Lookup(embed.ServerFeatureGateFlagName).Value.String())
252+
if err != nil {
253+
return err
254+
}
255+
241256
return cfg.validate()
242257
}
243258

0 commit comments

Comments
 (0)