Skip to content

Commit

Permalink
perf(kubernetes): Use a more efficient kind registry (#3845)
Browse files Browse the repository at this point in the history
* refactor(kubernetes): Pull kind identifying information to ScopedKind

I created ScopedKind as a helper class in KubernetesKind but it
really represents the "key" for a given Kind (ie, the unique
information that determines what kind we're talking about as
opposed to other attributes).

Move the name and apiGroup information to live in ScopedKind
to encapsulate this.

* perf(kubernetes): Use a more efficient kind registry

We're always looking up kinds by either name or alias; instead
of storing the kinds as an array and looping on every request,
create a HashMap mapping the name -> KubernetesKind and another
mapping alias -> KubernetesKind so it's easy to look up a kind
by name or alias.
  • Loading branch information
ezimanyi committed Jul 3, 2019
1 parent 4918a88 commit 1664d25
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import javax.annotation.Nullable;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

Expand Down Expand Up @@ -120,10 +119,7 @@ public final class KubernetesKind {
public static final KubernetesKind NONE =
createAndRegisterKind("none", KubernetesApiGroup.NONE, null, true, false);

@Getter @Nonnull private final String name;
@EqualsAndHashCode.Include @Nonnull private final String lcName;
@Getter @Nonnull private final KubernetesApiGroup apiGroup;
@EqualsAndHashCode.Include @Nullable private final KubernetesApiGroup customApiGroup;
@EqualsAndHashCode.Include @Nonnull @Getter ScopedKind scopedKind;

@Getter @Nullable private final String alias;
@Getter private final boolean isNamespaced;
Expand Down Expand Up @@ -153,14 +149,7 @@ private KubernetesKind(
boolean hasClusterRelationship,
boolean isDynamic,
boolean isRegistered) {
this.name = name;
this.lcName = name.toLowerCase();
this.apiGroup = apiGroup;
if (apiGroup.isNativeGroup()) {
this.customApiGroup = null;
} else {
this.customApiGroup = apiGroup;
}
this.scopedKind = new ScopedKind(name, apiGroup);
this.alias = alias;
this.isNamespaced = isNamespaced;
this.hasClusterRelationship = hasClusterRelationship;
Expand All @@ -175,10 +164,7 @@ public boolean hasClusterRelationship() {
@Override
@JsonValue
public String toString() {
if (apiGroup.isNativeGroup()) {
return name;
}
return name + "." + apiGroup.toString();
return scopedKind.toString();
}

@Nonnull
Expand All @@ -200,7 +186,7 @@ private static ScopedKind parseQualifiedKind(@Nonnull String qualifiedKind) {
@Nonnull
public static KubernetesKind fromString(@Nonnull final String name) {
ScopedKind scopedKind = parseQualifiedKind(name);
return fromString(scopedKind.kindName, scopedKind.apiGroup);
return fromString(scopedKind.getName(), scopedKind.getApiGroup());
}

@Nonnull
Expand Down Expand Up @@ -250,7 +236,7 @@ public static KubernetesKind getOrRegisterKind(
public static KubernetesKind getOrRegisterKind(
@Nonnull final String qualifiedName, boolean isNamespaced) {
ScopedKind scopedKind = parseQualifiedKind(qualifiedName);
return getOrRegisterKind(scopedKind.kindName, true, isNamespaced, scopedKind.apiGroup);
return getOrRegisterKind(scopedKind.getName(), true, isNamespaced, scopedKind.getApiGroup());
}

@Nonnull
Expand All @@ -263,9 +249,30 @@ public static List<KubernetesKind> getRegisteredKinds() {
return kindRegistry.getRegisteredKinds();
}

@RequiredArgsConstructor
private static class ScopedKind {
public final String kindName;
public final KubernetesApiGroup apiGroup;
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public static class ScopedKind {
@Getter @Nonnull private final String name;
@EqualsAndHashCode.Include @Nonnull private final String lcName;
@Getter @Nonnull private final KubernetesApiGroup apiGroup;
@EqualsAndHashCode.Include @Nullable private final KubernetesApiGroup customApiGroup;

public ScopedKind(@Nonnull String name, @Nullable KubernetesApiGroup apiGroup) {
this.name = name;
this.lcName = name.toLowerCase();
this.apiGroup = Optional.ofNullable(apiGroup).orElse(KubernetesApiGroup.NONE);
if (this.apiGroup.isNativeGroup()) {
this.customApiGroup = null;
} else {
this.customApiGroup = apiGroup;
}
}

@Override
public String toString() {
if (apiGroup.isNativeGroup()) {
return name;
}
return name + "." + apiGroup.toString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,27 @@
package com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;

public class KubernetesKindRegistry {
private final List<KubernetesKind> values = Collections.synchronizedList(new ArrayList<>());
private final Map<KubernetesKind.ScopedKind, KubernetesKind> nameMap = new ConcurrentHashMap<>();
private final Map<KubernetesKind.ScopedKind, KubernetesKind> aliasMap = new ConcurrentHashMap<>();

/** Registers a given {@link KubernetesKind} into the registry and returns the kind */
@Nonnull
public KubernetesKind registerKind(@Nonnull KubernetesKind kind) {
values.add(kind);
public synchronized KubernetesKind registerKind(@Nonnull KubernetesKind kind) {
nameMap.put(kind.getScopedKind(), kind);
if (kind.getAlias() != null) {
aliasMap.put(
new KubernetesKind.ScopedKind(kind.getAlias(), kind.getScopedKind().getApiGroup()), kind);
}
return kind;
}

Expand Down Expand Up @@ -71,33 +75,23 @@ public Optional<KubernetesKind> getRegisteredKind(
throw new IllegalArgumentException("The 'NONE' kind cannot be read.");
}

Predicate<KubernetesKind> groupMatches =
kind -> {
// Exact match
if (Objects.equals(kind.getApiGroup(), apiGroup)) {
return true;
}

// If we have not specified an API group, default to finding a native kind that matches
if (apiGroup == null || apiGroup.isNativeGroup()) {
return kind.getApiGroup().isNativeGroup();
}
KubernetesKind.ScopedKind searchKey = new KubernetesKind.ScopedKind(name, apiGroup);
KubernetesKind result = nameMap.get(searchKey);
if (result != null) {
return Optional.of(result);
}

return false;
};
result = aliasMap.get(searchKey);
if (result != null) {
return Optional.of(result);
}

return values.stream()
.filter(
v ->
v.getName().equalsIgnoreCase(name)
|| (v.getAlias() != null && v.getAlias().equalsIgnoreCase(name)))
.filter(groupMatches)
.findAny();
return Optional.empty();
}

/** Returns a list of all registered kinds */
@Nonnull
public List<KubernetesKind> getRegisteredKinds() {
return values;
return new ArrayList<>(nameMap.values());
}
}

0 comments on commit 1664d25

Please sign in to comment.