
io.fabric8.kubernetes.client.informers.cache.ReducedStateItemStore Maven / Gradle / Ivy
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* 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.fabric8.kubernetes.client.informers.cache;
import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* By default an informer stores every item that exists in memory.
* If that is too much memory for your application,
* you may instead reduce what is actually stored by using this item store.
*/
public class ReducedStateItemStore implements ItemStore {
private static final String METADATA = "metadata";
private final ConcurrentHashMap store = new ConcurrentHashMap<>();
private final List fields = new ArrayList<>();
private final Class typeClass;
private final KeyState keyState;
private KubernetesSerialization serialization;
public static class KeyState {
final Function keyFunction;
final Function keyFieldFunction;
final List keyFields;
/**
* The key function must decompose a given key into the given fields - in field order
*
* @param keyFieldFunction to convert a key into fields
* @param keyFields the fields represented by the key
*/
public KeyState(Function keyFunction, Function keyFieldFunction,
String[]... keyFields) {
this.keyFunction = keyFunction;
this.keyFieldFunction = keyFieldFunction;
this.keyFields = Arrays.asList(keyFields);
}
}
public static final KeyState NAME_KEY_STATE = new KeyState(Cache::metaNamespaceKeyFunc,
k -> {
int index = k.indexOf("/");
if (index == -1) {
return new String[] { null, k };
}
return new String[] { k.substring(0, index), k.substring(index + 1) };
}, new String[] { METADATA, "namespace" }, new String[] { METADATA, "name" });
public static final KeyState UID_KEY_STATE = new KeyState(Cache::metaUidKeyFunc,
k -> new String[] { k }, new String[] { METADATA, "uid" });
/**
* Create a state store with only the fields specified.
*
* metadata.resourceVersion - will automatically be saved as will
* the necessary key fields.
*
* If you are using custom indexers, then the fields used by those
* indexes must be added to the valueFields - otherwise the indexer won't be able to delete the
* index entries when the item is removed.
*
* For example in level event handling systems all you may need beyond the
* key is the ownerReferences. You would use withValueFields("metadata.ownerReferences")
* for that.
*
* NOTE: If you use this feature, you should only use the informer cache/store for basic
* existence checks and maintain your own cache of full resource objects.
*
* Only simple names are allowed in field paths - '.' is reserved as the separator.
*
* Whatever is provided as the {@link KeyState} should match the keyFunction provided to the informer.
*
* @param keyState information about the key fields/function
* @param typeClass the expected type
* @param valueFields the additional fields to save
*/
public ReducedStateItemStore(KeyState keyState, Class typeClass, KubernetesSerialization serialization,
String... valueFields) {
this.keyState = keyState;
fields.add(new String[] { METADATA, "resourceVersion" });
if (valueFields != null) {
for (int i = 0; i < valueFields.length; i++) {
fields.add(valueFields[i].split("\\."));
}
}
this.typeClass = typeClass;
this.serialization = serialization;
}
Object[] store(V value) {
if (value == null) {
return null;
}
Map raw = serialization.convertValue(value, Map.class);
return fields.stream().map(f -> GenericKubernetesResource.get(raw, (Object[]) f)).toArray();
}
V restore(String key, Object[] values) {
if (values == null) {
return null;
}
Map raw = new HashMap<>();
applyFields(values, raw, this.fields);
String[] keyParts = this.keyState.keyFieldFunction.apply(key);
applyFields(keyParts, raw, this.keyState.keyFields);
return serialization.convertValue(raw, typeClass);
}
private static void applyFields(Object[] values, Map raw, List fields) {
for (int i = 0; i < fields.size(); i++) {
Object value = values[i];
if (value == null) {
continue;
}
String[] path = fields.get(i);
Map parent = raw;
for (int j = 0; j < path.length - 1; j++) {
parent = (Map) parent.computeIfAbsent(path[j], k -> new LinkedHashMap());
}
parent.put(path[path.length - 1], value);
}
}
@Override
public V put(String key, V obj) {
return restore(key, store.put(key, store(obj)));
}
@Override
public V remove(String key) {
return restore(key, store.remove(key));
}
@Override
public Stream keySet() {
return store.keySet().stream();
}
@Override
public Stream values() {
return store.entrySet().stream().map(e -> restore(e.getKey(), e.getValue()));
}
@Override
public V get(String key) {
return restore(key, store.get(key));
}
public String getResourceVersion(String key) {
return (String) store.getOrDefault(key, new Object[1])[0];
}
@Override
public int size() {
return store.size();
}
@Override
public String getKey(V obj) {
return this.keyState.keyFunction.apply(obj);
}
@Override
public boolean isFullState() {
return false;
}
}