From 73c2bbfb296ccaeeefdb293adf02fa7aebf36081 Mon Sep 17 00:00:00 2001 From: Martin Kunc <5441732+martinkunc@users.noreply.github.com> Date: Thu, 5 Mar 2020 11:56:37 +0100 Subject: [PATCH] Add test to obseve config changes --- pkg/config/configobserver/configobserver.go | 5 +- .../configobserver/configobserver_test.go | 139 +++++++++++++----- 2 files changed, 109 insertions(+), 35 deletions(-) diff --git a/pkg/config/configobserver/configobserver.go b/pkg/config/configobserver/configobserver.go index e5a5a27c6..a929a6da7 100644 --- a/pkg/config/configobserver/configobserver.go +++ b/pkg/config/configobserver/configobserver.go @@ -30,6 +30,7 @@ type Controller struct { tokenConfig *config.Controller secretConfig *config.Controller config *config.Controller + checkPeriod time.Duration listeners []chan struct{} } @@ -37,6 +38,7 @@ func New(defaultConfig config.Controller, kubeClient kubernetes.Interface) *Cont c := &Controller{ kubeClient: kubeClient, defaultConfig: defaultConfig, + checkPeriod: 5 * time.Minute, } c.mergeConfigLocked() if err := c.retrieveToken(); err != nil { @@ -48,6 +50,7 @@ func New(defaultConfig config.Controller, kubeClient kubernetes.Interface) *Cont return c } +// Start is periodically invoking check and set of config and token func (c *Controller) Start(ctx context.Context) { wait.Until(func() { if err := c.retrieveToken(); err != nil { @@ -56,7 +59,7 @@ func (c *Controller) Start(ctx context.Context) { if err := c.retrieveConfig(); err != nil { klog.Warningf("Unable to retrieve config: %v", err) } - }, 5*time.Minute, ctx.Done()) + }, c.checkPeriod, ctx.Done()) } func (c *Controller) retrieveToken() error { diff --git a/pkg/config/configobserver/configobserver_test.go b/pkg/config/configobserver/configobserver_test.go index 7b26b92fa..7891cad24 100644 --- a/pkg/config/configobserver/configobserver_test.go +++ b/pkg/config/configobserver/configobserver_test.go @@ -1,11 +1,11 @@ package configobserver import ( + "context" "encoding/json" "fmt" - "io/ioutil" - "os" "reflect" + "sync" "testing" "time" @@ -66,22 +66,9 @@ func TestChangeSupportConfig(t *testing.T) { ctrl := config.Controller{} kube := kubeClientResponder{} - secs := tt.config // setup mock responses for secretes by secret name - kube.CoreV1().(*corefake.FakeCoreV1).Fake.AddReactor("get", "secrets", - func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { - actionName := "" - if getAction, ok := action.(clienttesting.GetAction); ok { - actionName = getAction.GetName() - } - - key := fmt.Sprintf("(%s) %s.%s", action.GetResource(), action.GetNamespace(), actionName) - sv, ok := secs[key] - if !ok { - return false, nil, nil - } - return true, sv, nil - }) + provideSecretMock(&kube, tt.config) + // imitates New function c := &Controller{ kubeClient: &kube, @@ -111,31 +98,115 @@ func TestChangeSupportConfig(t *testing.T) { } +func TestChangeObserved(t *testing.T) { + setIntervals := map[int]time.Duration{ + 0: time.Duration(10 * time.Minute), + 1: time.Duration(1 * time.Minute), + 2: time.Duration(3 * time.Minute), + 3: time.Duration(4 * time.Minute), + } + + klog.SetOutput(utils.NewTestLog(t).Writer()) + + ctrl := config.Controller{} + kube := kubeClientResponder{} + // The initial values set in configobserver.New + secs := map[string]*corev1.Secret{ + pullSecretKey: &corev1.Secret{Data: map[string][]byte{ + ".dockerconfigjson": fakeDockerConfig(), + }}, + supportKey: &corev1.Secret{Data: map[string][]byte{ + "username": []byte("someone"), + "password": []byte("secret"), + "endpoint": []byte("http://po.rt"), + intervalKey: []byte("10m"), + }}, + } + + provideSecretMock(&kube, secs) + // New reads first k8 configuration + co := New(ctrl, &kube) + // set some initial config because we are tracking changes only + co.setConfigLocked(&config.Controller{}) + + // observe changes every 50 ms + co.checkPeriod = 50 * time.Millisecond + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Watch for changes in configurations + done := make(chan bool) + go co.Start(ctx) + changedC, _ := co.ConfigChanged() + + // Sets gather intervals in config to 3 and 4 minutes after 100ms elapses + go func() { + + for i := range setIntervals { + time.Sleep(100 * time.Millisecond) + secs[supportKey].Data[intervalKey] = []byte(setIntervals[i].String()) + } + // Give observer chance to catch the change + time.Sleep(50 * time.Millisecond) + done <- true + }() + + actualIntervals := map[int]time.Duration{} + actIntMu := sync.Mutex{} + go func() { + for { + select { + case <-ctx.Done(): + done <- true + + case <-changedC: + actInt := co.Config().Interval + + actIntMu.Lock() + actIntMu.Unlock() + actualIntervals[len(actualIntervals)] = actInt + } + } + }() + <-done + + if !reflect.DeepEqual(setIntervals, actualIntervals) { + t.Fatalf("the expected intervals didn't match actual intervals. \nExpected %v \nActual %v", setIntervals, actualIntervals) + } +} + const ( pullSecretKey = "(/v1, Resource=secrets) openshift-config.pull-secret" supportKey = "(/v1, Resource=secrets) openshift-config.support" intervalKey = "interval" ) -func mustMarshal(v interface{}) []byte { - bt, err := json.Marshal(v) - if err != nil { - panic(err) - } - return bt +func fakeDockerConfig() []byte { + d, _ := json.Marshal( + serializedAuthMap{ + Auths: map[string]serializedAuth{ + "cloud.openshift.com": serializedAuth{Auth: ".."}, + }, + }) + return d } -func mustLoad(filename string) []byte { - f, err := os.Open(filename) - if err != nil { - panic(fmt.Errorf("test failed to load data: %v", err)) - } - defer f.Close() - bts, err := ioutil.ReadAll(f) - if err != nil { - panic(fmt.Errorf("test failed to read data: %v", err)) - } - return bts +func provideSecretMock(kube kubernetes.Interface, secs map[string]*corev1.Secret) { + kube.CoreV1().(*corefake.FakeCoreV1).Fake.AddReactor("get", "secrets", + func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { + actionName := "" + if getAction, ok := action.(clienttesting.GetAction); ok { + actionName = getAction.GetName() + } + + key := fmt.Sprintf("(%s) %s.%s", action.GetResource(), action.GetNamespace(), actionName) + sv, ok := secs[key] + + if !ok { + return false, nil, nil + } + return true, sv, nil + }) } type kubeClientResponder struct {