Skip to content

Commit

Permalink
feat(provider/kubernetes): dynamic target selection (spinnaker#3058)
Browse files Browse the repository at this point in the history
  • Loading branch information
lwander authored and clanesf committed Dec 8, 2018
1 parent cdc2d31 commit 92b7ea3
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@

package com.netflix.spinnaker.clouddriver.model;

public interface ManifestProvider <T extends Manifest> {
import java.util.List;

public interface ManifestProvider<T extends Manifest> {
enum Sort {
AGE, SIZE
}

T getManifest(String account, String location, String name);
List<T> getClusterAndSortAscending(String account, String location, String kind, String app, String cluster, Sort sort);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,16 @@

package com.netflix.spinnaker.clouddriver.model;

import java.util.List;

public class NoopManifestProvider implements ManifestProvider<Manifest> {
@Override
public Manifest getManifest(String account, String location, String name) {
return null;
}

@Override
public List<Manifest> getClusterAndSortAscending(String account, String location, String kind, String app, String cluster, Sort sort) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -113,6 +114,10 @@ public Collection<CacheData> getAllRelationshipsOfSpinnakerKind(Collection<Cache
.collect(Collectors.toList());
}

public Collection<CacheData> loadRelationshipsFromCache(CacheData source, String relationshipType) {
return loadRelationshipsFromCache(Collections.singleton(source), relationshipType);
}

public Collection<CacheData> loadRelationshipsFromCache(Collection<CacheData> sources, String relationshipType) {
List<String> keys = cleanupCollection(sources).stream()
.map(CacheData::getRelationships)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.Keys;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCacheDataConverter;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.view.model.KubernetesV2Manifest;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.KubernetesPodMetric;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.KubernetesResourceProperties;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.KubernetesResourcePropertyRegistry;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesKind;
Expand All @@ -35,15 +34,18 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.Keys.LogicalKind.CLUSTERS;

@Component
@Slf4j
public class KubernetesV2ManifestProvider implements ManifestProvider<KubernetesV2Manifest> {
Expand Down Expand Up @@ -84,6 +86,35 @@ public KubernetesV2Manifest getManifest(String account, String location, String
}

CacheData data = dataOptional.get();

return fromCacheData(data, account);
}

@Override
public List<KubernetesV2Manifest> getClusterAndSortAscending(String account, String location, String kind, String app, String cluster, Sort sort) {
KubernetesResourceProperties properties = registry.get(account, KubernetesKind.fromString(kind));
if (properties == null) {
return null;
}

KubernetesHandler handler = properties.getHandler();

return cacheUtils.getSingleEntry(CLUSTERS.toString(), Keys.cluster(account, app, cluster))
.map(c -> cacheUtils.loadRelationshipsFromCache(c, kind).stream()
.map(cd -> fromCacheData(cd, account)) // todo(lwander) perf improvement by checking namespace before converting
.filter(Objects::nonNull)
.filter(m -> m.getLocation().equals(location))
.sorted((m1, m2) -> handler.comparatorFor(sort).compare(m1.getManifest(), m2.getManifest()))
.collect(Collectors.toList()))
.orElse(new ArrayList<>());
}

private KubernetesV2Manifest fromCacheData(CacheData data, String account) {
KubernetesManifest manifest = KubernetesCacheDataConverter.getManifest(data);
String namespace = manifest.getNamespace();
KubernetesKind kind = manifest.getKind();
String key = data.getId();

KubernetesResourceProperties properties = registry.get(account, kind);
if (properties == null) {
return null;
Expand All @@ -97,19 +128,18 @@ public KubernetesV2Manifest getManifest(String account, String location, String
.sorted(Comparator.comparing(lastEventTimestamp))
.collect(Collectors.toList());

String metricKey = Keys.metric(kind, account, location, parsedName.getRight());
Moniker moniker = KubernetesCacheDataConverter.getMoniker(data);

String metricKey = Keys.metric(kind, account, namespace, manifest.getName());
List<Map> metrics = cacheUtils.getSingleEntry(Keys.Kind.KUBERNETES_METRIC.toString(), metricKey)
.map(KubernetesCacheDataConverter::getMetrics)
.orElse(Collections.emptyList());

KubernetesHandler handler = properties.getHandler();

KubernetesManifest manifest = KubernetesCacheDataConverter.getManifest(data);
Moniker moniker = KubernetesCacheDataConverter.getMoniker(data);

return new KubernetesV2Manifest().builder()
.account(account)
.location(location)
.location(namespace)
.manifest(manifest)
.moniker(moniker)
.status(handler.status(manifest))
Expand All @@ -118,5 +148,6 @@ public KubernetesV2Manifest getManifest(String account, String location, String
.warnings(handler.listWarnings(manifest))
.metrics(metrics)
.build();

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public void setNamespace(String namespace) {
public String getCreationTimestamp() {
return getMetadata().containsKey("creationTimestamp")
? getMetadata().get("creationTimestamp").toString()
: null;
: "";
}

@JsonIgnore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.netflix.spinnaker.clouddriver.kubernetes.v2.security.KubernetesV2Credentials;
import com.netflix.spinnaker.clouddriver.model.Manifest.Status;
import com.netflix.spinnaker.clouddriver.model.Manifest.Warning;
import com.netflix.spinnaker.clouddriver.model.ManifestProvider;
import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -41,6 +42,7 @@
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -183,4 +185,26 @@ public static DeployPriority fromString(String val) {
.orElseThrow(() -> new IllegalArgumentException("No such priority '" + val + "'"));
}
}

public Comparator<KubernetesManifest> comparatorFor(ManifestProvider.Sort sort) {
switch (sort) {
case AGE:
return ageComparator();
case SIZE:
return sizeComparator();
default:
throw new IllegalArgumentException("No comparator for " + sort + " found");

}
}

// can be overridden by each handler
protected Comparator<KubernetesManifest> ageComparator() {
return Comparator.comparing(KubernetesManifest::getCreationTimestamp);
}

// can be overridden by each handler
protected Comparator<KubernetesManifest> sizeComparator() {
return Comparator.comparing(m -> m.getReplicas() == null ? -1 : m.getReplicas());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@

import com.netflix.spinnaker.clouddriver.model.Manifest;
import com.netflix.spinnaker.clouddriver.model.ManifestProvider;
import com.netflix.spinnaker.clouddriver.model.ManifestProvider.Sort;
import com.netflix.spinnaker.clouddriver.requestqueue.RequestQueue;
import com.netflix.spinnaker.kork.web.exceptions.NotFoundException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PostAuthorize;
Expand Down Expand Up @@ -87,7 +89,66 @@ Manifest getForAccountLocationAndName(@PathVariable String account,

@RequestMapping(value = "/{account:.+}/{name:.+}", method = RequestMethod.GET)
Manifest getForAccountLocationAndName(@PathVariable String account,
@PathVariable String name) {
@PathVariable String name) {
return getForAccountLocationAndName(account, "", name);
}

@RequestMapping(value = "/{account:.+}/{location:.+}/{kind:.+}/cluster/{app:.+}/{cluster:.+}/dynamic/{criteria:.+}", method = RequestMethod.GET)
Manifest getDynamicManifestFromCluster(@PathVariable String account,
@PathVariable String location,
@PathVariable String kind,
@PathVariable String app,
@PathVariable String cluster,
@PathVariable Criteria criteria) {
final String request = String.format("(account: %s, location: %s, kind: %s, app %s, cluster: %s, criteria: %s)", account, location, kind, app, cluster, criteria);
List<List<Manifest>> manifestSet = manifestProviders.stream()
.map(p -> {
try {
return (List<Manifest>) requestQueue.execute(account, () -> p.getClusterAndSortAscending(account, location, kind, app, cluster, criteria.getSort()));
} catch (Throwable t) {
log.warn("Failed to read {}", request, t);
return null;
}
}).filter(l -> l != null && !l.isEmpty())
.collect(Collectors.toList());

if (manifestSet.isEmpty()) {
throw new NotFoundException("No manifests matching " + request + " found");
} else if (manifestSet.size() > 1) {
throw new IllegalStateException("Multiple sets of manifests matching " + request + " found");
}

List<Manifest> manifests = manifestSet.get(0);
try {
switch (criteria) {
case oldest:
case smallest:
return manifests.get(0);
case newest:
case largest:
return manifests.get(manifests.size() - 1);
case second_newest:
return manifests.get(manifests.size() - 2);
default:
throw new IllegalArgumentException("Unknown criteria: " + criteria);
}
} catch (IndexOutOfBoundsException e) {
throw new NotFoundException("No manifests matching " + request + " found");
}
}

enum Criteria {
oldest(Sort.AGE),
newest(Sort.AGE),
second_newest(Sort.AGE),
largest(Sort.SIZE),
smallest(Sort.SIZE);

@Getter
private final Sort sort;

Criteria(Sort sort) {
this.sort = sort;
}
}
}

0 comments on commit 92b7ea3

Please sign in to comment.