@@ -42,6 +42,7 @@ import (
42
42
"go.etcd.io/etcd/client/pkg/v3/transport"
43
43
"go.etcd.io/etcd/client/pkg/v3/types"
44
44
clientv3 "go.etcd.io/etcd/client/v3"
45
+ "go.etcd.io/etcd/pkg/v3/featuregate"
45
46
"go.etcd.io/etcd/pkg/v3/flags"
46
47
"go.etcd.io/etcd/pkg/v3/netutil"
47
48
"go.etcd.io/etcd/server/v3/config"
@@ -50,6 +51,7 @@ import (
50
51
"go.etcd.io/etcd/server/v3/etcdserver/api/rafthttp"
51
52
"go.etcd.io/etcd/server/v3/etcdserver/api/v3compactor"
52
53
"go.etcd.io/etcd/server/v3/etcdserver/api/v3discovery"
54
+ "go.etcd.io/etcd/server/v3/features"
53
55
)
54
56
55
57
const (
@@ -108,6 +110,8 @@ const (
108
110
maxElectionMs = 50000
109
111
// backend freelist map type
110
112
freelistArrayType = "array"
113
+
114
+ ServerFeatureGateFlagName = "server-feature-gates"
111
115
)
112
116
113
117
var (
@@ -455,6 +459,9 @@ type Config struct {
455
459
456
460
// V2Deprecation describes phase of API & Storage V2 support
457
461
V2Deprecation config.V2DeprecationEnum `json:"v2-deprecation"`
462
+
463
+ // ServerFeatureGate is a server level feature gate
464
+ ServerFeatureGate featuregate.FeatureGate
458
465
}
459
466
460
467
// configYAML holds the config suitable for yaml parsing
@@ -476,6 +483,8 @@ type configJSON struct {
476
483
477
484
ClientSecurityJSON securityConfig `json:"client-transport-security"`
478
485
PeerSecurityJSON securityConfig `json:"peer-transport-security"`
486
+
487
+ ServerFeatureGatesJSON string `json:"server-feature-gates"`
479
488
}
480
489
481
490
type securityConfig struct {
@@ -576,6 +585,7 @@ func NewConfig() *Config {
576
585
},
577
586
578
587
AutoCompactionMode : DefaultAutoCompactionMode ,
588
+ ServerFeatureGate : features .NewDefaultServerFeatureGate (DefaultName , nil ),
579
589
}
580
590
cfg .InitialCluster = cfg .InitialClusterFromName (cfg .Name )
581
591
return cfg
@@ -762,6 +772,9 @@ func (cfg *Config) AddFlags(fs *flag.FlagSet) {
762
772
// unsafe
763
773
fs .BoolVar (& cfg .UnsafeNoFsync , "unsafe-no-fsync" , false , "Disables fsync, unsafe, will cause data loss." )
764
774
fs .BoolVar (& cfg .ForceNewCluster , "force-new-cluster" , false , "Force to create a new one member cluster." )
775
+
776
+ // featuregate
777
+ cfg .ServerFeatureGate .(featuregate.MutableFeatureGate ).AddFlag (fs , ServerFeatureGateFlagName )
765
778
}
766
779
767
780
func ConfigFromFile (path string ) (* Config , error ) {
@@ -785,6 +798,26 @@ func (cfg *configYAML) configFromFile(path string) error {
785
798
return err
786
799
}
787
800
801
+ if cfg .configJSON .ServerFeatureGatesJSON != "" {
802
+ err = cfg .Config .ServerFeatureGate .(featuregate.MutableFeatureGate ).Set (cfg .configJSON .ServerFeatureGatesJSON )
803
+ if err != nil {
804
+ return err
805
+ }
806
+ }
807
+ var cfgMap map [string ]interface {}
808
+ err = yaml .Unmarshal (b , & cfgMap )
809
+ if err != nil {
810
+ return err
811
+ }
812
+ isExperimentalFlagSet := func (expFlag string ) bool {
813
+ _ , ok := cfgMap [expFlag ]
814
+ return ok
815
+ }
816
+ err = cfg .SetFeatureGatesFromExperimentalFlags (cfg .ServerFeatureGate , isExperimentalFlagSet , ServerFeatureGateFlagName , cfg .configJSON .ServerFeatureGatesJSON )
817
+ if err != nil {
818
+ return err
819
+ }
820
+
788
821
if cfg .configJSON .ListenPeerURLs != "" {
789
822
u , err := types .NewURLs (strings .Split (cfg .configJSON .ListenPeerURLs , "," ))
790
823
if err != nil {
@@ -877,6 +910,37 @@ func (cfg *configYAML) configFromFile(path string) error {
877
910
return cfg .Validate ()
878
911
}
879
912
913
+ // SetFeatureGatesFromExperimentalFlags sets the feature gate values if the feature gate is not explicitly set
914
+ // while their corresponding experimental flags are explicitly set.
915
+ // TODO: remove after all experimental flags are deprecated.
916
+ func (cfg * Config ) SetFeatureGatesFromExperimentalFlags (fg featuregate.FeatureGate , isExperimentalFlagSet func (string ) bool , flagName , featureGatesVal string ) error {
917
+ // verify that the feature gate and its experimental flag are not both set at the same time.
918
+ for expFlagName , featureName := range features .ExperimentalFlagToFeatureMap {
919
+ if isExperimentalFlagSet (expFlagName ) && strings .Contains (featureGatesVal , string (featureName )) {
920
+ return fmt .Errorf ("cannot specify both flags: --%s=(true|false) and --%s=%s=(true|false) at the same time, please just use --%s=%s=(true|false)" ,
921
+ expFlagName , flagName , featureName , flagName , featureName )
922
+ }
923
+ }
924
+
925
+ m := make (map [featuregate.Feature ]bool )
926
+ defaultEc := NewConfig ()
927
+ // if a ExperimentalXX config is different from the default value, that means the experimental flag is explicitly set.
928
+ // We need to pass that into the feature gate.
929
+ // This section should include all the experimental flag configs that are still in use.
930
+ if cfg .ExperimentalStopGRPCServiceOnDefrag != defaultEc .ExperimentalStopGRPCServiceOnDefrag {
931
+ m [features .StopGRPCServiceOnDefrag ] = cfg .ExperimentalStopGRPCServiceOnDefrag
932
+ }
933
+ // filter out unknown features for fg
934
+ allFeatures := fg .(featuregate.MutableFeatureGate ).GetAll ()
935
+ mFiltered := make (map [string ]bool )
936
+ for k , v := range m {
937
+ if _ , ok := allFeatures [k ]; ok {
938
+ mFiltered [string (k )] = v
939
+ }
940
+ }
941
+ return fg .(featuregate.MutableFeatureGate ).SetFromMap (mFiltered )
942
+ }
943
+
880
944
func updateCipherSuites (tls * transport.TLSInfo , ss []string ) error {
881
945
if len (tls .CipherSuites ) > 0 && len (ss ) > 0 {
882
946
return fmt .Errorf ("TLSInfo.CipherSuites is already specified (given %v)" , ss )
@@ -907,6 +971,7 @@ func (cfg *Config) Validate() error {
907
971
if err := cfg .setupLogging (); err != nil {
908
972
return err
909
973
}
974
+ cfg .ServerFeatureGate .(featuregate.MutableFeatureGate ).SetLogger (cfg .logger )
910
975
if err := checkBindURLs (cfg .ListenPeerUrls ); err != nil {
911
976
return err
912
977
}
0 commit comments