Skip to content

Commit

Permalink
feat(k8s): add stability check for HPA (#4363)
Browse files Browse the repository at this point in the history
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
perek and mergify[bot] authored Feb 28, 2020
1 parent 60e83b0 commit 2c62055
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@
import com.google.common.collect.ImmutableList;
import com.netflix.spinnaker.clouddriver.kubernetes.description.SpinnakerKind;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.Replacer;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCacheDataConverter;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesCoreCachingAgent;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.caching.agent.KubernetesV2CachingAgentFactory;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesKind;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesManifest;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.model.Manifest.Status;
import io.kubernetes.client.openapi.models.V1HorizontalPodAutoscaler;
import io.kubernetes.client.openapi.models.V1HorizontalPodAutoscalerStatus;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.springframework.stereotype.Component;

@Component
Expand Down Expand Up @@ -62,9 +66,43 @@ public SpinnakerKind spinnakerKind() {

@Override
public Status status(KubernetesManifest manifest) {
V1HorizontalPodAutoscaler hpa =
KubernetesCacheDataConverter.getResource(manifest, V1HorizontalPodAutoscaler.class);
return status(hpa);
}

private Status status(V1HorizontalPodAutoscaler hpa) {
V1HorizontalPodAutoscalerStatus status = hpa.getStatus();
if (status == null) {
return Status.noneReported();
}

int desiredReplicas = defaultToZero(status.getDesiredReplicas());
int existing = defaultToZero(status.getCurrentReplicas());
if (desiredReplicas > existing) {
return Status.defaultStatus()
.unstable(
String.format(
"Waiting for HPA to complete a scale up, current: %d desired: %d",
existing, desiredReplicas));
}

if (desiredReplicas < existing) {
return Status.defaultStatus()
.unstable(
String.format(
"Waiting for HPA to complete a scale down, current: %d desired: %d",
existing, desiredReplicas));
}
// desiredReplicas == existing, this is now stable
return Status.defaultStatus();
}

// Unboxes an Integer, returning 0 if the input is null
private static int defaultToZero(@Nullable Integer input) {
return input == null ? 0 : input;
}

@Override
protected KubernetesV2CachingAgentFactory cachingAgentFactory() {
return KubernetesCoreCachingAgent::new;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2020 Snap 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 com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler;

import static org.assertj.core.api.Assertions.assertThat;

import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesManifest;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.model.Manifest.Status;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
final class KubernetesHorizontalPodAutoscalerHandlerTest {
private KubernetesHorizontalPodAutoscalerHandler handler =
new KubernetesHorizontalPodAutoscalerHandler();

@Test
void noStatus() {
KubernetesManifest hpa = ManifestFetcher.getManifest("horizontalpodautoscaler/base.yml");
Status status = handler.status(hpa);

assertThat(status.getStable().isState()).isFalse();
assertThat(status.getStable().getMessage()).isEqualTo("No status reported yet");
assertThat(status.getAvailable().isState()).isFalse();
assertThat(status.getPaused().isState()).isFalse();
assertThat(status.getFailed().isState()).isFalse();
}

@Test
void waitingForScaleup() {
KubernetesManifest hpa =
ManifestFetcher.getManifest(
"horizontalpodautoscaler/base.yml", "horizontalpodautoscaler/waiting-for-scaleup.yml");
Status status = handler.status(hpa);

assertThat(status.getStable().isState()).isFalse();
assertThat(status.getStable().getMessage())
.isEqualTo("Waiting for HPA to complete a scale up, current: 2 desired: 3");
assertThat(status.getAvailable().isState()).isTrue();
assertThat(status.getPaused().isState()).isFalse();
assertThat(status.getFailed().isState()).isFalse();
}

@Test
void waitingForScaledown() {
KubernetesManifest hpa =
ManifestFetcher.getManifest(
"horizontalpodautoscaler/base.yml",
"horizontalpodautoscaler/waiting-for-scaledown.yml");
Status status = handler.status(hpa);

assertThat(status.getStable().isState()).isFalse();
assertThat(status.getStable().getMessage())
.isEqualTo("Waiting for HPA to complete a scale down, current: 5 desired: 2");
assertThat(status.getAvailable().isState()).isTrue();
assertThat(status.getPaused().isState()).isFalse();
assertThat(status.getFailed().isState()).isFalse();
}

@Test
void noReplicas() {
KubernetesManifest hpa =
ManifestFetcher.getManifest(
"horizontalpodautoscaler/base.yml", "horizontalpodautoscaler/no-replicas.yml");
Status status = handler.status(hpa);

assertThat(status.getStable().isState()).isTrue();
assertThat(status.getAvailable().isState()).isTrue();
assertThat(status.getPaused().isState()).isFalse();
assertThat(status.getFailed().isState()).isFalse();
}

@Test
void stable() {
KubernetesManifest hpa =
ManifestFetcher.getManifest(
"horizontalpodautoscaler/base.yml", "horizontalpodautoscaler/stable.yml");
Status status = handler.status(hpa);

assertThat(status.getStable().isState()).isTrue();
assertThat(status.getAvailable().isState()).isTrue();
assertThat(status.getPaused().isState()).isFalse();
assertThat(status.getFailed().isState()).isFalse();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
annotations: {}
creationTimestamp: '2020-02-10T21:45:07Z'
labels: {}
name: test-hpa
namespace: default
resourceVersion: '169474131'
selfLink: >-
/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/test-hpa
uid: 00000000-0000-0000-0000-000000000000
spec:
maxReplicas: 5
minReplicas: 2
scaleTargetRef:
apiVersion: extensions/v1beta1
kind: Deployment
name: test-deployment
targetCPUUtilizationPercentage: 40
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
status:
currentReplicas: 0
desiredReplicas: 0
lastScaleTime: '2020-02-25T20:32:16Z'
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
status:
currentReplicas: 2
desiredReplicas: 2
lastScaleTime: '2020-02-25T20:32:16Z'
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
status:
currentReplicas: 5
desiredReplicas: 2
lastScaleTime: '2020-02-25T20:32:16Z'
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
status:
currentReplicas: 2
desiredReplicas: 3
lastScaleTime: '2020-02-25T20:32:16Z'

0 comments on commit 2c62055

Please sign in to comment.