Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

45
library/exp/feature/BUILD Normal file
View File

@@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["feature_gate.go"],
importpath = "go-common/library/exp/feature",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//vendor/github.com/pkg/errors:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["feature_gate_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
)
go_test(
name = "go_default_xtest",
srcs = ["example_test.go"],
tags = ["automanaged"],
deps = ["//library/exp/feature:go_default_library"],
)

View File

@@ -0,0 +1,4 @@
### feature-gates
#### Version 0.1.0
> 1. 实现基本功能

View File

@@ -0,0 +1,9 @@
# Owner
zhoujiahui
# Author
zhoujiahui
maojian
# Reviewer
maojian

View File

@@ -0,0 +1,11 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- maojian
- zhoujiahui
labels:
- library
- library/exp/feature
reviewers:
- maojian
- zhoujiahui

View File

@@ -0,0 +1,5 @@
### feature-gates
go-common 里的 Feature 管理工具。
用于灰度测试一些基础库的功能。

View File

@@ -0,0 +1,54 @@
package feature_test
import (
"flag"
"fmt"
"go-common/library/exp/feature"
)
var (
AStableFeature feature.Feature = "a-stable-feature"
AStagingFeature feature.Feature = "a-staging-feature"
)
var exampleFeatures = map[feature.Feature]feature.Spec{
AStableFeature: feature.Spec{Default: true},
AStagingFeature: feature.Spec{Default: false},
}
func init() {
feature.DefaultGate.Add(exampleFeatures)
feature.DefaultGate.AddFlag(flag.CommandLine)
}
// This example create an example to using default features.
func Example() {
knows := feature.DefaultGate.KnownFeatures()
fmt.Println(knows)
enabled := feature.DefaultGate.Enabled(AStableFeature)
fmt.Println(enabled)
enabled = feature.DefaultGate.Enabled(AStagingFeature)
fmt.Println(enabled)
// Output: [a-stable-feature=true|false (default=true) a-staging-feature=true|false (default=false)]
// true
// false
}
// This example parsing flag from command line and enable a staging feature.
func ExampleFeature() {
knows := feature.DefaultGate.KnownFeatures()
fmt.Println(knows)
enabled := feature.DefaultGate.Enabled(AStagingFeature)
fmt.Println(enabled)
flag.Set("feature-gates", fmt.Sprintf("%s=true", AStagingFeature))
enabled = feature.DefaultGate.Enabled(AStagingFeature)
fmt.Println(enabled)
// Output: [a-stable-feature=true|false (default=true) a-staging-feature=true|false (default=false)]
// false
// true
}

View File

@@ -0,0 +1,275 @@
package feature
import (
"flag"
"fmt"
"os"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"github.com/pkg/errors"
)
// Feature is the feature name
type Feature string
const (
flagName = "feature-gates"
)
var (
// DefaultGate is a shared global Gate.
DefaultGate = NewGate()
)
// Spec is the spec of the feature
type Spec struct {
Default bool
}
// Gate parses and stores flag gates for known features from
// a string like feature1=true,feature2=false,...
type Gate interface {
// AddFlag adds a flag for setting global feature gates to the specified FlagSet.
AddFlag(fs *flag.FlagSet)
// Set parses and stores flag gates for known features
// from a string like feature1=true,feature2=false,...
Set(value string) error
// SetFromMap stores flag gates for known features from a map[string]bool or returns an error
SetFromMap(m map[string]bool) error
// Enabled returns true if the key is enabled.
Enabled(key Feature) bool
// Add adds features to the featureGate.
Add(features map[Feature]Spec) error
// KnownFeatures returns a slice of strings describing the Gate's known features.
KnownFeatures() []string
// DeepCopy returns a deep copy of the Gate object, such that gates can be
// set on the copy without mutating the original. This is useful for validating
// config against potential feature gate changes before committing those changes.
DeepCopy() Gate
}
// featureGate implements Gate as well as flag.Value for flag parsing.
type featureGate struct {
// lock guards writes to known, enabled, and reads/writes of closed
lock sync.Mutex
// known holds a map[Feature]Spec
known atomic.Value
// enabled holds a map[Feature]bool
enabled atomic.Value
// closed is set to true when AddFlag is called, and prevents subsequent calls to Add
closed bool
}
// Set, String, and Type implement flag.Value
var _ flag.Value = &featureGate{}
// NewGate create a feature gate.
func NewGate() *featureGate {
known := map[Feature]Spec{}
knownValue := atomic.Value{}
knownValue.Store(known)
enabled := map[Feature]bool{}
enabledValue := atomic.Value{}
enabledValue.Store(enabled)
f := &featureGate{
known: knownValue,
enabled: enabledValue,
}
return f
}
// Set parses a string of the form "key1=value1,key2=value2,..." into a
// map[string]bool of known keys or returns an error.
func (f *featureGate) Set(value string) error {
f.lock.Lock()
defer f.lock.Unlock()
// Copy existing state
known := map[Feature]Spec{}
for k, v := range f.known.Load().(map[Feature]Spec) {
known[k] = v
}
enabled := map[Feature]bool{}
for k, v := range f.enabled.Load().(map[Feature]bool) {
enabled[k] = v
}
for _, s := range strings.Split(value, ",") {
if len(s) == 0 {
continue
}
arr := strings.SplitN(s, "=", 2)
k := Feature(strings.TrimSpace(arr[0]))
_, ok := known[k]
if !ok {
return errors.Errorf("unrecognized key: %s", k)
}
if len(arr) != 2 {
return errors.Errorf("missing bool value for %s", k)
}
v := strings.TrimSpace(arr[1])
boolValue, err := strconv.ParseBool(v)
if err != nil {
return errors.Errorf("invalid value of %s: %s, err: %v", k, v, err)
}
enabled[k] = boolValue
}
// Persist changes
f.known.Store(known)
f.enabled.Store(enabled)
fmt.Fprintf(os.Stderr, "feature gates: %v", enabled)
return nil
}
// SetFromMap stores flag gates for known features from a map[string]bool or returns an error
func (f *featureGate) SetFromMap(m map[string]bool) error {
f.lock.Lock()
defer f.lock.Unlock()
// Copy existing state
known := map[Feature]Spec{}
for k, v := range f.known.Load().(map[Feature]Spec) {
known[k] = v
}
enabled := map[Feature]bool{}
for k, v := range f.enabled.Load().(map[Feature]bool) {
enabled[k] = v
}
for k, v := range m {
k := Feature(k)
_, ok := known[k]
if !ok {
return errors.Errorf("unrecognized key: %s", k)
}
enabled[k] = v
}
// Persist changes
f.known.Store(known)
f.enabled.Store(enabled)
fmt.Fprintf(os.Stderr, "feature gates: %v", f.enabled)
return nil
}
// String returns a string containing all enabled feature gates, formatted as "key1=value1,key2=value2,...".
func (f *featureGate) String() string {
pairs := []string{}
enabled, ok := f.enabled.Load().(map[Feature]bool)
if !ok {
return ""
}
for k, v := range enabled {
pairs = append(pairs, fmt.Sprintf("%s=%t", k, v))
}
sort.Strings(pairs)
return strings.Join(pairs, ",")
}
func (f *featureGate) Type() string {
return "mapStringBool"
}
// Add adds features to the featureGate.
func (f *featureGate) Add(features map[Feature]Spec) error {
f.lock.Lock()
defer f.lock.Unlock()
if f.closed {
return errors.Errorf("cannot add a feature gate after adding it to the flag set")
}
// Copy existing state
known := map[Feature]Spec{}
for k, v := range f.known.Load().(map[Feature]Spec) {
known[k] = v
}
for name, spec := range features {
if existingSpec, found := known[name]; found {
if existingSpec == spec {
continue
}
return errors.Errorf("feature gate %q with different spec already exists: %v", name, existingSpec)
}
known[name] = spec
}
// Persist updated state
f.known.Store(known)
return nil
}
// Enabled returns true if the key is enabled.
func (f *featureGate) Enabled(key Feature) bool {
if v, ok := f.enabled.Load().(map[Feature]bool)[key]; ok {
return v
}
return f.known.Load().(map[Feature]Spec)[key].Default
}
// AddFlag adds a flag for setting global feature gates to the specified FlagSet.
func (f *featureGate) AddFlag(fs *flag.FlagSet) {
f.lock.Lock()
// TODO(mtaufen): Shouldn't we just close it on the first Set/SetFromMap instead?
// Not all components expose a feature gates flag using this AddFlag method, and
// in the future, all components will completely stop exposing a feature gates flag,
// in favor of componentconfig.
f.closed = true
f.lock.Unlock()
known := f.KnownFeatures()
fs.Var(f, flagName, ""+
"A set of key=value pairs that describe feature gates for alpha/experimental features. "+
"Options are:\n"+strings.Join(known, "\n"))
}
// KnownFeatures returns a slice of strings describing the Gate's known features.
func (f *featureGate) KnownFeatures() []string {
var known []string
for k, v := range f.known.Load().(map[Feature]Spec) {
known = append(known, fmt.Sprintf("%s=true|false (default=%t)", k, v.Default))
}
sort.Strings(known)
return known
}
// DeepCopy returns a deep copy of the Gate object, such that gates can be
// set on the copy without mutating the original. This is useful for validating
// config against potential feature gate changes before committing those changes.
func (f *featureGate) DeepCopy() Gate {
// Copy existing state.
known := map[Feature]Spec{}
for k, v := range f.known.Load().(map[Feature]Spec) {
known[k] = v
}
enabled := map[Feature]bool{}
for k, v := range f.enabled.Load().(map[Feature]bool) {
enabled[k] = v
}
// Store copied state in new atomics.
knownValue := atomic.Value{}
knownValue.Store(known)
enabledValue := atomic.Value{}
enabledValue.Store(enabled)
// Construct a new featureGate around the copied state.
// We maintain the value of f.closed across the copy.
return &featureGate{
known: knownValue,
enabled: enabledValue,
closed: f.closed,
}
}

View File

@@ -0,0 +1,271 @@
package feature
import (
"flag"
"fmt"
"strings"
"testing"
)
func TestFeatureGateOverride(t *testing.T) {
const testAlphaGate Feature = "TestAlpha"
const testBetaGate Feature = "TestBeta"
// Don't parse the flag, assert defaults are used.
var f Gate = NewGate()
f.Add(map[Feature]Spec{
testAlphaGate: {Default: false},
testBetaGate: {Default: false},
})
f.Set("TestAlpha=true,TestBeta=true")
if f.Enabled(testAlphaGate) != true {
t.Errorf("Expected true")
}
if f.Enabled(testBetaGate) != true {
t.Errorf("Expected true")
}
f.Set("TestAlpha=false")
if f.Enabled(testAlphaGate) != false {
t.Errorf("Expected false")
}
if f.Enabled(testBetaGate) != true {
t.Errorf("Expected true")
}
}
func TestFeatureGateFlagDefaults(t *testing.T) {
// gates for testing
const testAlphaGate Feature = "TestAlpha"
const testBetaGate Feature = "TestBeta"
// Don't parse the flag, assert defaults are used.
var f Gate = NewGate()
f.Add(map[Feature]Spec{
testAlphaGate: {Default: false},
testBetaGate: {Default: true},
})
if f.Enabled(testAlphaGate) != false {
t.Errorf("Expected false")
}
if f.Enabled(testBetaGate) != true {
t.Errorf("Expected true")
}
}
func TestFeatureGateSetFromMap(t *testing.T) {
// gates for testing
const testAlphaGate Feature = "TestAlpha"
const testBetaGate Feature = "TestBeta"
tests := []struct {
name string
setmap map[string]bool
expect map[Feature]bool
setmapError string
}{
{
name: "set TestAlpha and TestBeta true",
setmap: map[string]bool{
"TestAlpha": true,
"TestBeta": true,
},
expect: map[Feature]bool{
testAlphaGate: true,
testBetaGate: true,
},
},
{
name: "set TestBeta true",
setmap: map[string]bool{
"TestBeta": true,
},
expect: map[Feature]bool{
testAlphaGate: false,
testBetaGate: true,
},
},
{
name: "set TestAlpha false",
setmap: map[string]bool{
"TestAlpha": false,
},
expect: map[Feature]bool{
testAlphaGate: false,
testBetaGate: false,
},
},
{
name: "set TestInvaild true",
setmap: map[string]bool{
"TestInvaild": true,
},
expect: map[Feature]bool{
testAlphaGate: false,
testBetaGate: false,
},
setmapError: "unrecognized key:",
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("SetFromMap %s", test.name), func(t *testing.T) {
f := NewGate()
f.Add(map[Feature]Spec{
testAlphaGate: {Default: false},
testBetaGate: {Default: false},
})
err := f.SetFromMap(test.setmap)
if test.setmapError != "" {
if !strings.Contains(err.Error(), test.setmapError) {
t.Errorf("%d: SetFromMap(%#v) Expected err:%v, Got err:%v", i, test.setmap, test.setmapError, err)
}
} else if err != nil {
t.Errorf("%d: SetFromMap(%#v) Expected success, Got err:%v", i, test.setmap, err)
}
for k, v := range test.expect {
if actual := f.Enabled(k); actual != v {
t.Errorf("%d: SetFromMap(%#v) Expected %s=%v, Got %s=%v", i, test.setmap, k, v, k, actual)
}
}
})
}
}
func TestFeatureGateString(t *testing.T) {
// gates for testing
const testAlphaGate Feature = "TestAlpha"
const testBetaGate Feature = "TestBeta"
const testGAGate Feature = "TestGA"
featuremap := map[Feature]Spec{
testGAGate: {Default: true},
testAlphaGate: {Default: false},
testBetaGate: {Default: true},
}
tests := []struct {
setmap map[string]bool
expect string
}{
{
setmap: map[string]bool{
"TestAlpha": false,
},
expect: "TestAlpha=false",
},
{
setmap: map[string]bool{
"TestAlpha": false,
"TestBeta": true,
},
expect: "TestAlpha=false,TestBeta=true",
},
{
setmap: map[string]bool{
"TestGA": true,
"TestAlpha": false,
"TestBeta": true,
},
expect: "TestAlpha=false,TestBeta=true,TestGA=true",
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("SetFromMap %s", test.expect), func(t *testing.T) {
f := NewGate()
f.Add(featuremap)
f.SetFromMap(test.setmap)
result := f.String()
if result != test.expect {
t.Errorf("%d: SetFromMap(%#v) Expected %s, Got %s", i, test.setmap, test.expect, result)
}
})
}
}
func TestFeatureGateFlag(t *testing.T) {
// gates for testing
const testAlphaGate Feature = "TestAlpha"
const testBetaGate Feature = "TestBeta"
tests := []struct {
arg string
expect map[Feature]bool
parseError string
}{
{
arg: "",
expect: map[Feature]bool{
testAlphaGate: false,
testBetaGate: false,
},
},
{
arg: "fooBarBaz=maybeidk",
expect: map[Feature]bool{
testAlphaGate: false,
testBetaGate: false,
},
parseError: "unrecognized key: fooBarBaz",
},
{
arg: "TestAlpha=true",
expect: map[Feature]bool{
testAlphaGate: true,
testBetaGate: false,
},
},
{
arg: "TestAlpha=true",
expect: map[Feature]bool{
testAlphaGate: true,
testBetaGate: false,
},
},
{
arg: "TestAlpha=false",
expect: map[Feature]bool{
testAlphaGate: false,
testBetaGate: false,
},
},
{
arg: "TestAlpha=false",
expect: map[Feature]bool{
testAlphaGate: false,
testBetaGate: false,
},
},
{
arg: "TestBeta=true",
expect: map[Feature]bool{
testAlphaGate: false,
testBetaGate: true,
},
},
}
for i, test := range tests {
fs := flag.NewFlagSet("testfeaturegateflag", flag.ContinueOnError)
f := NewGate()
f.Add(map[Feature]Spec{
testAlphaGate: {Default: false},
testBetaGate: {Default: false},
})
f.AddFlag(fs)
err := fs.Parse([]string{fmt.Sprintf("-%s=%s", flagName, test.arg)})
if test.parseError != "" {
if !strings.Contains(err.Error(), test.parseError) {
t.Errorf("%d: Parse() Expected %v, Got %v", i, test.parseError, err)
}
} else if err != nil {
t.Errorf("%d: Parse() Expected nil, Got %v", i, err)
}
for k, v := range test.expect {
if actual := f.enabled.Load().(map[Feature]bool)[k]; actual != v {
t.Errorf("%d: expected %s=%v, Got %v", i, k, v, actual)
}
}
}
}