From dceafbadabb8a82216cac5797523eadfae69b8d2 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Tue, 15 Jul 2014 22:04:33 -0700 Subject: [PATCH] Add a caching minion registry. --- pkg/registry/caching_minion_registry.go | 122 +++++++++++++++++++ pkg/registry/caching_minion_registry_test.go | 116 ++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 pkg/registry/caching_minion_registry.go create mode 100644 pkg/registry/caching_minion_registry_test.go diff --git a/pkg/registry/caching_minion_registry.go b/pkg/registry/caching_minion_registry.go new file mode 100644 index 00000000000..44b61bf0fc8 --- /dev/null +++ b/pkg/registry/caching_minion_registry.go @@ -0,0 +1,122 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package registry + +import ( + "sync" + "sync/atomic" + "time" +) + +type Clock interface { + Now() time.Time +} + +type SystemClock struct{} + +func (SystemClock) Now() time.Time { + return time.Now() +} + +type CachingMinionRegistry struct { + delegate MinionRegistry + ttl time.Duration + minions []string + lastUpdate int64 + lock sync.RWMutex + clock Clock +} + +func NewCachingMinionRegistry(delegate MinionRegistry, ttl time.Duration) (MinionRegistry, error) { + list, err := delegate.List() + if err != nil { + return nil, err + } + return &CachingMinionRegistry{ + delegate: delegate, + ttl: ttl, + minions: list, + lastUpdate: time.Now().Unix(), + clock: SystemClock{}, + }, nil +} + +func (c *CachingMinionRegistry) List() ([]string, error) { + if c.expired() { + err := c.refresh(false) + if err != nil { + return c.minions, err + } + } + return c.minions, nil +} + +func (c *CachingMinionRegistry) Insert(minion string) error { + err := c.delegate.Insert(minion) + if err != nil { + return err + } + return c.refresh(true) +} + +func (c *CachingMinionRegistry) Delete(minion string) error { + err := c.delegate.Delete(minion) + if err != nil { + return err + } + return c.refresh(true) +} + +func (c *CachingMinionRegistry) Contains(minion string) (bool, error) { + if c.expired() { + err := c.refresh(false) + if err != nil { + return false, err + } + } + + // block updates in the middle of a contains. + c.lock.RLock() + defer c.lock.RUnlock() + for _, name := range c.minions { + if name == minion { + return true, nil + } + } + return false, nil +} + +// refresh updates the current store. It double checks expired under lock with the assumption +// of optimistic concurrency with the other functions. +func (c *CachingMinionRegistry) refresh(force bool) error { + c.lock.Lock() + defer c.lock.Unlock() + if force || c.expired() { + var err error + c.minions, err = c.delegate.List() + time := c.clock.Now() + atomic.SwapInt64(&c.lastUpdate, time.Unix()) + return err + } + return nil +} + +func (c *CachingMinionRegistry) expired() bool { + var unix int64 + atomic.SwapInt64(&unix, c.lastUpdate) + return c.clock.Now().Sub(time.Unix(c.lastUpdate, 0)) > c.ttl +} diff --git a/pkg/registry/caching_minion_registry_test.go b/pkg/registry/caching_minion_registry_test.go new file mode 100644 index 00000000000..5b9c763bb31 --- /dev/null +++ b/pkg/registry/caching_minion_registry_test.go @@ -0,0 +1,116 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package registry + +import ( + "reflect" + "testing" + "time" +) + +type fakeClock struct { + now time.Time +} + +func (f *fakeClock) Now() time.Time { + return f.now +} + +func TestCachingHit(t *testing.T) { + fakeClock := fakeClock{ + now: time.Unix(0, 0), + } + fakeRegistry := MakeMockMinionRegistry([]string{"m1", "m2"}) + expected := []string{"m1", "m2", "m3"} + cache := CachingMinionRegistry{ + delegate: fakeRegistry, + ttl: 1 * time.Second, + clock: &fakeClock, + lastUpdate: fakeClock.Now().Unix(), + minions: expected, + } + list, err := cache.List() + expectNoError(t, err) + if !reflect.DeepEqual(list, expected) { + t.Errorf("expected: %v, got %v", expected, list) + } +} + +func TestCachingMiss(t *testing.T) { + fakeClock := fakeClock{ + now: time.Unix(0, 0), + } + fakeRegistry := MakeMockMinionRegistry([]string{"m1", "m2"}) + expected := []string{"m1", "m2", "m3"} + cache := CachingMinionRegistry{ + delegate: fakeRegistry, + ttl: 1 * time.Second, + clock: &fakeClock, + lastUpdate: fakeClock.Now().Unix(), + minions: expected, + } + fakeClock.now = time.Unix(3, 0) + list, err := cache.List() + expectNoError(t, err) + if !reflect.DeepEqual(list, fakeRegistry.minions) { + t.Errorf("expected: %v, got %v", fakeRegistry.minions, list) + } +} + +func TestCachingInsert(t *testing.T) { + fakeClock := fakeClock{ + now: time.Unix(0, 0), + } + fakeRegistry := MakeMockMinionRegistry([]string{"m1", "m2"}) + expected := []string{"m1", "m2", "m3"} + cache := CachingMinionRegistry{ + delegate: fakeRegistry, + ttl: 1 * time.Second, + clock: &fakeClock, + lastUpdate: fakeClock.Now().Unix(), + minions: expected, + } + err := cache.Insert("foo") + expectNoError(t, err) + list, err := cache.List() + expectNoError(t, err) + if !reflect.DeepEqual(list, fakeRegistry.minions) { + t.Errorf("expected: %v, got %v", fakeRegistry.minions, list) + } +} + +func TestCachingDelete(t *testing.T) { + fakeClock := fakeClock{ + now: time.Unix(0, 0), + } + fakeRegistry := MakeMockMinionRegistry([]string{"m1", "m2"}) + expected := []string{"m1", "m2", "m3"} + cache := CachingMinionRegistry{ + delegate: fakeRegistry, + ttl: 1 * time.Second, + clock: &fakeClock, + lastUpdate: fakeClock.Now().Unix(), + minions: expected, + } + err := cache.Delete("m2") + expectNoError(t, err) + list, err := cache.List() + expectNoError(t, err) + if !reflect.DeepEqual(list, fakeRegistry.minions) { + t.Errorf("expected: %v, got %v", fakeRegistry.minions, list) + } +}