-
Notifications
You must be signed in to change notification settings - Fork 180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introducing FeatureGates and global feature config #344
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package config | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
) | ||
|
||
var cfg *Config | ||
|
||
// Config defines config structure | ||
type Config struct { | ||
FeatureGates map[string]bool `json:"featureGates,omitempty"` | ||
} | ||
|
||
// NewConfig creates new config | ||
func NewConfig() { | ||
if cfg == nil { | ||
cfg = newConfig() | ||
} | ||
} | ||
|
||
// GetConfig returns config | ||
func GetConfig() (*Config, error) { | ||
var err error | ||
if cfg == nil { | ||
err = fmt.Errorf("config was not initialized") | ||
} | ||
return cfg, err | ||
} | ||
|
||
func newConfig() *Config { | ||
if cfg != nil { | ||
return cfg | ||
} | ||
newCfg := &Config{} | ||
newCfg.FeatureGates = make(map[string]bool) | ||
return newCfg | ||
} | ||
|
||
// ReadConfig loads config | ||
func (cfg *Config) ReadConfig(configFile string) error { | ||
allCfg := make(map[string]json.RawMessage) | ||
rawBytes, err := os.ReadFile(configFile) | ||
|
||
if err != nil { | ||
return fmt.Errorf("error reading file %s, %v", configFile, err) | ||
} | ||
|
||
if err = json.Unmarshal(rawBytes, &allCfg); err != nil { | ||
return fmt.Errorf("error unmarshalling raw bytes %v", err) | ||
} | ||
|
||
fgMap := make(map[string]bool) | ||
|
||
if _, exists := allCfg["featureGates"]; !exists { | ||
return fmt.Errorf("no config for feature gate present") | ||
} | ||
|
||
if err = json.Unmarshal(allCfg["featureGates"], &fgMap); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't you check if "featureGates" key is in the map before indexing? Potential panic if it isn't available. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
return fmt.Errorf("error unmarshalling raw bytes %v", err) | ||
} | ||
|
||
for k, v := range fgMap { | ||
cfg.FeatureGates[k] = v | ||
} | ||
|
||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
package features | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/golang/glog" | ||
) | ||
|
||
// FeatureGate is feature gate 'manager' to be used | ||
var fg *FeatureGate | ||
|
||
// List of supported feature maturity levels | ||
const ( | ||
// Alpha - alpha version | ||
Alpha = string("ALPHA") | ||
|
||
// GA - general availability | ||
GA = string("GA") | ||
|
||
// Deprecated - feature that will be deprecated in 2 releases | ||
Deprecated = string("DEPRECATED") | ||
|
||
splitLength = 2 | ||
) | ||
|
||
// List of supported features | ||
const ( | ||
// AlphaFeature - description | ||
AlphaFeature string = "alphaFeature" | ||
|
||
// BetaFeature - description | ||
BetaFeature string = "betaFeature" | ||
|
||
// GaFeature - description | ||
GaFeature string = "gaFeature" | ||
|
||
// DeprecatedFeature - description | ||
DeprecatedFeature string = "deprecatedFeature" | ||
) | ||
|
||
type featureSpec struct { | ||
// Default is the default enablement state for the feature | ||
Default bool | ||
// Maturity indicates the maturity level of the feature | ||
Maturity string | ||
} | ||
|
||
var defaultSriovDpFeatureGates = map[string]featureSpec{ | ||
AlphaFeature: {Default: false, Maturity: Alpha}, | ||
GaFeature: {Default: true, Maturity: GA}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GaFeature -> GAFeature |
||
DeprecatedFeature: {Default: false, Maturity: Deprecated}, | ||
} | ||
|
||
// FeatureGate defines FeatureGate structure | ||
type FeatureGate struct { | ||
knownFeatures map[string]featureSpec | ||
enabled map[string]bool | ||
} | ||
|
||
// NewFeatureGate creates new FeatureGate if it does not exist yet | ||
func NewFeatureGate() { | ||
fg = newFeatureGate() | ||
} | ||
|
||
// GetFeatureGate returns current feature gate | ||
func GetFeatureGate() (*FeatureGate, error) { | ||
var err error | ||
if fg == nil { | ||
err = fmt.Errorf("feature gate object was not initialized") | ||
} | ||
return fg, err | ||
} | ||
|
||
func newFeatureGate() *FeatureGate { | ||
if fg != nil { | ||
return fg | ||
} | ||
fg := &FeatureGate{} | ||
fg.knownFeatures = make(map[string]featureSpec) | ||
fg.enabled = make(map[string]bool) | ||
|
||
for k, v := range defaultSriovDpFeatureGates { | ||
fg.knownFeatures[k] = v | ||
} | ||
|
||
for k, v := range fg.knownFeatures { | ||
fg.enabled[k] = v.Default | ||
} | ||
return fg | ||
} | ||
|
||
// Enabled returns enabelement status of the provided feature | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. enabelement -> enablement |
||
func (fg *FeatureGate) Enabled(featureName string) bool { | ||
return fg.enabled[featureName] | ||
} | ||
|
||
func (fg *FeatureGate) isFeatureSupported(featureName string) bool { | ||
_, exists := fg.knownFeatures[featureName] | ||
return exists | ||
} | ||
|
||
func (fg *FeatureGate) set(featureName string, status bool) error { | ||
if !fg.isFeatureSupported(featureName) { | ||
return fmt.Errorf("feature %s is not supported", featureName) | ||
} | ||
fg.enabled[featureName] = status | ||
if status && fg.knownFeatures[featureName].Maturity == Deprecated { | ||
glog.Warningf("WARNING: Feature %s will be deprecated soon", featureName) | ||
} | ||
return nil | ||
} | ||
|
||
// SetFromMap sets the enablement status of featuers accordig to a map | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. two typos featuers & accordig |
||
func (fg *FeatureGate) SetFromMap(valuesToSet map[string]bool) error { | ||
for k, v := range valuesToSet { | ||
if err := fg.set(k, v); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// SetFromString converts config string to map and sets the enablement status of the selected features | ||
// copied from k8s and slightly changed - TBC? | ||
func (fg *FeatureGate) SetFromString(value string) error { | ||
featureMap := make(map[string]bool) | ||
for _, s := range strings.Split(value, ",") { | ||
if s == "" { | ||
continue | ||
} | ||
splitted := strings.Split(s, "=") | ||
key := strings.TrimSpace(splitted[0]) | ||
if len(splitted) != splitLength { | ||
if len(splitted) > splitLength { | ||
return fmt.Errorf("too many values for %s", key) | ||
} | ||
return fmt.Errorf("enablement value for %s is missing", key) | ||
} | ||
|
||
val := strings.TrimSpace(splitted[1]) | ||
boolVal, err := strconv.ParseBool(val) | ||
if err != nil { | ||
return fmt.Errorf("error while processing %s=%s, err: %v", key, val, err) | ||
} | ||
|
||
featureMap[key] = boolVal | ||
} | ||
return fg.SetFromMap(featureMap) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Separate imports according to convention [1]
(space)
(space)
[1] https://github.com/golang/go/wiki/CodeReviewComments#imports
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done