Skip to content

Commit

Permalink
fix(stackdriver): handle null timeSeries and empty points (backport #…
Browse files Browse the repository at this point in the history
…1047) (#1048)

* fix(stackdriver): handle null timeSeries and empty points (#1047)

(cherry picked from commit 6684f9d)

# Conflicts:
#	kayenta-stackdriver/src/main/java/com/netflix/kayenta/stackdriver/metrics/StackdriverMetricsService.java
#	kayenta-stackdriver/src/test/java/com/netflix/kayenta/stackdriver/metrics/StackdriverMetricsServiceTest.java

* fix(mergify): fix conflicts in cherry-pick

---------

Co-authored-by: Shlomo Daari <[email protected]>
Co-authored-by: Edgar Garcia <[email protected]>
  • Loading branch information
3 people authored Aug 8, 2024
1 parent d6f5f13 commit d341ae7
Show file tree
Hide file tree
Showing 2 changed files with 303 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -398,11 +398,37 @@ public List<MetricSet> queryMetrics(
? Instant.parse(points.get(points.size() - 1).getInterval().getEndTime())
: stackdriverCanaryScope.getEnd();

// TODO(duftler): What if there are no data points?
List<Double> pointValues =
points.stream()
.map(point -> point.getValue().getDoubleValue())
.collect(Collectors.toList());
List<Double> pointValues;

if (points.isEmpty()) {
log.warn("No data points available.");
pointValues = Collections.emptyList();
} else {
if (timeSeries.getValueType() != null) {
if (timeSeries.getValueType().equals("INT64")) {
pointValues =
points.stream()
.map(point -> (double) point.getValue().getInt64Value())
.collect(Collectors.toList());
} else if (timeSeries.getValueType().equals("DOUBLE")) {
pointValues =
points.stream()
.map(point -> point.getValue().getDoubleValue())
.collect(Collectors.toList());
} else {
log.warn(
"Expected timeSeries value type to be either DOUBLE or INT64. Got {}.",
timeSeries.getValueType());
pointValues =
points.stream()
.map(point -> point.getValue().getDoubleValue())
.collect(Collectors.toList());
}
} else {
log.warn("timeSeries valueType is null.");
pointValues = Collections.emptyList(); // Handle null valueType case as well
}
}

MetricSet.MetricSetBuilder metricSetBuilder =
MetricSet.builder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
package com.netflix.kayenta.stackdriver.metrics;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.google.api.services.monitoring.v3.Monitoring;
import com.google.api.services.monitoring.v3.model.ListMetricDescriptorsResponse;
import com.google.api.services.monitoring.v3.model.ListTimeSeriesResponse;
import com.google.api.services.monitoring.v3.model.MetricDescriptor;
import com.google.api.services.monitoring.v3.model.Point;
import com.google.api.services.monitoring.v3.model.TimeInterval;
import com.google.api.services.monitoring.v3.model.TimeSeries;
import com.google.api.services.monitoring.v3.model.TypedValue;
import com.netflix.kayenta.canary.CanaryConfig;
import com.netflix.kayenta.canary.CanaryMetricConfig;
import com.netflix.kayenta.canary.providers.metrics.StackdriverCanaryMetricSetQueryConfig;
import com.netflix.kayenta.google.security.GoogleNamedAccountCredentials;
import com.netflix.kayenta.metrics.MetricSet;
import com.netflix.kayenta.security.AccountCredentials;
import com.netflix.kayenta.security.AccountCredentialsRepository;
import com.netflix.kayenta.stackdriver.canary.StackdriverCanaryScope;
import com.netflix.spectator.api.DefaultRegistry;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

public class StackdriverMetricsServiceTest {

private static final String ACCOUNT = "test-account";

private AccountCredentialsRepository accountCredentialsRepoMock;

private StackdriverMetricsService stackdriverMetricsService;

@BeforeEach
void setup() {
StackdriverMetricsService.StackdriverMetricsServiceBuilder stackdriverMetricsServiceBuilder =
StackdriverMetricsService.builder();
accountCredentialsRepoMock = mock(AccountCredentialsRepository.class);
stackdriverMetricsServiceBuilder
.accountCredentialsRepository(accountCredentialsRepoMock)
.registry(new DefaultRegistry());
stackdriverMetricsService = stackdriverMetricsServiceBuilder.build();
}

@Test
void readsInt64Metrics() throws IOException {
GoogleNamedAccountCredentials stackdriverCredentialsMock =
mock(GoogleNamedAccountCredentials.class);
when(accountCredentialsRepoMock.getRequiredOne(ACCOUNT)).thenReturn(stackdriverCredentialsMock);

Monitoring monitoringMock = mock(Monitoring.class, Mockito.RETURNS_DEEP_STUBS);
when(stackdriverCredentialsMock.getMonitoring()).thenReturn(monitoringMock);

Monitoring.Projects.TimeSeries.List timeSeriesListMock =
mock(Monitoring.Projects.TimeSeries.List.class);

when(monitoringMock
.projects()
.timeSeries()
.list(anyString())
.setAggregationAlignmentPeriod(anyString())
.setAggregationCrossSeriesReducer(anyString())
.setAggregationPerSeriesAligner(anyString())
.setFilter(anyString())
.setIntervalStartTime(anyString())
.setIntervalEndTime(anyString()))
.thenReturn(timeSeriesListMock);

ListTimeSeriesResponse responseMock = mock(ListTimeSeriesResponse.class);
when(timeSeriesListMock.execute()).thenReturn(responseMock);

List<TimeSeries> timeSeriesListWithInt64Points = new ArrayList<TimeSeries>();

// Create a time series with INT64 points
List<Point> int64Points = new ArrayList<Point>();
int64Points.add(
new Point()
.setValue(new TypedValue().setInt64Value((Long) 64l))
.setInterval(
new TimeInterval()
.setStartTime("1970-01-01T00:00:00.00Z")
.setEndTime("1970-01-01T00:00:01.00Z")));
TimeSeries timeSeriesWithInt64Points =
new TimeSeries().setValueType("INT64").setPoints(int64Points);
timeSeriesListWithInt64Points.add(timeSeriesWithInt64Points);

when(responseMock.getTimeSeries()).thenReturn(timeSeriesListWithInt64Points);

CanaryConfig canaryConfig = new CanaryConfig();
CanaryMetricConfig canaryMetricConfig =
CanaryMetricConfig.builder()
.name("metricConfig")
.query(
StackdriverCanaryMetricSetQueryConfig.builder()
.resourceType("global")
.metricType("instance")
.build())
.build();

StackdriverCanaryScope canaryScope = new StackdriverCanaryScope();
canaryScope.setStart(Instant.EPOCH).setEnd(Instant.EPOCH.plusSeconds(1)).setStep(1l);
canaryScope.setProject("my-project");
List<MetricSet> queriedMetrics =
stackdriverMetricsService.queryMetrics(
ACCOUNT, canaryConfig, canaryMetricConfig, canaryScope);

assertThat(queriedMetrics.get(0).getValues()).contains(64d);
}

@Test
void returnsSingleMetricDescriptorInCache() throws IOException {
GoogleNamedAccountCredentials googleAccountCredentialsMock =
mock(GoogleNamedAccountCredentials.class, Mockito.RETURNS_DEEP_STUBS);

Set<AccountCredentials> accountCredentialsSetMock = new HashSet<>();
accountCredentialsSetMock.add(googleAccountCredentialsMock);

when(accountCredentialsRepoMock.getAllOf(AccountCredentials.Type.METRICS_STORE))
.thenReturn(accountCredentialsSetMock);

ListMetricDescriptorsResponse listMetricDescriptorsResponseMock =
mock(ListMetricDescriptorsResponse.class);
when(googleAccountCredentialsMock
.getMonitoring()
.projects()
.metricDescriptors()
.list(anyString())
.execute())
.thenReturn(listMetricDescriptorsResponseMock);

List<MetricDescriptor> metricDesciprtorMockList = new ArrayList<MetricDescriptor>();

MetricDescriptor exampleMetricDescriptor = new MetricDescriptor();
metricDesciprtorMockList.add(exampleMetricDescriptor);
when(listMetricDescriptorsResponseMock.getMetricDescriptors())
.thenReturn(metricDesciprtorMockList);

stackdriverMetricsService.updateMetricDescriptorsCache();

List<Map> metadata = stackdriverMetricsService.getMetadata(ACCOUNT, "");

assertThat(metadata).containsOnly(exampleMetricDescriptor);
}

@Test
void handlesEmptyResponse() throws IOException {
GoogleNamedAccountCredentials stackdriverCredentialsMock =
mock(GoogleNamedAccountCredentials.class);
when(accountCredentialsRepoMock.getRequiredOne(ACCOUNT)).thenReturn(stackdriverCredentialsMock);

Monitoring monitoringMock = mock(Monitoring.class, Mockito.RETURNS_DEEP_STUBS);
when(stackdriverCredentialsMock.getMonitoring()).thenReturn(monitoringMock);

Monitoring.Projects.TimeSeries.List timeSeriesListMock =
mock(Monitoring.Projects.TimeSeries.List.class);
when(monitoringMock
.projects()
.timeSeries()
.list(anyString())
.setAggregationAlignmentPeriod(anyString())
.setAggregationCrossSeriesReducer(anyString())
.setAggregationPerSeriesAligner(anyString())
.setFilter(anyString())
.setIntervalStartTime(anyString())
.setIntervalEndTime(anyString()))
.thenReturn(timeSeriesListMock);

ListTimeSeriesResponse responseMock = mock(ListTimeSeriesResponse.class);
when(timeSeriesListMock.execute()).thenReturn(responseMock);

// Return an empty list for time series
when(responseMock.getTimeSeries()).thenReturn(Collections.emptyList());

CanaryConfig canaryConfig = new CanaryConfig();
CanaryMetricConfig canaryMetricConfig =
CanaryMetricConfig.builder()
.name("metricConfig")
.query(
StackdriverCanaryMetricSetQueryConfig.builder()
.resourceType("global")
.metricType("instance")
.build())
.build();

StackdriverCanaryScope canaryScope = new StackdriverCanaryScope();
canaryScope.setStart(Instant.EPOCH).setEnd(Instant.EPOCH.plusSeconds(1)).setStep(1l);
canaryScope.setProject("my-project");
List<MetricSet> queriedMetrics =
stackdriverMetricsService.queryMetrics(
ACCOUNT, canaryConfig, canaryMetricConfig, canaryScope);

// Verify that an empty metric set is returned
assertThat(queriedMetrics).hasSize(1);
assertThat(queriedMetrics.get(0).getValues()).isEmpty();
}

@Test
void handlesInvalidMetricType() throws IOException {
GoogleNamedAccountCredentials stackdriverCredentialsMock =
mock(GoogleNamedAccountCredentials.class);
when(accountCredentialsRepoMock.getRequiredOne(ACCOUNT)).thenReturn(stackdriverCredentialsMock);

Monitoring monitoringMock = mock(Monitoring.class, Mockito.RETURNS_DEEP_STUBS);
when(stackdriverCredentialsMock.getMonitoring()).thenReturn(monitoringMock);

Monitoring.Projects.TimeSeries.List timeSeriesListMock =
mock(Monitoring.Projects.TimeSeries.List.class);
when(monitoringMock
.projects()
.timeSeries()
.list(anyString())
.setAggregationAlignmentPeriod(anyString())
.setAggregationCrossSeriesReducer(anyString())
.setAggregationPerSeriesAligner(anyString())
.setFilter(anyString())
.setIntervalStartTime(anyString())
.setIntervalEndTime(anyString()))
.thenReturn(timeSeriesListMock);

ListTimeSeriesResponse responseMock = mock(ListTimeSeriesResponse.class);
when(timeSeriesListMock.execute()).thenReturn(responseMock);

List<TimeSeries> timeSeriesListWithInvalidMetricType = new ArrayList<>();

// Create a time series with an invalid metric type
List<Point> points = new ArrayList<>();
points.add(
new Point()
.setValue(new TypedValue().setDoubleValue(3.14))
.setInterval(
new TimeInterval()
.setStartTime("1970-01-01T00:00:00.00Z")
.setEndTime("1970-01-01T00:00:01.00Z")));
TimeSeries timeSeriesWithInvalidMetricType =
new TimeSeries().setValueType("STRING").setPoints(points);
timeSeriesListWithInvalidMetricType.add(timeSeriesWithInvalidMetricType);

when(responseMock.getTimeSeries()).thenReturn(timeSeriesListWithInvalidMetricType);

CanaryConfig canaryConfig = new CanaryConfig();
CanaryMetricConfig canaryMetricConfig =
CanaryMetricConfig.builder()
.name("metricConfig")
.query(
StackdriverCanaryMetricSetQueryConfig.builder()
.resourceType("global")
.metricType("instance")
.build())
.build();

StackdriverCanaryScope canaryScope = new StackdriverCanaryScope();
canaryScope.setStart(Instant.EPOCH).setEnd(Instant.EPOCH.plusSeconds(1)).setStep(1l);
canaryScope.setProject("my-project");
List<MetricSet> queriedMetrics =
stackdriverMetricsService.queryMetrics(
ACCOUNT, canaryConfig, canaryMetricConfig, canaryScope);

// Verify that the values are extracted as Double
assertThat(queriedMetrics.get(0).getValues()).contains(3.14);
}
}

0 comments on commit d341ae7

Please sign in to comment.