io.kubernetes.client.informer.cache.Cache Maven / Gradle / Ivy
/*
Copyright 2020 The Kubernetes Authors.
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 io.kubernetes.client.informer.cache;
import io.kubernetes.client.common.KubernetesObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
/**
* Cache is a java port of k/client-go's ThreadSafeStore. It basically saves and indexes all the
* entries.
*/
// TODO(yue9944882): Cache is very similar to a Map, replace/inherit w/ Map interface
public class Cache implements Indexer {
/** keyFunc defines how to map objects into indices */
private Function keyFunc;
/** indexers stores index functions by their names */
private Map>> indexers = new HashMap<>();
/** items stores object instances */
private Map items = new HashMap<>();
/** indices stores objects' keys by their indices */
private Map>> indices = new HashMap<>();
public Cache() {
this(
Caches.NAMESPACE_INDEX,
Caches::metaNamespaceIndexFunc,
Caches::deletionHandlingMetaNamespaceKeyFunc);
}
/**
* Constructor.
*
* @param indexName the index name, an unique name representing the index
* @param indexFunc the index func by which we map multiple object to an index for querying
* @param keyFunc the key func by which we map one object to an unique key for storing
*/
public Cache(
String indexName,
Function> indexFunc,
Function keyFunc) {
this.indexers.put(indexName, indexFunc);
this.keyFunc = keyFunc;
this.indices.put(indexName, new HashMap<>());
}
/**
* Add objects.
*
* @param obj the obj
*/
@Override
public void add(ApiType obj) {
String key = keyFunc.apply(obj);
synchronized (this) {
ApiType oldObj = this.items.get(key);
this.items.put(key, obj);
this.updateIndices(oldObj, obj, key);
}
}
/**
* Update the object.
*
* @param obj the obj
*/
@Override
public void update(ApiType obj) {
String key = keyFunc.apply(obj);
synchronized (this) {
ApiType oldObj = this.items.get(key);
this.items.put(key, obj);
updateIndices(oldObj, obj, key);
}
}
/**
* Delete the object.
*
* @param obj the obj
*/
@Override
public void delete(ApiType obj) {
String key = keyFunc.apply(obj);
synchronized (this) {
boolean exists = this.items.containsKey(key);
if (exists) {
this.deleteFromIndices(this.items.get(key), key);
this.items.remove(key);
}
}
}
/**
* Replace the content in the cache completely.
*
* @param list the list
* @param resourceVersion the resource version
*/
@Override
public synchronized void replace(List list, String resourceVersion) {
Map newItems = new HashMap<>();
for (ApiType item : list) {
String key = keyFunc.apply(item);
newItems.put(key, item);
}
this.items = newItems;
// rebuild any index
this.indices = new HashMap<>();
for (Map.Entry itemEntry : items.entrySet()) {
this.updateIndices(null, itemEntry.getValue(), itemEntry.getKey());
}
}
/** Resync. */
@Override
public void resync() {
// Do nothing by default
}
/**
* List keys.
*
* @return the list
*/
@Override
public synchronized List listKeys() {
List keys = new ArrayList<>(this.items.size());
for (Map.Entry entry : this.items.entrySet()) {
keys.add(entry.getKey());
}
return keys;
}
/**
* Get object t.
*
* @param obj the obj
* @return the t
*/
@Override
public ApiType get(ApiType obj) {
String key = this.keyFunc.apply(obj);
synchronized (this) {
return this.getByKey(key);
}
}
/**
* List all objects in the cache.
*
* @return the list
*/
@Override
public synchronized List list() {
List itemList = new ArrayList<>(this.items.size());
for (Map.Entry entry : this.items.entrySet()) {
itemList.add(entry.getValue());
}
return itemList;
}
/**
* Gets get by key.
*
* @param key the key
* @return the get by key
*/
@Override
public synchronized ApiType getByKey(String key) {
return this.items.get(key);
}
/**
* Get objects .
*
* @param indexName the index name
* @param obj the obj
* @return the list
*/
@Override
public synchronized List index(String indexName, ApiType obj) {
if (!this.indexers.containsKey(indexName)) {
throw new IllegalArgumentException(String.format("index %s doesn't exist!", indexName));
}
Function> indexFunc = this.indexers.get(indexName);
List indexKeys = indexFunc.apply((ApiType) obj);
Map> index = this.indices.get(indexName);
if (MapUtils.isEmpty(index)) {
return new ArrayList<>();
}
Set returnKeySet = new HashSet<>();
for (String indexKey : indexKeys) {
Set set = index.get(indexKey);
if (CollectionUtils.isEmpty(set)) {
continue;
}
returnKeySet.addAll(set);
}
List items = new ArrayList<>(returnKeySet.size());
for (String absoluteKey : returnKeySet) {
items.add(this.items.get(absoluteKey));
}
return items;
}
/**
* Index keys list.
*
* @param indexName the index name
* @param indexKey the index key
* @return the list
*/
@Override
public synchronized List indexKeys(String indexName, String indexKey) {
if (!this.indexers.containsKey(indexName)) {
throw new IllegalArgumentException(String.format("index %s doesn't exist!", indexName));
}
Map> index = this.indices.get(indexName);
Set set = index.get(indexKey);
if (set == null) {
return Collections.EMPTY_LIST;
}
List keys = new ArrayList<>(set.size());
for (String key : set) {
keys.add(key);
}
return keys;
}
/**
* By index list.
*
* @param indexName the index name
* @param indexKey the index key
* @return the list
*/
@Override
public synchronized List byIndex(String indexName, String indexKey) {
if (!this.indexers.containsKey(indexName)) {
throw new IllegalArgumentException(String.format("index %s doesn't exist!", indexName));
}
Map> index = this.indices.get(indexName);
Set set = index.get(indexKey);
if (set == null) {
return Collections.EMPTY_LIST;
}
List items = new ArrayList<>(set.size());
for (String key : set) {
items.add(this.items.get(key));
}
return items;
}
/**
* Return the indexers registered with the cache.
*
* @return registered indexers
*/
@Override
public Map>> getIndexers() {
return indexers;
}
/**
* Add additional indexers to the cache.
*
* @param newIndexers indexers to add
*/
@Override
public void addIndexers(Map>> newIndexers) {
if (!items.isEmpty()) {
throw new IllegalStateException("cannot add indexers to a non-empty cache");
}
Set oldKeys = indexers.keySet();
Set newKeys = newIndexers.keySet();
Set intersection = new HashSet<>(oldKeys);
intersection.retainAll(newKeys);
if (!intersection.isEmpty()) {
throw new IllegalArgumentException("indexer conflict: " + intersection);
}
for (Map.Entry>> indexEntry : newIndexers.entrySet()) {
addIndexFunc(indexEntry.getKey(), indexEntry.getValue());
}
}
/**
* updateIndices modifies the objects location in the managed indexes, if this is an update, you
* must provide an oldObj.
*
* Note: updateIndices must be called from a function that already has a lock on the cache.
*
* @param oldObj the old obj
* @param newObj the new obj
* @param key the key
*/
public void updateIndices(ApiType oldObj, ApiType newObj, String key) {
// if we got an old object, we need to remove it before we can add
// it again.
if (oldObj != null) {
deleteFromIndices(oldObj, key);
}
for (Map.Entry>> indexEntry : indexers.entrySet()) {
String indexName = indexEntry.getKey();
Function> indexFunc = indexEntry.getValue();
List indexValues = indexFunc.apply(newObj);
if (CollectionUtils.isEmpty(indexValues)) {
continue;
}
Map> index =
this.indices.computeIfAbsent(indexName, k -> new HashMap<>());
for (String indexValue : indexValues) {
Set indexSet = index.computeIfAbsent(indexValue, k -> new HashSet<>());
indexSet.add(key);
}
}
}
/**
* deleteFromIndices removes the object from each of the managed indexes.
*
* Note: It is intended to be called from a function that already has a lock on the cache.
*
* @param oldObj the old obj
* @param key the key
*/
private void deleteFromIndices(ApiType oldObj, String key) {
for (Map.Entry>> indexEntry : this.indexers.entrySet()) {
Function> indexFunc = indexEntry.getValue();
List indexValues = indexFunc.apply(oldObj);
if (CollectionUtils.isEmpty(indexValues)) {
continue;
}
Map> index = this.indices.get(indexEntry.getKey());
if (index == null) {
continue;
}
for (String indexValue : indexValues) {
Set indexSet = index.get(indexValue);
if (indexSet != null) {
indexSet.remove(key);
}
}
}
}
/**
* Add index func.
*
* @param indexName the index name
* @param indexFunc the index func
*/
public void addIndexFunc(String indexName, Function> indexFunc) {
this.indices.put(indexName, new HashMap<>());
this.indexers.put(indexName, indexFunc);
}
public Function getKeyFunc() {
return keyFunc;
}
public void setKeyFunc(Function keyFunc) {
this.keyFunc = keyFunc;
}
}