diff --git a/pkg/featuregate/feature_gate.go b/pkg/featuregate/feature_gate.go index b9e0175bef2..cb77017b5a7 100644 --- a/pkg/featuregate/feature_gate.go +++ b/pkg/featuregate/feature_gate.go @@ -91,6 +91,8 @@ type FeatureGate interface { // set on the copy without mutating the original. This is useful for validating // config against potential feature gate changes before committing those changes. DeepCopy() MutableFeatureGate + // String returns a string containing all enabled feature gates, formatted as "key1=value1,key2=value2,...". + String() string } // MutableFeatureGate parses and stores flag gates for known features from diff --git a/server/config/config.go b/server/config/config.go index b4a8f61a575..dee41b86de5 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -147,10 +147,9 @@ type ServerConfig struct { // InitialCorruptCheck is true to check data corruption on boot // before serving any peer/client traffic. - InitialCorruptCheck bool - CorruptCheckTime time.Duration - CompactHashCheckEnabled bool - CompactHashCheckTime time.Duration + InitialCorruptCheck bool + CorruptCheckTime time.Duration + CompactHashCheckTime time.Duration // PreVote is true to enable Raft Pre-Vote. PreVote bool diff --git a/server/embed/config.go b/server/embed/config.go index 1a4d1c41b06..4567e7a3fc9 100644 --- a/server/embed/config.go +++ b/server/embed/config.go @@ -58,21 +58,21 @@ const ( ClusterStateFlagNew = "new" ClusterStateFlagExisting = "existing" - DefaultName = "default" - DefaultMaxSnapshots = 5 - DefaultMaxWALs = 5 - DefaultMaxTxnOps = uint(128) - DefaultWarningApplyDuration = 100 * time.Millisecond - DefaultWarningUnaryRequestDuration = 300 * time.Millisecond - DefaultMaxRequestBytes = 1.5 * 1024 * 1024 - DefaultMaxConcurrentStreams = math.MaxUint32 - DefaultGRPCKeepAliveMinTime = 5 * time.Second - DefaultGRPCKeepAliveInterval = 2 * time.Hour - DefaultGRPCKeepAliveTimeout = 20 * time.Second - DefaultDowngradeCheckTime = 5 * time.Second - DefaultAutoCompactionMode = "periodic" - DefaultAuthToken = "simple" - DefaultExperimentalCompactHashCheckTime = time.Minute + DefaultName = "default" + DefaultMaxSnapshots = 5 + DefaultMaxWALs = 5 + DefaultMaxTxnOps = uint(128) + DefaultWarningApplyDuration = 100 * time.Millisecond + DefaultWarningUnaryRequestDuration = 300 * time.Millisecond + DefaultMaxRequestBytes = 1.5 * 1024 * 1024 + DefaultMaxConcurrentStreams = math.MaxUint32 + DefaultGRPCKeepAliveMinTime = 5 * time.Second + DefaultGRPCKeepAliveInterval = 2 * time.Hour + DefaultGRPCKeepAliveTimeout = 20 * time.Second + DefaultDowngradeCheckTime = 5 * time.Second + DefaultAutoCompactionMode = "periodic" + DefaultAuthToken = "simple" + DefaultCompactHashCheckTime = time.Minute DefaultDiscoveryDialTimeout = 2 * time.Second DefaultDiscoveryRequestTimeOut = 5 * time.Second @@ -128,6 +128,13 @@ var ( // indirection for testing getCluster = srv.GetCluster + + // in 3.6, we are migration all the --experimental flags to feature gate and flags without the prefix. + // This is the mapping from the non boolean `experimental-` to the new flags. + // TODO: delete in v3.7 + experimentalNonBoolFlagMigrationMap = map[string]string{ + "experimental-compact-hash-check-time": "compact-hash-check-time", + } ) var ( @@ -356,10 +363,17 @@ type Config struct { // AuthTokenTTL in seconds of the simple token AuthTokenTTL uint `json:"auth-token-ttl"` - ExperimentalInitialCorruptCheck bool `json:"experimental-initial-corrupt-check"` - ExperimentalCorruptCheckTime time.Duration `json:"experimental-corrupt-check-time"` - ExperimentalCompactHashCheckEnabled bool `json:"experimental-compact-hash-check-enabled"` - ExperimentalCompactHashCheckTime time.Duration `json:"experimental-compact-hash-check-time"` + ExperimentalInitialCorruptCheck bool `json:"experimental-initial-corrupt-check"` + ExperimentalCorruptCheckTime time.Duration `json:"experimental-corrupt-check-time"` + // ExperimentalCompactHashCheckEnabled enables leader to periodically check followers compaction hashes. + // Deprecated in v3.6 and will be decommissioned in v3.7. + // TODO: delete in v3.7 + ExperimentalCompactHashCheckEnabled bool `json:"experimental-compact-hash-check-enabled"` + // ExperimentalCompactHashCheckTime is the duration of time between leader checks followers compaction hashes. + // Deprecated in v3.6 and will be decommissioned in v3.7. + // TODO: delete in v3.7 + ExperimentalCompactHashCheckTime time.Duration `json:"experimental-compact-hash-check-time"` + CompactHashCheckTime time.Duration `json:"compact-hash-check-time"` // ExperimentalEnableLeaseCheckpoint enables leader to send regular checkpoints to other members to prevent reset of remaining TTL on leader change. ExperimentalEnableLeaseCheckpoint bool `json:"experimental-enable-lease-checkpoint"` @@ -468,6 +482,8 @@ type Config struct { // ServerFeatureGate is a server level feature gate ServerFeatureGate featuregate.FeatureGate + // FlagsExplicitlySet stores if a flag is explicitly set from the cmd line or config file. + FlagsExplicitlySet map[string]bool } // configYAML holds the config suitable for yaml parsing @@ -573,8 +589,9 @@ func NewConfig() *Config { ExperimentalStopGRPCServiceOnDefrag: false, ExperimentalMaxLearners: membership.DefaultMaxLearners, - ExperimentalCompactHashCheckEnabled: false, - ExperimentalCompactHashCheckTime: DefaultExperimentalCompactHashCheckTime, + CompactHashCheckTime: DefaultCompactHashCheckTime, + // TODO: delete in v3.7 + ExperimentalCompactHashCheckTime: DefaultCompactHashCheckTime, V2Deprecation: config.V2DeprDefault, @@ -592,6 +609,7 @@ func NewConfig() *Config { AutoCompactionMode: DefaultAutoCompactionMode, ServerFeatureGate: features.NewDefaultServerFeatureGate(DefaultName, nil), + FlagsExplicitlySet: map[string]bool{}, } cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name) return cfg @@ -755,8 +773,11 @@ func (cfg *Config) AddFlags(fs *flag.FlagSet) { // experimental fs.BoolVar(&cfg.ExperimentalInitialCorruptCheck, "experimental-initial-corrupt-check", cfg.ExperimentalInitialCorruptCheck, "Enable to check data corruption before serving any client/peer traffic.") fs.DurationVar(&cfg.ExperimentalCorruptCheckTime, "experimental-corrupt-check-time", cfg.ExperimentalCorruptCheckTime, "Duration of time between cluster corruption check passes.") - fs.BoolVar(&cfg.ExperimentalCompactHashCheckEnabled, "experimental-compact-hash-check-enabled", cfg.ExperimentalCompactHashCheckEnabled, "Enable leader to periodically check followers compaction hashes.") - fs.DurationVar(&cfg.ExperimentalCompactHashCheckTime, "experimental-compact-hash-check-time", cfg.ExperimentalCompactHashCheckTime, "Duration of time between leader checks followers compaction hashes.") + // TODO: delete in v3.7 + fs.BoolVar(&cfg.ExperimentalCompactHashCheckEnabled, "experimental-compact-hash-check-enabled", cfg.ExperimentalCompactHashCheckEnabled, "Enable leader to periodically check followers compaction hashes. Deprecated in v3.6 and will be decommissioned in v3.7. Use '--feature-gates=CompactHashCheck=true' instead") + fs.DurationVar(&cfg.ExperimentalCompactHashCheckTime, "experimental-compact-hash-check-time", cfg.ExperimentalCompactHashCheckTime, "Duration of time between leader checks followers compaction hashes. Deprecated in v3.6 and will be decommissioned in v3.7. Use --compact-hash-check-time instead.") + + fs.DurationVar(&cfg.CompactHashCheckTime, "compact-hash-check-time", cfg.CompactHashCheckTime, "Duration of time between leader checks followers compaction hashes.") fs.BoolVar(&cfg.ExperimentalEnableLeaseCheckpoint, "experimental-enable-lease-checkpoint", false, "Enable leader to send regular checkpoints to other members to prevent reset of remaining TTL on leader change.") // TODO: delete in v3.7 @@ -817,6 +838,11 @@ func (cfg *configYAML) configFromFile(path string) error { if err != nil { return err } + + for flg := range cfgMap { + cfg.FlagsExplicitlySet[flg] = true + } + getBoolFlagVal := func(flagName string) *bool { flagVal, ok := cfgMap[flagName] if !ok { @@ -979,6 +1005,14 @@ func updateMinMaxVersions(info *transport.TLSInfo, min, max string) { // Validate ensures that '*embed.Config' fields are properly configured. func (cfg *Config) Validate() error { + // make sure there is no conflict in the flag settings in the ExperimentalNonBoolFlagMigrationMap + // TODO: delete in v3.7 + for oldFlag, newFlag := range experimentalNonBoolFlagMigrationMap { + if cfg.FlagsExplicitlySet[oldFlag] && cfg.FlagsExplicitlySet[newFlag] { + return fmt.Errorf("cannot set %s and %s at the same time in the config file, please use %s only", oldFlag, newFlag, newFlag) + } + } + if err := cfg.setupLogging(); err != nil { return err } @@ -1083,9 +1117,12 @@ func (cfg *Config) Validate() error { if cfg.ExperimentalEnableLeaseCheckpointPersist && !cfg.ExperimentalEnableLeaseCheckpoint { return fmt.Errorf("setting experimental-enable-lease-checkpoint-persist requires experimental-enable-lease-checkpoint") } - + // TODO: delete in v3.7 if cfg.ExperimentalCompactHashCheckTime <= 0 { - return fmt.Errorf("--experimental-compact-hash-check-time must be >0 (set to %v)", cfg.ExperimentalCompactHashCheckTime) + return fmt.Errorf("--experimental-compact-hash-check-time must be >=0 (set to %v)", cfg.ExperimentalCompactHashCheckTime) + } + if cfg.CompactHashCheckTime <= 0 { + return fmt.Errorf("--compact-hash-check-time must be >0 (set to %v)", cfg.CompactHashCheckTime) } // If `--name` isn't configured, then multiple members may have the same "default" name. diff --git a/server/embed/config_test.go b/server/embed/config_test.go index 8ba8e17dd09..e25fb743045 100644 --- a/server/embed/config_test.go +++ b/server/embed/config_test.go @@ -98,6 +98,7 @@ func TestConfigFileFeatureGates(t *testing.T) { serverFeatureGatesJSON string experimentalStopGRPCServiceOnDefrag string experimentalInitialCorruptCheck string + experimentalCompactHashCheckEnabled string expectErr bool expectedFeatures map[featuregate.Feature]bool }{ @@ -194,12 +195,46 @@ func TestConfigFileFeatureGates(t *testing.T) { features.InitialCorruptCheck: false, }, }, + { + name: "cannot set both experimental flag and feature gate flag for ExperimentalCompactHashCheckEnabled", + serverFeatureGatesJSON: "CompactHashCheck=true", + experimentalCompactHashCheckEnabled: "false", + expectErr: true, + }, + { + name: "can set feature gate experimentalCompactHashCheckEnabled to true from experimental flag", + experimentalCompactHashCheckEnabled: "true", + expectedFeatures: map[featuregate.Feature]bool{ + features.StopGRPCServiceOnDefrag: false, + features.DistributedTracing: false, + features.CompactHashCheck: true, + }, + }, + { + name: "can set feature gate experimentalCompactHashCheckEnabled to false from experimental flag", + experimentalCompactHashCheckEnabled: "false", + expectedFeatures: map[featuregate.Feature]bool{ + features.StopGRPCServiceOnDefrag: false, + features.DistributedTracing: false, + features.CompactHashCheck: false, + }, + }, + { + name: "can set feature gate CompactHashCheck to true from feature gate flag", + serverFeatureGatesJSON: "CompactHashCheck=true", + expectedFeatures: map[featuregate.Feature]bool{ + features.StopGRPCServiceOnDefrag: false, + features.DistributedTracing: false, + features.CompactHashCheck: true, + }, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { yc := struct { ExperimentalStopGRPCServiceOnDefrag *bool `json:"experimental-stop-grpc-service-on-defrag,omitempty"` ExperimentalInitialCorruptCheck *bool `json:"experimental-initial-corrupt-check,omitempty"` + ExperimentalCompactHashCheckEnabled *bool `json:"experimental-compact-hash-check-enabled,omitempty"` ServerFeatureGatesJSON string `json:"feature-gates"` }{ ServerFeatureGatesJSON: tc.serverFeatureGatesJSON, @@ -221,6 +256,14 @@ func TestConfigFileFeatureGates(t *testing.T) { yc.ExperimentalStopGRPCServiceOnDefrag = &experimentalStopGRPCServiceOnDefrag } + if tc.experimentalCompactHashCheckEnabled != "" { + experimentalCompactHashCheckEnabled, err := strconv.ParseBool(tc.experimentalCompactHashCheckEnabled) + if err != nil { + t.Fatal(err) + } + yc.ExperimentalCompactHashCheckEnabled = &experimentalCompactHashCheckEnabled + } + b, err := yaml.Marshal(&yc) if err != nil { t.Fatal(err) diff --git a/server/embed/etcd.go b/server/embed/etcd.go index 81d9962adde..b69f7928472 100644 --- a/server/embed/etcd.go +++ b/server/embed/etcd.go @@ -205,8 +205,7 @@ func StartEtcd(inCfg *Config) (e *Etcd, err error) { CORS: cfg.CORS, HostWhitelist: cfg.HostWhitelist, CorruptCheckTime: cfg.ExperimentalCorruptCheckTime, - CompactHashCheckEnabled: cfg.ExperimentalCompactHashCheckEnabled, - CompactHashCheckTime: cfg.ExperimentalCompactHashCheckTime, + CompactHashCheckTime: cfg.CompactHashCheckTime, PreVote: cfg.PreVote, Logger: cfg.logger, ForceNewCluster: cfg.ForceNewCluster, @@ -351,9 +350,9 @@ func print(lg *zap.Logger, ec Config, sc config.ServerConfig, memberInitialized zap.Uint32("max-concurrent-streams", sc.MaxConcurrentStreams), zap.Bool("pre-vote", sc.PreVote), + zap.String(ServerFeatureGateFlagName, sc.ServerFeatureGate.String()), zap.Bool("initial-corrupt-check", sc.InitialCorruptCheck), zap.String("corrupt-check-time-interval", sc.CorruptCheckTime.String()), - zap.Bool("compact-check-time-enabled", sc.CompactHashCheckEnabled), zap.Duration("compact-check-time-interval", sc.CompactHashCheckTime), zap.String("auto-compaction-mode", sc.AutoCompactionMode), zap.Duration("auto-compaction-retention", sc.AutoCompactionRetention), diff --git a/server/etcdmain/config.go b/server/etcdmain/config.go index af42134fe82..42914b3fd1d 100644 --- a/server/etcdmain/config.go +++ b/server/etcdmain/config.go @@ -62,6 +62,8 @@ var ( "snapshot-count": "--snapshot-count is deprecated in 3.6 and will be decommissioned in 3.7.", "max-snapshots": "--max-snapshots is deprecated in 3.6 and will be decommissioned in 3.7.", "v2-deprecation": "--v2-deprecation is deprecated and scheduled for removal in v3.8. The default value is enforced, ignoring user input.", + "experimental-compact-hash-check-enabled": "--experimental-compact-hash-check-enabled is deprecated in 3.6 and will be decommissioned in 3.7.", + "experimental-compact-hash-check-time": "--experimental-compact-hash-check-time is deprecated in 3.6 and will be decommissioned in 3.7.", } ) @@ -165,6 +167,12 @@ func (cfg *config) parse(arguments []string) error { err = cfg.configFromCmdLine() } + // params related to experimental flag deprecation + // TODO: delete in v3.7 + if cfg.ec.FlagsExplicitlySet["experimental-compact-hash-check-time"] { + cfg.ec.CompactHashCheckTime = cfg.ec.ExperimentalCompactHashCheckTime + } + // `V2Deprecation` (--v2-deprecation) is deprecated and scheduled for removal in v3.8. The default value is enforced, ignoring user input. cfg.ec.V2Deprecation = cconfig.V2DeprDefault @@ -260,6 +268,10 @@ func (cfg *config) configFromCmdLine() error { cfg.ec.InitialCluster = "" } + cfg.cf.flagSet.Visit(func(f *flag.Flag) { + cfg.ec.FlagsExplicitlySet[f.Name] = true + }) + getBoolFlagVal := func(flagName string) *bool { boolVal, parseErr := flags.GetBoolFlagVal(cfg.cf.flagSet, flagName) if parseErr != nil { diff --git a/server/etcdmain/config_test.go b/server/etcdmain/config_test.go index f002102897a..b834f3d33ea 100644 --- a/server/etcdmain/config_test.go +++ b/server/etcdmain/config_test.go @@ -23,6 +23,7 @@ import ( "reflect" "strings" "testing" + "time" "sigs.k8s.io/yaml" @@ -475,6 +476,99 @@ func TestParseFeatureGateFlags(t *testing.T) { } } +// TestCompactHashCheckTimeFlagMigration tests the migration from +// --experimental-compact-hash-check-time to --compact-hash-check-time +// TODO: delete in v3.7 +func TestCompactHashCheckTimeFlagMigration(t *testing.T) { + testCases := []struct { + name string + compactHashCheckTime string + experimentalCompactHashCheckTime string + useConfigFile bool + expectErr bool + expectedCompactHashCheckTime time.Duration + }{ + { + name: "default", + expectedCompactHashCheckTime: time.Minute, + }, + { + name: "cannot set both experimental flag and non experimental flag", + compactHashCheckTime: "2m", + experimentalCompactHashCheckTime: "3m", + expectErr: true, + }, + { + name: "can set experimental flag", + experimentalCompactHashCheckTime: "3m", + expectedCompactHashCheckTime: 3 * time.Minute, + }, + { + name: "can set non experimental flag", + compactHashCheckTime: "2m", + expectedCompactHashCheckTime: 2 * time.Minute, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + cmdLineArgs := []string{} + yc := struct { + ExperimentalCompactHashCheckTime time.Duration `json:"experimental-compact-hash-check-time,omitempty"` + CompactHashCheckTime time.Duration `json:"compact-hash-check-time,omitempty"` + }{} + + if tc.compactHashCheckTime != "" { + cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--compact-hash-check-time=%s", tc.compactHashCheckTime)) + compactHashCheckTime, err := time.ParseDuration(tc.compactHashCheckTime) + if err != nil { + t.Fatal(err) + } + yc.CompactHashCheckTime = compactHashCheckTime + } + + if tc.experimentalCompactHashCheckTime != "" { + cmdLineArgs = append(cmdLineArgs, fmt.Sprintf("--experimental-compact-hash-check-time=%s", tc.experimentalCompactHashCheckTime)) + experimentalCompactHashCheckTime, err := time.ParseDuration(tc.experimentalCompactHashCheckTime) + if err != nil { + t.Fatal(err) + } + yc.ExperimentalCompactHashCheckTime = experimentalCompactHashCheckTime + } + + b, err := yaml.Marshal(&yc) + if err != nil { + t.Fatal(err) + } + + tmpfile := mustCreateCfgFile(t, b) + defer os.Remove(tmpfile.Name()) + + cfgFromCmdLine := newConfig() + errFromCmdLine := cfgFromCmdLine.parse(cmdLineArgs) + + cfgFromFile := newConfig() + errFromFile := cfgFromFile.parse([]string{fmt.Sprintf("--config-file=%s", tmpfile.Name())}) + + if tc.expectErr { + if errFromCmdLine == nil || errFromFile == nil { + t.Fatal("expect parse error") + } + return + } + if errFromCmdLine != nil || errFromFile != nil { + t.Fatal(err) + } + + if cfgFromCmdLine.ec.CompactHashCheckTime != tc.expectedCompactHashCheckTime { + t.Errorf("expected CompactHashCheckTime=%v, got %v", tc.expectedCompactHashCheckTime, cfgFromCmdLine.ec.CompactHashCheckTime) + } + if cfgFromFile.ec.CompactHashCheckTime != tc.expectedCompactHashCheckTime { + t.Errorf("expected CompactHashCheckTime=%v, got %v", tc.expectedCompactHashCheckTime, cfgFromFile.ec.CompactHashCheckTime) + } + }) + } +} + func mustCreateCfgFile(t *testing.T, b []byte) *os.File { tmpfile, err := os.CreateTemp("", "servercfg") if err != nil { diff --git a/server/etcdmain/help.go b/server/etcdmain/help.go index 9a87ff52f7b..afc85f63efc 100644 --- a/server/etcdmain/help.go +++ b/server/etcdmain/help.go @@ -277,9 +277,11 @@ Experimental feature: Enable to check data corruption before serving any client/peer traffic. --experimental-corrupt-check-time '0s' Duration of time between cluster corruption check passes. - --experimental-compact-hash-check-enabled 'false' + --experimental-compact-hash-check-enabled 'false'. It's deprecated, and will be decommissioned in v3.7. Use '--feature-gates=CompactHashCheck=true' instead. Enable leader to periodically check followers compaction hashes. --experimental-compact-hash-check-time '1m' + Duration of time between leader checks followers compaction hashes. It's deprecated, and will be decommissioned in v3.7. Use '--compact-hash-check-time' instead. + --compact-hash-check-time '1m' Duration of time between leader checks followers compaction hashes. --experimental-enable-lease-checkpoint 'false' ExperimentalEnableLeaseCheckpoint enables primary lessor to persist lease remainingTTL to prevent indefinite auto-renewal of long lived leases. diff --git a/server/etcdserver/server.go b/server/etcdserver/server.go index 2677e929f98..72f51723195 100644 --- a/server/etcdserver/server.go +++ b/server/etcdserver/server.go @@ -64,6 +64,7 @@ import ( "go.etcd.io/etcd/server/v3/etcdserver/errors" "go.etcd.io/etcd/server/v3/etcdserver/txn" serverversion "go.etcd.io/etcd/server/v3/etcdserver/version" + "go.etcd.io/etcd/server/v3/features" "go.etcd.io/etcd/server/v3/lease" "go.etcd.io/etcd/server/v3/lease/leasehttp" serverstorage "go.etcd.io/etcd/server/v3/storage" @@ -2328,7 +2329,7 @@ func (s *EtcdServer) monitorKVHash() { } func (s *EtcdServer) monitorCompactHash() { - if !s.Cfg.CompactHashCheckEnabled { + if !s.FeatureEnabled(features.CompactHashCheck) { return } t := s.Cfg.CompactHashCheckTime diff --git a/server/features/etcd_features.go b/server/features/etcd_features.go index abb1aa171f8..3e6fb4ec2f0 100644 --- a/server/features/etcd_features.go +++ b/server/features/etcd_features.go @@ -45,11 +45,16 @@ const ( // alpha: v3.6 // main PR: https://github.com/etcd-io/etcd/pull/18279 StopGRPCServiceOnDefrag featuregate.Feature = "StopGRPCServiceOnDefrag" - // InitialCorruptCheck enable to check data corruption before serving any client/peer traffic. + // InitialCorruptCheck enables to check data corruption before serving any client/peer traffic. // owner: @serathius // alpha: v3.6 // main PR: https://github.com/etcd-io/etcd/pull/10524 InitialCorruptCheck featuregate.Feature = "InitialCorruptCheck" + // CompactHashCheck enables leader to periodically check followers compaction hashes. + // owner: @serathius + // alpha: v3.6 + // main PR: https://github.com/etcd-io/etcd/pull/14120 + CompactHashCheck featuregate.Feature = "CompactHashCheck" ) var ( @@ -57,6 +62,7 @@ var ( DistributedTracing: {Default: false, PreRelease: featuregate.Alpha}, StopGRPCServiceOnDefrag: {Default: false, PreRelease: featuregate.Alpha}, InitialCorruptCheck: {Default: false, PreRelease: featuregate.Alpha}, + CompactHashCheck: {Default: false, PreRelease: featuregate.Alpha}, } // ExperimentalFlagToFeatureMap is the map from the cmd line flags of experimental features // to their corresponding feature gates. @@ -64,6 +70,7 @@ var ( ExperimentalFlagToFeatureMap = map[string]featuregate.Feature{ "experimental-stop-grpc-service-on-defrag": StopGRPCServiceOnDefrag, "experimental-initial-corrupt-check": InitialCorruptCheck, + "experimental-compact-hash-check-enabled": CompactHashCheck, } ) diff --git a/tests/e2e/corrupt_test.go b/tests/e2e/corrupt_test.go index 9f8e1e56d3a..9b71c8dc609 100644 --- a/tests/e2e/corrupt_test.go +++ b/tests/e2e/corrupt_test.go @@ -228,15 +228,25 @@ func TestPeriodicCheckDetectsCorruption(t *testing.T) { } func TestCompactHashCheckDetectCorruption(t *testing.T) { + testCompactHashCheckDetectCorruption(t, false) +} + +func TestCompactHashCheckDetectCorruptionWithFeatureGate(t *testing.T) { + testCompactHashCheckDetectCorruption(t, true) +} + +func testCompactHashCheckDetectCorruption(t *testing.T, useFeatureGate bool) { checkTime := time.Second e2e.BeforeTest(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - epc, err := e2e.NewEtcdProcessCluster(ctx, t, - e2e.WithKeepDataDir(true), - e2e.WithCompactHashCheckEnabled(true), - e2e.WithCompactHashCheckTime(checkTime), - ) + opts := []e2e.EPClusterOption{e2e.WithKeepDataDir(true), e2e.WithCompactHashCheckTime(checkTime)} + if useFeatureGate { + opts = append(opts, e2e.WithServerFeatureGate("CompactHashCheck", true)) + } else { + opts = append(opts, e2e.WithCompactHashCheckEnabled(true)) + } + epc, err := e2e.NewEtcdProcessCluster(ctx, t, opts...) if err != nil { t.Fatalf("could not start etcd process cluster (%v)", err) } @@ -270,6 +280,14 @@ func TestCompactHashCheckDetectCorruption(t *testing.T) { } func TestCompactHashCheckDetectCorruptionInterrupt(t *testing.T) { + testCompactHashCheckDetectCorruptionInterrupt(t, false) +} + +func TestCompactHashCheckDetectCorruptionInterruptWithFeatureGate(t *testing.T) { + testCompactHashCheckDetectCorruptionInterrupt(t, true) +} + +func testCompactHashCheckDetectCorruptionInterrupt(t *testing.T, useFeatureGate bool) { checkTime := time.Second e2e.BeforeTest(t) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) @@ -281,14 +299,20 @@ func TestCompactHashCheckDetectCorruptionInterrupt(t *testing.T) { t.Log("creating a new cluster with 3 nodes...") dataDirPath := t.TempDir() - cfg := e2e.NewConfig( + opts := []e2e.EPClusterOption{ e2e.WithKeepDataDir(true), - e2e.WithCompactHashCheckEnabled(true), e2e.WithCompactHashCheckTime(checkTime), e2e.WithClusterSize(3), e2e.WithDataDirPath(dataDirPath), e2e.WithLogLevel("info"), - ) + } + if useFeatureGate { + opts = append(opts, e2e.WithServerFeatureGate("CompactHashCheck", true)) + } else { + opts = append(opts, e2e.WithCompactHashCheckEnabled(true)) + } + + cfg := e2e.NewConfig(opts...) epc, err := e2e.InitEtcdProcessCluster(t, cfg) require.NoError(t, err) diff --git a/tests/framework/e2e/cluster.go b/tests/framework/e2e/cluster.go index 4aff11b9d6f..083dcc7a077 100644 --- a/tests/framework/e2e/cluster.go +++ b/tests/framework/e2e/cluster.go @@ -328,7 +328,7 @@ func WithCompactHashCheckEnabled(enabled bool) EPClusterOption { } func WithCompactHashCheckTime(time time.Duration) EPClusterOption { - return func(c *EtcdProcessClusterConfig) { c.ServerConfig.ExperimentalCompactHashCheckTime = time } + return func(c *EtcdProcessClusterConfig) { c.ServerConfig.CompactHashCheckTime = time } } func WithGoFailEnabled(enabled bool) EPClusterOption {