Skip to content

Commit 82306ec

Browse files
authored
Merge pull request #123 from harness/FFM-9462
FFM-9462 Refresh Segments/Flags keys when we get delete SSE events
2 parents 25c087a + aa9e9cd commit 82306ec

File tree

5 files changed

+131
-6
lines changed

5 files changed

+131
-6
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
github.com/spaolacci/murmur3 v1.1.0
1818
github.com/stretchr/testify v1.7.1
1919
go.uber.org/zap v1.16.0
20+
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
2021
gopkg.in/cenkalti/backoff.v1 v1.1.0
2122
)
2223

go.sum

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,13 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0
153153
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
154154
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c=
155155
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
156+
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
157+
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
156158
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
157159
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
158160
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
159-
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
160161
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
162+
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
161163
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
162164
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
163165
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -183,7 +185,7 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
183185
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
184186
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
185187
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
186-
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
188+
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
187189
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
188190
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
189191
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -200,13 +202,12 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw
200202
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
201203
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
202204
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
203-
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
204205
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
206+
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
205207
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
206208
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
207209
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
208210
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
209-
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
210211
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
211212
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
212213
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=

pkg/repository/repository.go

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package repository
33
import (
44
"fmt"
55

6+
"golang.org/x/exp/slices"
7+
68
"github.com/harness/ff-golang-server-sdk/log"
79
"github.com/harness/ff-golang-server-sdk/rest"
810
"github.com/harness/ff-golang-server-sdk/storage"
@@ -20,7 +22,9 @@ type Repository interface {
2022
SetSegments(initialLoad bool, envID string, segment ...rest.Segment)
2123

2224
DeleteFlag(identifier string)
25+
DeleteFlags(envID string, identifier string)
2326
DeleteSegment(identifier string)
27+
DeleteSegments(envID string, identifier string)
2428

2529
Close()
2630
}
@@ -29,10 +33,12 @@ type Repository interface {
2933
type Callback interface {
3034
OnFlagStored(identifier string)
3135
OnFlagsStored(envID string)
36+
OnFlagsDeleted(envID string, identifier string)
3237
OnFlagDeleted(identifier string)
3338
OnSegmentStored(identifier string)
3439
OnSegmentsStored(envID string)
3540
OnSegmentDeleted(identifier string)
41+
OnSegmentsDeleted(envID string, identifier string)
3642
}
3743

3844
// FFRepository holds cache and optionally offline data
@@ -77,6 +83,16 @@ func (r FFRepository) getFlags(envID string) ([]rest.FeatureConfig, error) {
7783
return []rest.FeatureConfig{}, fmt.Errorf("%w with environment: %s", ErrFeatureConfigNotFound, envID)
7884
}
7985

86+
func (r FFRepository) getSegments(envID string) ([]rest.Segment, error) {
87+
segmentsKey := formatSegmentsKey(envID)
88+
flags, ok := r.cache.Get(segmentsKey)
89+
if ok {
90+
return flags.([]rest.Segment), nil
91+
}
92+
93+
return []rest.Segment{}, fmt.Errorf("%w with environment: %s", ErrFeatureConfigNotFound, envID)
94+
}
95+
8096
func (r FFRepository) getFlagAndCache(identifier string, cacheable bool) (rest.FeatureConfig, error) {
8197
flagKey := formatFlagKey(identifier)
8298
flag, ok := r.cache.Get(flagKey)
@@ -201,7 +217,7 @@ func (r FFRepository) SetSegment(segment rest.Segment, initialLoad bool) {
201217
func (r FFRepository) SetSegments(initialLoad bool, envID string, segments ...rest.Segment) {
202218
if !initialLoad {
203219
// If segments aren't outdated then we can exit as we don't need to refresh the cache
204-
if !r.areSegmentsOutdated(segments...) {
220+
if !r.areSegmentsOutdated(envID, segments...) {
205221
return
206222
}
207223
}
@@ -238,6 +254,42 @@ func (r FFRepository) DeleteFlag(identifier string) {
238254
}
239255
}
240256

257+
// DeleteFlags removes a flag from the flags key.
258+
//
259+
// We can't just delete the key here the way we can for a single flag because then we'd be removing flags that
260+
// haven't been deleted. So we have to first fetch value, then remove the specific flag that has been deleted
261+
// and update the key in the cache/storage
262+
func (r FFRepository) DeleteFlags(envID string, identifier string) {
263+
flagsKey := formatFlagsKey(envID)
264+
if r.storage != nil {
265+
// remove from storage
266+
if err := r.storage.Remove(flagsKey); err != nil {
267+
log.Errorf("error while removing flags %s from repository", envID)
268+
}
269+
}
270+
271+
value, ok := r.cache.Get(flagsKey)
272+
if !ok {
273+
log.Errorf("error fetching flags from cache for env=%s", envID)
274+
return
275+
}
276+
277+
featureConfigs, ok := value.([]rest.FeatureConfig)
278+
if !ok {
279+
log.Errorf("failed to delete flags, expected type to be []rest.FeatureConfig but got %T", featureConfigs)
280+
return
281+
}
282+
283+
updatedFeatureConfigs := slices.DeleteFunc(featureConfigs, func(element rest.FeatureConfig) bool {
284+
return element.Feature == identifier
285+
})
286+
r.cache.Set(flagsKey, updatedFeatureConfigs)
287+
288+
if r.callback != nil {
289+
r.callback.OnFlagsDeleted(envID, identifier)
290+
}
291+
}
292+
241293
// DeleteSegment removes a segment from the repository
242294
func (r FFRepository) DeleteSegment(identifier string) {
243295
segmentKey := formatSegmentKey(identifier)
@@ -254,6 +306,42 @@ func (r FFRepository) DeleteSegment(identifier string) {
254306
}
255307
}
256308

309+
// DeleteSegments removes a Segment from the segments key.
310+
//
311+
// We can't just delete the key here the way we can for a single flag because then we'd be removing segments that
312+
// haven't been deleted. So we have to first fetch value, then remove the specific segment that has been deleted
313+
// and update the key in the cache/storage
314+
func (r FFRepository) DeleteSegments(envID string, identifier string) {
315+
segmentsKey := formatSegmentsKey(envID)
316+
if r.storage != nil {
317+
// remove from storage
318+
if err := r.storage.Remove(segmentsKey); err != nil {
319+
log.Errorf("error while removing segments %s from repository", envID)
320+
}
321+
}
322+
323+
value, ok := r.cache.Get(segmentsKey)
324+
if !ok {
325+
log.Errorf("error fetching segments from cache for env=%s", envID)
326+
return
327+
}
328+
329+
segments, ok := value.([]rest.Segment)
330+
if !ok {
331+
log.Errorf("failed to delete flags, expected type to be []rest.Segment but got %T", segments)
332+
return
333+
}
334+
335+
updatedSegments := slices.DeleteFunc(segments, func(element rest.Segment) bool {
336+
return element.Identifier == identifier
337+
})
338+
r.cache.Set(segmentsKey, updatedSegments)
339+
340+
if r.callback != nil {
341+
r.callback.OnSegmentsDeleted(envID, identifier)
342+
}
343+
}
344+
257345
func (r FFRepository) isFlagOutdated(featureConfig rest.FeatureConfig) bool {
258346
oldFlag, err := r.getFlagAndCache(featureConfig.Feature, false)
259347
if err != nil || oldFlag.Version == nil {
@@ -319,7 +407,32 @@ func (r FFRepository) isSegmentOutdated(segment rest.Segment) bool {
319407
return *oldSegment.Version < *segment.Version
320408
}
321409

322-
func (r FFRepository) areSegmentsOutdated(segments ...rest.Segment) bool {
410+
func (r FFRepository) areSegmentsOutdated(envID string, segments ...rest.Segment) bool {
411+
oldSegments, err := r.getSegments(envID)
412+
if err != nil {
413+
// If we get an error return true to force a cache refresh
414+
return true
415+
}
416+
417+
oldSegmentsMap := map[string]rest.Segment{}
418+
for _, v := range oldSegments {
419+
oldSegmentsMap[v.Identifier] = v
420+
}
421+
422+
for _, seg := range segments {
423+
os, ok := oldSegmentsMap[seg.Identifier]
424+
if !ok {
425+
// If a new flag isn't in the oldFlagMap then the list of old flags are outdated and we'll
426+
// want to refresh the cache
427+
return true
428+
}
429+
430+
if *os.Version < *seg.Version {
431+
return true
432+
}
433+
}
434+
return false
435+
323436
for _, segment := range segments {
324437
if r.isSegmentOutdated(segment) {
325438
return true

pkg/repository/repository_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ func (m *mockCache) Get(key interface{}) (value interface{}, ok bool) {
161161
return m.features, true
162162
}
163163

164+
if s == "target-segments/123" {
165+
return m.segments, true
166+
}
167+
164168
if strings.Contains(s, "target-segment") {
165169
seg := strings.TrimPrefix(s, "target-segment/")
166170

@@ -275,6 +279,10 @@ func (m *mockCallback) OnSegmentsStored(envID string) {
275279

276280
func (m *mockCallback) OnSegmentDeleted(identifier string) {}
277281

282+
func (m *mockCallback) OnSegmentsDeleted(envID string, identifier string) {}
283+
284+
func (m *mockCallback) OnFlagsDeleted(envID string, identifier string) {}
285+
278286
func TestFFRepository_SetFlags(t *testing.T) {
279287
type args struct {
280288
initialLoad bool

stream/sse.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ func (c *SSEClient) handleEvent(event Event) {
130130
switch cfMsg.Event {
131131
case dto.SseDeleteEvent:
132132
c.repository.DeleteFlag(cfMsg.Identifier)
133+
c.repository.DeleteFlags(event.Environment, cfMsg.Identifier)
133134
case dto.SsePatchEvent, dto.SseCreateEvent:
134135
fallthrough
135136
default:
@@ -173,6 +174,7 @@ func (c *SSEClient) handleEvent(event Event) {
173174
switch cfMsg.Event {
174175
case dto.SseDeleteEvent:
175176
c.repository.DeleteSegment(cfMsg.Identifier)
177+
c.repository.DeleteSegments(event.Environment, cfMsg.Identifier)
176178
case dto.SsePatchEvent, dto.SseCreateEvent:
177179
fallthrough
178180
default:

0 commit comments

Comments
 (0)