Commit 1cf69bde authored by derekwaynecarr's avatar derekwaynecarr

Support multiple index functions, address feedback

parent 7a2d6304
/*
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 cache
import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
// Indexer is a storage interface that lets you list objects using multiple indexing functions
type Indexer interface {
Store
// Retrieve list of objects that match on the named indexing function
Index(indexName string, obj interface{}) ([]interface{}, error)
}
// IndexFunc knows how to provide an indexed value for an object.
type IndexFunc func(obj interface{}) (string, error)
// MetaNamespaceIndexFunc is a default index function that indexes based on an object's namespace
func MetaNamespaceIndexFunc(obj interface{}) (string, error) {
meta, err := meta.Accessor(obj)
if err != nil {
return "", fmt.Errorf("object has no meta: %v", err)
}
return meta.Namespace(), nil
}
// Index maps the indexed value to a set of keys in the store that match on that value
type Index map[string]util.StringSet
// Indexers maps a name to a IndexFunc
type Indexers map[string]IndexFunc
// Indices maps a name to an Index
type Indices map[string]Index
...@@ -60,35 +60,16 @@ func MetaNamespaceKeyFunc(obj interface{}) (string, error) { ...@@ -60,35 +60,16 @@ func MetaNamespaceKeyFunc(obj interface{}) (string, error) {
return meta.Namespace() + "/" + meta.Name(), nil return meta.Namespace() + "/" + meta.Name(), nil
} }
// Index is a generic object storage interface that lets you list objects by their Index
type Index interface {
Store
Index(obj interface{}) ([]interface{}, error)
}
// IndexFunc knows how to provide an indexed value for an object.
type IndexFunc func(obj interface{}) (string, error)
// MetaNamespaceIndexFunc is a convenient default IndexFun which knows how to index
// an object by its namespace.
func MetaNamespaceIndexFunc(obj interface{}) (string, error) {
meta, err := meta.Accessor(obj)
if err != nil {
return "", fmt.Errorf("object has no meta: %v", err)
}
return meta.Namespace(), nil
}
type cache struct { type cache struct {
lock sync.RWMutex lock sync.RWMutex
items map[string]interface{} items map[string]interface{}
// keyFunc is used to make the key for objects stored in and retrieved from items, and // keyFunc is used to make the key for objects stored in and retrieved from items, and
// should be deterministic. // should be deterministic.
keyFunc KeyFunc keyFunc KeyFunc
// indexFunc is used to make the index value for objects stored in an retrieved from index // indexers maps a name to an IndexFunc
indexFunc IndexFunc indexers Indexers
// maps the indexFunc value for an object to a set whose keys are keys in items // indices maps a name to an Index
index map[string]util.StringSet indices Indices
} }
// Add inserts an item into the cache. // Add inserts an item into the cache.
...@@ -100,53 +81,57 @@ func (c *cache) Add(obj interface{}) error { ...@@ -100,53 +81,57 @@ func (c *cache) Add(obj interface{}) error {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
c.items[key] = obj c.items[key] = obj
c.updateIndex(obj) c.updateIndices(obj)
return nil return nil
} }
// updateIndex adds or modifies an object in the index // updateIndices modifies the objects location in the managed indexes
// it is intended to be called from a function that already has a lock on the cache // updateIndices must be called from a function that already has a lock on the cache
func (c *cache) updateIndex(obj interface{}) error { func (c *cache) updateIndices(obj interface{}) error {
if c.indexFunc == nil {
return nil
}
key, err := c.keyFunc(obj) key, err := c.keyFunc(obj)
if err != nil { if err != nil {
return err return err
} }
indexValue, err := c.indexFunc(obj) for name, indexFunc := range c.indexers {
if err != nil { indexValue, err := indexFunc(obj)
return err if err != nil {
} return err
set := c.index[indexValue] }
if set == nil { index := c.indices[name]
set = util.StringSet{} if index == nil {
c.index[indexValue] = set index = Index{}
c.indices[name] = index
}
set := index[indexValue]
if set == nil {
set = util.StringSet{}
index[indexValue] = set
}
set.Insert(key)
} }
set.Insert(key)
return nil return nil
} }
// deleteFromIndex removes an entry from the index // deleteFromIndices removes the object from each of the managed indexes
// it is intended to be called from a function that already has a lock on the cache // it is intended to be called from a function that already has a lock on the cache
func (c *cache) deleteFromIndex(obj interface{}) error { func (c *cache) deleteFromIndices(obj interface{}) error {
if c.indexFunc == nil {
return nil
}
key, err := c.keyFunc(obj) key, err := c.keyFunc(obj)
if err != nil { if err != nil {
return err return err
} }
indexValue, err := c.indexFunc(obj) for name, indexFunc := range c.indexers {
if err != nil { indexValue, err := indexFunc(obj)
return err if err != nil {
} return err
set := c.index[indexValue] }
if set == nil { index := c.indices[name]
set = util.StringSet{} if index != nil {
c.index[indexValue] = set set := index[indexValue]
if set != nil {
set.Delete(key)
}
}
} }
set.Delete(key)
return nil return nil
} }
...@@ -159,7 +144,7 @@ func (c *cache) Update(obj interface{}) error { ...@@ -159,7 +144,7 @@ func (c *cache) Update(obj interface{}) error {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
c.items[key] = obj c.items[key] = obj
c.updateIndex(obj) c.updateIndices(obj)
return nil return nil
} }
...@@ -172,7 +157,7 @@ func (c *cache) Delete(obj interface{}) error { ...@@ -172,7 +157,7 @@ func (c *cache) Delete(obj interface{}) error {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
delete(c.items, key) delete(c.items, key)
c.deleteFromIndex(obj) c.deleteFromIndices(obj)
return nil return nil
} }
...@@ -190,15 +175,21 @@ func (c *cache) List() []interface{} { ...@@ -190,15 +175,21 @@ func (c *cache) List() []interface{} {
// Index returns a list of items that match on the index function // Index returns a list of items that match on the index function
// Index is thread-safe so long as you treat all items as immutable // Index is thread-safe so long as you treat all items as immutable
func (c *cache) Index(obj interface{}) ([]interface{}, error) { func (c *cache) Index(indexName string, obj interface{}) ([]interface{}, error) {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
indexKey, err := c.indexFunc(obj) indexFunc := c.indexers[indexName]
if indexFunc == nil {
return nil, fmt.Errorf("Index with name %s does not exist", indexName)
}
indexKey, err := indexFunc(obj)
if err != nil { if err != nil {
return nil, err return nil, err
} }
set := c.index[indexKey] index := c.indices[indexName]
set := index[indexKey]
list := make([]interface{}, 0, set.Len()) list := make([]interface{}, 0, set.Len())
for _, key := range set.List() { for _, key := range set.List() {
list = append(list, c.items[key]) list = append(list, c.items[key])
...@@ -243,9 +234,9 @@ func (c *cache) Replace(list []interface{}) error { ...@@ -243,9 +234,9 @@ func (c *cache) Replace(list []interface{}) error {
c.items = items c.items = items
// rebuild any index // rebuild any index
c.index = map[string]util.StringSet{} c.indices = Indices{}
for _, item := range c.items { for _, item := range c.items {
c.updateIndex(item) c.updateIndices(item)
} }
return nil return nil
...@@ -253,10 +244,10 @@ func (c *cache) Replace(list []interface{}) error { ...@@ -253,10 +244,10 @@ func (c *cache) Replace(list []interface{}) error {
// NewStore returns a Store implemented simply with a map and a lock. // NewStore returns a Store implemented simply with a map and a lock.
func NewStore(keyFunc KeyFunc) Store { func NewStore(keyFunc KeyFunc) Store {
return &cache{items: map[string]interface{}{}, keyFunc: keyFunc} return &cache{items: map[string]interface{}{}, keyFunc: keyFunc, indexers: Indexers{}, indices: Indices{}}
} }
// NewIndex returns an Index implemented simply with a map and a lock. // NewIndexer returns an Indexer implemented simply with a map and a lock.
func NewIndex(keyFunc KeyFunc, indexFunc IndexFunc) Index { func NewIndexer(keyFunc KeyFunc, indexers Indexers) Indexer {
return &cache{items: map[string]interface{}{}, keyFunc: keyFunc, indexFunc: indexFunc, index: map[string]util.StringSet{}} return &cache{items: map[string]interface{}{}, keyFunc: keyFunc, indexers: indexers, indices: Indices{}}
} }
...@@ -87,7 +87,7 @@ func doTestStore(t *testing.T, store Store) { ...@@ -87,7 +87,7 @@ func doTestStore(t *testing.T, store Store) {
} }
// Test public interface // Test public interface
func doTestIndex(t *testing.T, index Index) { func doTestIndex(t *testing.T, indexer Indexer) {
mkObj := func(id string, val string) testStoreObject { mkObj := func(id string, val string) testStoreObject {
return testStoreObject{id: id, val: val} return testStoreObject{id: id, val: val}
} }
...@@ -97,14 +97,14 @@ func doTestIndex(t *testing.T, index Index) { ...@@ -97,14 +97,14 @@ func doTestIndex(t *testing.T, index Index) {
expected["b"] = util.NewStringSet("a", "c") expected["b"] = util.NewStringSet("a", "c")
expected["f"] = util.NewStringSet("e") expected["f"] = util.NewStringSet("e")
expected["h"] = util.NewStringSet("g") expected["h"] = util.NewStringSet("g")
index.Add(mkObj("a", "b")) indexer.Add(mkObj("a", "b"))
index.Add(mkObj("c", "b")) indexer.Add(mkObj("c", "b"))
index.Add(mkObj("e", "f")) indexer.Add(mkObj("e", "f"))
index.Add(mkObj("g", "h")) indexer.Add(mkObj("g", "h"))
{ {
for k, v := range expected { for k, v := range expected {
found := util.StringSet{} found := util.StringSet{}
indexResults, err := index.Index(mkObj("", k)) indexResults, err := indexer.Index("by_val", mkObj("", k))
if err != nil { if err != nil {
t.Errorf("Unexpected error %v", err) t.Errorf("Unexpected error %v", err)
} }
...@@ -127,6 +127,12 @@ func testStoreIndexFunc(obj interface{}) (string, error) { ...@@ -127,6 +127,12 @@ func testStoreIndexFunc(obj interface{}) (string, error) {
return obj.(testStoreObject).val, nil return obj.(testStoreObject).val, nil
} }
func testStoreIndexers() Indexers {
indexers := Indexers{}
indexers["by_val"] = testStoreIndexFunc
return indexers
}
type testStoreObject struct { type testStoreObject struct {
id string id string
val string val string
...@@ -146,5 +152,5 @@ func TestUndeltaStore(t *testing.T) { ...@@ -146,5 +152,5 @@ func TestUndeltaStore(t *testing.T) {
} }
func TestIndex(t *testing.T) { func TestIndex(t *testing.T) {
doTestIndex(t, NewIndex(testStoreKeyFunc, testStoreIndexFunc)) doTestIndex(t, NewIndexer(testStoreKeyFunc, testStoreIndexers()))
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment