Skip to content
This repository has been archived by the owner on May 14, 2022. It is now read-only.

Commit

Permalink
Step scaling custom dimensions (#1191)
Browse files Browse the repository at this point in the history
Step scaling custom dimensions support for job auto scaling
  • Loading branch information
amit-git committed Dec 8, 2021
1 parent c3475a9 commit efcff9d
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.netflix.titus.api.appscale.model;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

import com.fasterxml.jackson.annotation.JsonProperty;
Expand All @@ -32,10 +34,11 @@ public class AlarmConfiguration {
private final String metricNamespace;
private final String metricName;
private final Statistic statistic;
private final List<MetricDimension> dimensions;

public AlarmConfiguration(String name, String region, Optional<Boolean> actionsEnabled, ComparisonOperator comparisonOperator,
int evaluationPeriods, int periodSec, double threshold, String metricNamespace, String metricName,
Statistic statistic) {
Statistic statistic, List<MetricDimension> dimensions) {
this.name = name;
this.region = region;
this.actionsEnabled = actionsEnabled;
Expand All @@ -47,6 +50,7 @@ public AlarmConfiguration(String name, String region, Optional<Boolean> actionsE
this.metricNamespace = metricNamespace;
this.metricName = metricName;
this.statistic = statistic;
this.dimensions = dimensions;
}

@JsonProperty
Expand Down Expand Up @@ -99,6 +103,11 @@ public Statistic getStatistic() {
return statistic;
}

@JsonProperty
public List<MetricDimension> getDimensions() {
return dimensions;
}

@Override
public String toString() {
return "AlarmConfiguration{" +
Expand All @@ -111,7 +120,8 @@ public String toString() {
", threshold=" + threshold +
", metricNamespace='" + metricNamespace + '\'' +
", metricName='" + metricName + '\'' +
", statistic=" + statistic +
", statistic=" + statistic + '\n' +
", dimensions=" + dimensions +
'}';
}

Expand All @@ -131,6 +141,7 @@ public static class Builder {
private String metricNamespace;
private String metricName;
private Statistic statistic;
private List<MetricDimension> dimensions = Collections.emptyList();

private Builder() {

Expand Down Expand Up @@ -191,9 +202,14 @@ public Builder withStatistic(Statistic staticstic) {
return this;
}

public Builder withDimensions(List<MetricDimension> dimensions) {
this.dimensions = dimensions;
return this;
}

public AlarmConfiguration build() {
return new AlarmConfiguration(name, region, actionsEnabled, comparisonOperator,
evaluationPeriods, periodSec, threshold, metricNamespace, metricName, statistic);
evaluationPeriods, periodSec, threshold, metricNamespace, metricName, statistic, dimensions);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
package com.netflix.titus.api.appscale.store.mixin;


import java.util.List;
import java.util.Optional;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.netflix.titus.api.appscale.model.ComparisonOperator;
import com.netflix.titus.api.appscale.model.MetricDimension;
import com.netflix.titus.api.appscale.model.Statistic;

@JsonIgnoreProperties(ignoreUnknown = true)
Expand All @@ -38,7 +40,8 @@ public abstract class AlarmConfigurationMixIn {
@JsonProperty("threshold") double threshold,
@JsonProperty("metricNamespace") String metricNamespace,
@JsonProperty("metricName") String metricName,
@JsonProperty("statistic") Statistic statistic) {
@JsonProperty("statistic") Statistic statistic,
@JsonProperty("dimensions") List<MetricDimension> dimensions ) {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.netflix.titus.api.appscale.model;

import com.netflix.titus.api.json.ObjectMappers;
import org.junit.Test;

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

public class PolicyConfigurationTest {
@Test
public void deserializePolicyConfiguration() {
String policyConfigStrNoMetricDimensions = "{\n" +
" \"name\": null,\n" +
" \"policyType\": \"StepScaling\",\n" +
" \"stepScalingPolicyConfiguration\": {\n" +
" \"metricAggregationType\": \"Maximum\",\n" +
" \"adjustmentType\": \"ChangeInCapacity\",\n" +
" \"minAdjustmentMagnitude\": null,\n" +
" \"steps\": [\n" +
" {\n" +
" \"scalingAdjustment\": 1,\n" +
" \"metricIntervalLowerBound\": 0.0,\n" +
" \"metricIntervalUpperBound\": null\n" +
" }\n" +
" ],\n" +
" \"coolDownSec\": 60\n" +
" },\n" +
" \"alarmConfiguration\": {\n" +
" \"name\": null,\n" +
" \"region\": null,\n" +
" \"actionsEnabled\": true,\n" +
" \"comparisonOperator\": \"GreaterThanThreshold\",\n" +
" \"evaluationPeriods\": 1,\n" +
" \"threshold\": 2.0,\n" +
" \"metricNamespace\": \"titus/integrationTest\",\n" +
" \"metricName\": \"RequestCount\",\n" +
" \"statistic\": \"Sum\",\n" +
" \"periodSec\": 60\n" +
" },\n" +
" \"targetTrackingPolicy\": null\n" +
"}";

PolicyConfiguration policyConfigNoMetricDimension = ObjectMappers.readValue(ObjectMappers.appScalePolicyMapper(),
policyConfigStrNoMetricDimensions, PolicyConfiguration.class);
assertThat(policyConfigNoMetricDimension).isNotNull();
assertThat(policyConfigNoMetricDimension.getAlarmConfiguration().getDimensions()).isNull();

String policyConfigStrWithMetricDimensions = "{\n" +
" \"name\": null,\n" +
" \"policyType\": \"StepScaling\",\n" +
" \"stepScalingPolicyConfiguration\": {\n" +
" \"metricAggregationType\": \"Maximum\",\n" +
" \"adjustmentType\": \"ChangeInCapacity\",\n" +
" \"minAdjustmentMagnitude\": null,\n" +
" \"steps\": [\n" +
" {\n" +
" \"scalingAdjustment\": 1,\n" +
" \"metricIntervalLowerBound\": 0.0,\n" +
" \"metricIntervalUpperBound\": null\n" +
" }\n" +
" ],\n" +
" \"coolDownSec\": 60\n" +
" },\n" +
" \"alarmConfiguration\": {\n" +
" \"name\": null,\n" +
" \"region\": null,\n" +
" \"actionsEnabled\": true,\n" +
" \"comparisonOperator\": \"GreaterThanThreshold\",\n" +
" \"evaluationPeriods\": 1,\n" +
" \"threshold\": 2.0,\n" +
" \"metricNamespace\": \"titus/integrationTest\",\n" +
" \"metricName\": \"RequestCount\",\n" +
" \"statistic\": \"Sum\",\n" +
" \"periodSec\": 60,\n" +
" \"unknownField\": 100,\n" +
" \"dimensions\": [\n" +
" {\n" +
" \"Name\": \"foo\",\n" +
" \"Value\": \"bar\"\n" +
" },\n" +
" {\n" +
" \"Name\": \"tier\",\n" +
" \"Value\": \"1\"\n" +
" }\n" +
" ]\n" +
" },\n" +
" \"targetTrackingPolicy\": null\n" +
"}";

PolicyConfiguration policyConfigWithMetricDimensions = ObjectMappers.readValue(ObjectMappers.appScalePolicyMapper(),
policyConfigStrWithMetricDimensions, PolicyConfiguration.class);
assertThat(policyConfigWithMetricDimensions).isNotNull();
assertThat(policyConfigWithMetricDimensions.getAlarmConfiguration().getDimensions()).isNotNull();
assertThat(policyConfigWithMetricDimensions.getAlarmConfiguration().getDimensions().size()).isEqualTo(2);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.netflix.titus.ext.aws.cloudwatch;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
Expand All @@ -31,9 +32,11 @@
import com.amazonaws.services.cloudwatch.model.PutMetricAlarmRequest;
import com.amazonaws.services.cloudwatch.model.PutMetricAlarmResult;
import com.amazonaws.services.cloudwatch.model.ResourceNotFoundException;
import com.google.common.annotations.VisibleForTesting;
import com.netflix.spectator.api.Counter;
import com.netflix.spectator.api.Registry;
import com.netflix.titus.api.appscale.model.AlarmConfiguration;
import com.netflix.titus.api.appscale.model.MetricDimension;
import com.netflix.titus.api.appscale.service.AutoScalePolicyException;
import com.netflix.titus.api.connector.cloud.CloudAlarmClient;
import com.netflix.titus.ext.aws.AwsConfiguration;
Expand Down Expand Up @@ -84,10 +87,8 @@ public Observable<String> createOrUpdateAlarm(String policyRefId,
AlarmConfiguration alarmConfiguration,
String autoScalingGroup,
List<String> actions) {
Dimension dimension = new Dimension();
dimension.setName(AUTO_SCALING_GROUP_NAME);
dimension.setValue(autoScalingGroup);

List<Dimension> metricDimensions = buildMetricDimensions(alarmConfiguration, autoScalingGroup);
String cloudWatchName = buildCloudWatchName(policyRefId, jobId);

PutMetricAlarmRequest putMetricAlarmRequest = new PutMetricAlarmRequest();
Expand All @@ -96,7 +97,7 @@ public Observable<String> createOrUpdateAlarm(String policyRefId,
}
putMetricAlarmRequest.setAlarmActions(actions);
putMetricAlarmRequest.setAlarmName(cloudWatchName);
putMetricAlarmRequest.setDimensions(Arrays.asList(dimension));
putMetricAlarmRequest.setDimensions(metricDimensions);
putMetricAlarmRequest.setNamespace(alarmConfiguration.getMetricNamespace());
putMetricAlarmRequest.setComparisonOperator(alarmConfiguration.getComparisonOperator().name());
putMetricAlarmRequest.setStatistic(alarmConfiguration.getStatistic().name());
Expand Down Expand Up @@ -159,5 +160,22 @@ private String buildCloudWatchName(String policyRefId, String jobId) {
return String.format("%s/%s", jobId, policyRefId);
}


@VisibleForTesting
static List<Dimension> buildMetricDimensions(AlarmConfiguration alarmConfiguration, String autoScalingGroup) {
List<Dimension> metricDimensions = new ArrayList<>(1);
if (alarmConfiguration.getDimensions() != null && ! alarmConfiguration.getDimensions().isEmpty()) {
for (MetricDimension customMetricDimension : alarmConfiguration.getDimensions()) {
Dimension dimension = new Dimension();
dimension.setName(customMetricDimension.getName());
dimension.setValue(customMetricDimension.getValue());
metricDimensions.add(dimension);
}
} else {
Dimension dimension = new Dimension();
dimension.setName(AUTO_SCALING_GROUP_NAME);
dimension.setValue(autoScalingGroup);
metricDimensions.add(dimension);
}
return metricDimensions;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.netflix.titus.ext.aws.cloudwatch;

import java.util.Arrays;
import java.util.List;

import com.amazonaws.services.cloudwatch.model.Dimension;
import com.netflix.titus.api.appscale.model.AlarmConfiguration;
import com.netflix.titus.api.appscale.model.ComparisonOperator;
import com.netflix.titus.api.appscale.model.MetricDimension;
import com.netflix.titus.api.appscale.model.Statistic;
import org.junit.Test;

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

public class CloudWatchClientTest {

private AlarmConfiguration.Builder getAlarmConfigBuilder() {
return AlarmConfiguration.newBuilder()
.withName("alarm-config-1")
.withMetricName("metric-1")
.withMetricNamespace("standard")
.withStatistic(Statistic.Average)
.withActionsEnabled(true)
.withPeriodSec(60)
.withThreshold(5)
.withEvaluationPeriods(2)
.withComparisonOperator(ComparisonOperator.GreaterThanOrEqualToThreshold);
}

@Test
public void buildDefaultMetricDimensions() {
AlarmConfiguration alarmConfiguration = getAlarmConfigBuilder().build();
List<Dimension> dimensions = CloudWatchClient.buildMetricDimensions(alarmConfiguration, "foo-bar");
assertThat(dimensions).isNotNull();
assertThat(dimensions.size()).isEqualTo(1);
assertThat(dimensions.get(0).getName()).isEqualTo("AutoScalingGroupName");
assertThat(dimensions.get(0).getValue()).isEqualTo("foo-bar");
}

@Test
public void buildCustomMetricDimensions() {
MetricDimension md1 = MetricDimension.newBuilder().withName("foo").withValue("bar").build();
MetricDimension md2 = MetricDimension.newBuilder().withName("service-tier").withValue("1").build();
List<MetricDimension> customMetricDimensions = Arrays.asList(md1, md2);
AlarmConfiguration alarmConfiguration = getAlarmConfigBuilder().withDimensions(customMetricDimensions).build();
List<Dimension> dimensions = CloudWatchClient.buildMetricDimensions(alarmConfiguration, "foo-bar");
assertThat(dimensions).isNotNull();
assertThat(dimensions.size()).isEqualTo(2);
assertThat(dimensions.get(0).getName()).isEqualTo("foo");
assertThat(dimensions.get(0).getValue()).isEqualTo("bar");
assertThat(dimensions.get(1).getName()).isEqualTo("service-tier");
assertThat(dimensions.get(1).getValue()).isEqualTo("1");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.netflix.titus.api.service.TitusServiceException;
import com.netflix.titus.common.model.sanitizer.EntitySanitizer;
import com.netflix.titus.common.util.rx.ReactorExt;
import com.netflix.titus.grpc.protogen.AlarmConfiguration;
import com.netflix.titus.grpc.protogen.AutoScalingServiceGrpc;
import com.netflix.titus.grpc.protogen.GetPolicyResult;
import com.netflix.titus.grpc.protogen.PutPolicyRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ private static AlarmConfiguration toAlarmConfiguration(com.netflix.titus.grpc.pr
if (alarmConfigGrpc.getStatisticOneofCase() != com.netflix.titus.grpc.protogen.AlarmConfiguration.StatisticOneofCase.STATISTICONEOF_NOT_SET) {
alarmConfigBuilder.withStatistic(toStatistic(alarmConfigGrpc.getStatistic()));
}
alarmConfigBuilder.withDimensions(toMetricDimensionList(alarmConfigGrpc.getMetricDimensionsList()));

// TODO(Andrew L): Do we want to just always set empty string, if unset?
alarmConfigBuilder.withMetricNamespace(alarmConfigGrpc.getMetricNamespace());
Expand Down

0 comments on commit efcff9d

Please sign in to comment.