Support multiple index functions, address feedback

This commit is contained in:
derekwaynecarr 2015-02-05 13:22:28 -05:00
parent 7a2d63048d
commit 1cf69bdefc
3 changed files with 120 additions and 71 deletions

52
pkg/client/cache/index.go vendored Normal file
View File

@ -0,0 +1,52 @@
/*
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

View File

@ -60,35 +60,16 @@ func MetaNamespaceKeyFunc(obj interface{}) (string, error) {
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 {
lock sync.RWMutex
items map[string]interface{}
// keyFunc is used to make the key for objects stored in and retrieved from items, and
// should be deterministic.
keyFunc KeyFunc
// indexFunc is used to make the index value for objects stored in an retrieved from index
indexFunc IndexFunc
// maps the indexFunc value for an object to a set whose keys are keys in items
index map[string]util.StringSet
// indexers maps a name to an IndexFunc
indexers Indexers
// indices maps a name to an Index
indices Indices
}
// Add inserts an item into the cache.
@ -100,53 +81,57 @@ func (c *cache) Add(obj interface{}) error {
c.lock.Lock()
defer c.lock.Unlock()
c.items[key] = obj
c.updateIndex(obj)
c.updateIndices(obj)
return nil
}
// updateIndex adds or modifies an object in the index
// it is intended to be called from a function that already has a lock on the cache
func (c *cache) updateIndex(obj interface{}) error {
if c.indexFunc == nil {
return nil
}
// updateIndices modifies the objects location in the managed indexes
// updateIndices must be called from a function that already has a lock on the cache
func (c *cache) updateIndices(obj interface{}) error {
key, err := c.keyFunc(obj)
if err != nil {
return err
}
indexValue, err := c.indexFunc(obj)
if err != nil {
return err
for name, indexFunc := range c.indexers {
indexValue, err := indexFunc(obj)
if err != nil {
return err
}
index := c.indices[name]
if index == nil {
index = Index{}
c.indices[name] = index
}
set := index[indexValue]
if set == nil {
set = util.StringSet{}
index[indexValue] = set
}
set.Insert(key)
}
set := c.index[indexValue]
if set == nil {
set = util.StringSet{}
c.index[indexValue] = set
}
set.Insert(key)
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
func (c *cache) deleteFromIndex(obj interface{}) error {
if c.indexFunc == nil {
return nil
}
func (c *cache) deleteFromIndices(obj interface{}) error {
key, err := c.keyFunc(obj)
if err != nil {
return err
}
indexValue, err := c.indexFunc(obj)
if err != nil {
return err
for name, indexFunc := range c.indexers {
indexValue, err := indexFunc(obj)
if err != nil {
return err
}
index := c.indices[name]
if index != nil {
set := index[indexValue]
if set != nil {
set.Delete(key)
}
}
}
set := c.index[indexValue]
if set == nil {
set = util.StringSet{}
c.index[indexValue] = set
}
set.Delete(key)
return nil
}
@ -159,7 +144,7 @@ func (c *cache) Update(obj interface{}) error {
c.lock.Lock()
defer c.lock.Unlock()
c.items[key] = obj
c.updateIndex(obj)
c.updateIndices(obj)
return nil
}
@ -172,7 +157,7 @@ func (c *cache) Delete(obj interface{}) error {
c.lock.Lock()
defer c.lock.Unlock()
delete(c.items, key)
c.deleteFromIndex(obj)
c.deleteFromIndices(obj)
return nil
}
@ -190,15 +175,21 @@ func (c *cache) List() []interface{} {
// 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
func (c *cache) Index(obj interface{}) ([]interface{}, error) {
func (c *cache) Index(indexName string, obj interface{}) ([]interface{}, error) {
c.lock.RLock()
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 {
return nil, err
}
set := c.index[indexKey]
index := c.indices[indexName]
set := index[indexKey]
list := make([]interface{}, 0, set.Len())
for _, key := range set.List() {
list = append(list, c.items[key])
@ -243,9 +234,9 @@ func (c *cache) Replace(list []interface{}) error {
c.items = items
// rebuild any index
c.index = map[string]util.StringSet{}
c.indices = Indices{}
for _, item := range c.items {
c.updateIndex(item)
c.updateIndices(item)
}
return nil
@ -253,10 +244,10 @@ func (c *cache) Replace(list []interface{}) error {
// NewStore returns a Store implemented simply with a map and a lock.
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.
func NewIndex(keyFunc KeyFunc, indexFunc IndexFunc) Index {
return &cache{items: map[string]interface{}{}, keyFunc: keyFunc, indexFunc: indexFunc, index: map[string]util.StringSet{}}
// NewIndexer returns an Indexer implemented simply with a map and a lock.
func NewIndexer(keyFunc KeyFunc, indexers Indexers) Indexer {
return &cache{items: map[string]interface{}{}, keyFunc: keyFunc, indexers: indexers, indices: Indices{}}
}

View File

@ -87,7 +87,7 @@ func doTestStore(t *testing.T, store Store) {
}
// Test public interface
func doTestIndex(t *testing.T, index Index) {
func doTestIndex(t *testing.T, indexer Indexer) {
mkObj := func(id string, val string) testStoreObject {
return testStoreObject{id: id, val: val}
}
@ -97,14 +97,14 @@ func doTestIndex(t *testing.T, index Index) {
expected["b"] = util.NewStringSet("a", "c")
expected["f"] = util.NewStringSet("e")
expected["h"] = util.NewStringSet("g")
index.Add(mkObj("a", "b"))
index.Add(mkObj("c", "b"))
index.Add(mkObj("e", "f"))
index.Add(mkObj("g", "h"))
indexer.Add(mkObj("a", "b"))
indexer.Add(mkObj("c", "b"))
indexer.Add(mkObj("e", "f"))
indexer.Add(mkObj("g", "h"))
{
for k, v := range expected {
found := util.StringSet{}
indexResults, err := index.Index(mkObj("", k))
indexResults, err := indexer.Index("by_val", mkObj("", k))
if err != nil {
t.Errorf("Unexpected error %v", err)
}
@ -127,6 +127,12 @@ func testStoreIndexFunc(obj interface{}) (string, error) {
return obj.(testStoreObject).val, nil
}
func testStoreIndexers() Indexers {
indexers := Indexers{}
indexers["by_val"] = testStoreIndexFunc
return indexers
}
type testStoreObject struct {
id string
val string
@ -146,5 +152,5 @@ func TestUndeltaStore(t *testing.T) {
}
func TestIndex(t *testing.T) {
doTestIndex(t, NewIndex(testStoreKeyFunc, testStoreIndexFunc))
doTestIndex(t, NewIndexer(testStoreKeyFunc, testStoreIndexers()))
}