-
Notifications
You must be signed in to change notification settings - Fork 0
/
feature.go
120 lines (97 loc) · 2.71 KB
/
feature.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package rollout
import (
"hash/crc32"
"math"
"strconv"
"sync"
msgpack "github.com/vmihailenco/msgpack/v4"
)
const (
randBase = uint32((math.MaxUint32 - 1) / 100)
)
// NewFeature constructs a new Feature with the given name
func NewFeature(name string) *Feature {
return &Feature{name: name}
}
// Feature represents a development feature toggle for rollout
type Feature struct {
sync.Mutex
name string // the name of the feature
percentage uint8 // the rollout percentage
teamIDs intSet // explicit team ids with the feature enabled
}
// EncodeMsgpack implements msgpack.CustomEncoder
func (f *Feature) EncodeMsgpack(enc *msgpack.Encoder) error {
return enc.EncodeMulti(f.percentage, f.teamIDs)
}
// DecodeMsgpack implements msgpack.CustomDecoder
func (f *Feature) DecodeMsgpack(dec *msgpack.Decoder) error {
return dec.DecodeMulti(&f.percentage, &f.teamIDs)
}
// Name returns the name of the feature
func (f *Feature) Name() string {
return f.name
}
func (f *Feature) activate() {
f.percentage = 100
}
func (f *Feature) deactivate() {
f.percentage = 0
f.teamIDs = nil
}
func (f *Feature) activatePercentage(percentage uint8) {
f.percentage = percentage
}
func (f *Feature) isActive() bool {
return f.percentage == 100
}
func (f *Feature) activateTeam(teamID int64) {
if f.teamIDs == nil {
f.teamIDs = make(intSet)
}
f.teamIDs[teamID] = struct{}{}
}
func (f *Feature) deactivateTeam(teamID int64) {
delete(f.teamIDs, teamID)
}
func (f *Feature) isTeamActive(teamID int64, randomizePercentage bool) bool {
if f.percentage == 100 {
// feature is globally active
return true
} else if randomizePercentage && crc32.ChecksumIEEE([]byte(f.name+strconv.FormatInt(teamID, 10))) < randBase*uint32(f.percentage) {
// include the feature name in the checksum when randomizing percentage
return true
} else if !randomizePercentage && crc32.ChecksumIEEE([]byte(strconv.FormatInt(teamID, 10))) < randBase*uint32(f.percentage) {
// only use the team id for the checksum when not randomizing the percentage
return true
} else if _, active := f.teamIDs[teamID]; active {
// check if the team is explicitly active
return true
}
return false
}
// ref: https://github.com/vmihailenco/msgpack/blob/master/types_test.go#L52
type intSet map[int64]struct{}
func (s intSet) EncodeMsgpack(enc *msgpack.Encoder) error {
slice := make([]int64, 0, len(s))
for n := range s {
slice = append(slice, n)
}
return enc.Encode(slice)
}
func (s *intSet) DecodeMsgpack(dec *msgpack.Decoder) error {
n, err := dec.DecodeArrayLen()
if err != nil {
return err
}
set := make(intSet, n)
for i := 0; i < n; i++ {
n, err := dec.DecodeInt64()
if err != nil {
return err
}
set[n] = struct{}{}
}
*s = set
return nil
}