-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Kavindu Dodanduwa <kavindudodanduwa@gmail.com>
- Loading branch information
1 parent
d5ad018
commit 8b0323e
Showing
6 changed files
with
307 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,81 @@ | ||
package flag_sync | ||
package sync | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
syncv1 "buf.build/gen/go/open-feature/flagd/protocolbuffers/go/flagd/sync/v1" | ||
"connectrpc.com/connect" | ||
"context" | ||
"github.com/open-feature/flagd/core/pkg/logger" | ||
"google.golang.org/protobuf/types/known/structpb" | ||
) | ||
|
||
// Request handler | ||
type syncHandler struct { | ||
mux *syncMultiplexer | ||
log *logger.Logger | ||
} | ||
|
||
func (s syncHandler) SyncFlags(ctx context.Context, c *connect.Request[syncv1.SyncFlagsRequest], c2 *connect.ServerStream[syncv1.SyncFlagsResponse]) error { | ||
//TODO implement me | ||
panic("implement me") | ||
func (s *syncHandler) SyncFlags(ctx context.Context, req *connect.Request[syncv1.SyncFlagsRequest], | ||
rsp *connect.ServerStream[syncv1.SyncFlagsResponse], | ||
) error { | ||
muxPayload := make(chan payload) | ||
selector := req.Msg.GetSelector() | ||
|
||
err := s.mux.register(ctx, selector, muxPayload) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for { | ||
select { | ||
case payload := <-muxPayload: | ||
err := rsp.Send(&syncv1.SyncFlagsResponse{ | ||
FlagConfiguration: payload.flags, | ||
}) | ||
if err != nil { | ||
s.log.Debug(fmt.Sprintf("error sending stream response: %v", err)) | ||
return fmt.Errorf("error sending stream response: %w", err) | ||
} | ||
case <-ctx.Done(): | ||
s.mux.unregister(ctx, selector) | ||
s.log.Debug("context done, exiting stream request") | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
func (s syncHandler) FetchAllFlags(ctx context.Context, c *connect.Request[syncv1.FetchAllFlagsRequest]) (*connect.Response[syncv1.FetchAllFlagsResponse], error) { | ||
//TODO implement me | ||
panic("implement me") | ||
func (s *syncHandler) FetchAllFlags(_ context.Context, req *connect.Request[syncv1.FetchAllFlagsRequest]) ( | ||
*connect.Response[syncv1.FetchAllFlagsResponse], error, | ||
) { | ||
flags, err := s.mux.getALlFlags(req.Msg.GetSelector()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &connect.Response[syncv1.FetchAllFlagsResponse]{ | ||
Msg: &syncv1.FetchAllFlagsResponse{ | ||
FlagConfiguration: flags, | ||
}, | ||
}, nil | ||
} | ||
|
||
func (s syncHandler) GetMetadata(ctx context.Context, c *connect.Request[syncv1.GetMetadataRequest]) (*connect.Response[syncv1.GetMetadataResponse], error) { | ||
//TODO implement me | ||
panic("implement me") | ||
func (s *syncHandler) GetMetadata(_ context.Context, _ *connect.Request[syncv1.GetMetadataRequest]) ( | ||
*connect.Response[syncv1.GetMetadataResponse], error, | ||
) { | ||
// todo handle request | ||
metadata, err := structpb.NewStruct(map[string]interface{}{ | ||
"sources": s.mux.sourcesAsMetadata(), | ||
}) | ||
if err != nil { | ||
s.log.Warn(fmt.Sprintf("error from struct creation: %v", err)) | ||
return nil, fmt.Errorf("error constructing response") | ||
} | ||
|
||
return &connect.Response[syncv1.GetMetadataResponse]{ | ||
Msg: &syncv1.GetMetadataResponse{ | ||
Metadata: metadata, | ||
}, | ||
}, | ||
nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,185 @@ | ||
package flag_sync | ||
package sync | ||
|
||
// multiplex data sync to listeners | ||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"slices" | ||
"strings" | ||
"sync" | ||
|
||
"github.com/open-feature/flagd/core/pkg/model" | ||
"github.com/open-feature/flagd/core/pkg/store" | ||
) | ||
|
||
type syncMultiplexer struct { | ||
store *store.Flags | ||
sources []string | ||
|
||
subs map[interface{}]subscription // subscriptions on all sources | ||
selectorSubs map[string]map[interface{}]subscription // source specific subscriptions | ||
|
||
allFlags string // pre-calculated all flags in store as a string | ||
selectorFlags map[string]string // pre-calculated selector scoped flags | ||
|
||
mu sync.Mutex | ||
} | ||
|
||
type subscription struct { | ||
id interface{} | ||
channel chan payload | ||
} | ||
|
||
type payload struct { | ||
flags string | ||
} | ||
|
||
func newMux(store *store.Flags, sources []string) *syncMultiplexer { | ||
return &syncMultiplexer{ | ||
store: store, | ||
sources: sources, | ||
subs: map[interface{}]subscription{}, | ||
selectorSubs: map[string]map[interface{}]subscription{}, | ||
selectorFlags: map[string]string{}, | ||
} | ||
} | ||
|
||
func (r *syncMultiplexer) register(id interface{}, source string, con chan payload) error { | ||
r.mu.Lock() | ||
defer r.mu.Unlock() | ||
|
||
if source != "" && !slices.Contains(r.sources, source) { | ||
return fmt.Errorf("no flag watcher setup for source %s", source) | ||
} | ||
|
||
var initSync string | ||
var err error | ||
|
||
if source == "" { | ||
// subscribe for flags from all source | ||
r.subs[id] = subscription{ | ||
id: id, | ||
channel: con, | ||
} | ||
|
||
initSync, err = r.store.String() | ||
if err != nil { | ||
return fmt.Errorf("errpr getting all flags: %w", err) | ||
} | ||
} else { | ||
// subscribe for specific source | ||
s, ok := r.selectorSubs[source] | ||
if ok { | ||
s[id] = subscription{ | ||
id: id, | ||
channel: con, | ||
} | ||
} else { | ||
r.selectorSubs[source] = map[interface{}]subscription{ | ||
id: { | ||
id: id, | ||
channel: con, | ||
}, | ||
} | ||
} | ||
|
||
initSync = r.selectorFlags[source] | ||
} | ||
|
||
// Initial sync | ||
con <- payload{flags: initSync} | ||
return nil | ||
} | ||
|
||
func (r *syncMultiplexer) pushUpdates() error { | ||
r.mu.Lock() | ||
defer r.mu.Unlock() | ||
|
||
err := r.extract() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// push to all source subs | ||
for _, sub := range r.subs { | ||
sub.channel <- payload{r.allFlags} | ||
} | ||
|
||
// push to selector subs | ||
for source, flags := range r.selectorFlags { | ||
for _, s := range r.selectorSubs[source] { | ||
s.channel <- payload{flags} | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (r *syncMultiplexer) unregister(id interface{}, selector string) { | ||
r.mu.Lock() | ||
defer r.mu.Unlock() | ||
|
||
var from map[interface{}]subscription | ||
|
||
if selector == "" { | ||
from = r.subs | ||
} else { | ||
from = r.selectorSubs[selector] | ||
} | ||
|
||
delete(from, id) | ||
} | ||
|
||
func (r *syncMultiplexer) getALlFlags(source string) (string, error) { | ||
if source != "" && !slices.Contains(r.sources, source) { | ||
return "", fmt.Errorf("no flag watcher setup for source %s", source) | ||
} | ||
|
||
if source == "" { | ||
return r.allFlags, nil | ||
} | ||
|
||
return r.selectorFlags[source], nil | ||
} | ||
|
||
func (r *syncMultiplexer) sourcesAsMetadata() string { | ||
r.mu.Lock() | ||
defer r.mu.Unlock() | ||
|
||
return strings.Join(r.store.FlagSources, ",") | ||
} | ||
|
||
func (r *syncMultiplexer) extract() error { | ||
clear(r.selectorFlags) | ||
|
||
all := r.store.GetAll() | ||
bytes, err := json.Marshal(all) | ||
if err != nil { | ||
return fmt.Errorf("error from marshallin: %w", err) | ||
} | ||
|
||
r.allFlags = string(bytes) | ||
|
||
collector := map[string]map[string]model.Flag{} | ||
|
||
for key, flag := range all { | ||
c, ok := collector[flag.Source] | ||
if ok { | ||
c[key] = flag | ||
} else { | ||
collector[flag.Source] = map[string]model.Flag{ | ||
key: flag, | ||
} | ||
} | ||
} | ||
|
||
for source, flags := range collector { | ||
bytes, err := json.Marshal(flags) | ||
if err != nil { | ||
return fmt.Errorf("unable to marshal flags: %w", err) | ||
} | ||
|
||
r.selectorFlags[source] = string(bytes) | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.