/* * Copyright 2013-2015 the original author or authors. * * 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 org.springframework.cloud.netflix.metrics.atlas; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.junit.Test; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; import org.springframework.web.client.RestTemplate; import com.netflix.servo.Metric; import com.netflix.servo.annotations.DataSourceType; import com.netflix.servo.monitor.MonitorConfig; import com.netflix.servo.tag.BasicTagList; import com.netflix.servo.tag.Tag; import static com.netflix.servo.annotations.DataSourceType.COUNTER; import static com.netflix.servo.annotations.DataSourceType.GAUGE; import static com.netflix.servo.annotations.DataSourceType.INFORMATIONAL; import static com.netflix.servo.annotations.DataSourceType.KEY; import static com.netflix.servo.annotations.DataSourceType.NORMALIZED; import static com.netflix.servo.annotations.DataSourceType.RATE; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; /** * @author Jon Schneider */ public class AtlasMetricObserverTests { @Test public void normalizeAtlasUri() { String normalized = "http://localhost:7001/api/v1/publish"; assertThat(AtlasMetricObserver.normalizeAtlasUri("http://localhost:7001"), is(equalTo(normalized))); assertThat(AtlasMetricObserver.normalizeAtlasUri("http://localhost:7001/"), is(equalTo(normalized))); assertThat(AtlasMetricObserver.normalizeAtlasUri("http://localhost:7001/api/v1/publish"), is(equalTo(normalized))); assertThat(AtlasMetricObserver.normalizeAtlasUri("http://localhost:7001/api/v1/publish/"), is(equalTo(normalized))); } @Test(expected = IllegalStateException.class) public void emptyAtlasUriThrowsException() { AtlasMetricObserver.normalizeAtlasUri(""); } @Test(expected = IllegalStateException.class) public void missingAtlasUriThrowsException() { AtlasMetricObserver.normalizeAtlasUri(null); } @Test public void checkValidityOfTags() { assertTrue(AtlasMetricObserver.validTags(BasicTagList.of("foo", "bar"))); assertFalse(AtlasMetricObserver.validTags(BasicTagList.of("{foo}", "bar"))); assertFalse(AtlasMetricObserver.validTags(BasicTagList.of("foo", "{bar}"))); } @Test public void assignTypesToMetrics() { assertHasAtlasType("counter", metricWithType("foo", COUNTER)); assertHasAtlasType("gauge", metricWithType("foo", GAUGE)); assertHasAtlasType("gauge", metricWithType("foo", NORMALIZED)); assertHasAtlasType("gauge", metricWithType("foo", RATE)); assertHasAtlasType("rate", metricWithType("foo", INFORMATIONAL)); assertHasAtlasType("rate", new Metric(new MonitorConfig.Builder("foo").build(), System.currentTimeMillis(), "bar")); // already has type Metric m = new Metric(new MonitorConfig.Builder("foo") .withTag(KEY, COUNTER.name()).withTag("atlas.dstype", "counter").build(), System.currentTimeMillis(), "bar"); assertHasAtlasType("counter", m); assertThat(m.getConfig().getTags().size(), is(equalTo(2))); } private void assertHasAtlasType(String atlasType, Metric m) { assertThat(AtlasMetricObserver.addTypeTagsAsNecessary(Collections.singletonList(m)) .get(0).getConfig().getTags().getValue("atlas.dstype"), is(equalTo(atlasType))); } private Metric metricWithType(String key, DataSourceType type) { return new Metric(new MonitorConfig.Builder(key).withTag(KEY, type.name()) .build(), System.currentTimeMillis(), 1); } @Test public void metricsSentInBatches() { RestTemplate restTemplate = new RestTemplate(); AtlasMetricObserverConfigBean config = new AtlasMetricObserverConfigBean(); config.setBatchSize(2); config.setUri("atlas"); AtlasMetricObserver obs = new AtlasMetricObserver(config, restTemplate, BasicTagList.EMPTY); // batch size is divisible by metric size MockRestServiceServer mockServer = MockRestServiceServer .createServer(restTemplate); expectTotalBatches(mockServer, 2); obs.update(generateMetrics(4)); mockServer.verify(); // batch size is not divisible by metric size mockServer = MockRestServiceServer.createServer(restTemplate); expectTotalBatches(mockServer, 3); obs.update(generateMetrics(5)); mockServer.verify(); // metric size is less than batch size mockServer = MockRestServiceServer.createServer(restTemplate); expectTotalBatches(mockServer, 1); obs.update(generateMetrics(1)); mockServer.verify(); // no metrics to send mockServer = MockRestServiceServer.createServer(restTemplate); expectTotalBatches(mockServer, 0); obs.update(Collections.<Metric> emptyList()); mockServer.verify(); // a single non-numeric metric does not result in a post mockServer = MockRestServiceServer.createServer(restTemplate); expectTotalBatches(mockServer, 0); obs.update(Collections.singletonList(new Metric(new MonitorConfig.Builder("foo") .build(), 0, "nonumber"))); mockServer.verify(); } /** * If ALL of the metrics in a batch fail, Atlas will return a 400 with a String body indicating why. */ @Test public void failingMetricsBatch() { RestTemplate restTemplate = new RestTemplate(); AtlasMetricObserverConfigBean config = new AtlasMetricObserverConfigBean(); config.setBatchSize(1); config.setUri("atlas"); MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate); mockServer .expect(MockRestRequestMatchers.requestTo("atlas/api/v1/publish")) .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) .andRespond(MockRestResponseCreators.withBadRequest().body("foo0 is bad for some reason")); AtlasMetricObserver obs = new AtlasMetricObserver(config, restTemplate, BasicTagList.EMPTY); assertThat(obs.sendMetricsBatch(generateMetrics(1)), is(equalTo(AtlasMetricObserver.PublishMetricsBatchStatus.Failure))); } /** * If SOME metrics in a batch fail, Atlas will return a 202 with a JSON body with a message for each * failing metric. */ @Test public void partialSuccessMetricsBatch() { RestTemplate restTemplate = new RestTemplate(); AtlasMetricObserverConfigBean config = new AtlasMetricObserverConfigBean(); config.setBatchSize(2); config.setUri("atlas"); MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate); mockServer .expect(MockRestRequestMatchers.requestTo("atlas/api/v1/publish")) .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) .andRespond( MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) .body("{\"message\" : [\"foo1 is bad for some reason\"]}") .contentType(MediaType.APPLICATION_JSON)); AtlasMetricObserver obs = new AtlasMetricObserver(config, restTemplate, BasicTagList.EMPTY); assertThat(obs.sendMetricsBatch(generateMetrics(2)), is(equalTo(AtlasMetricObserver.PublishMetricsBatchStatus.PartialSuccess))); } @Test public void sanitizeMetrics() { String mixtureOfValidAndInvalidChars = "a_1.2-Z/ A"; Metric m = new Metric(new MonitorConfig.Builder(mixtureOfValidAndInvalidChars) .withTag(mixtureOfValidAndInvalidChars, mixtureOfValidAndInvalidChars).build(), 0, 1); Metric sanitizedMetric = AtlasMetricObserver.sanitizeTags(Collections.singletonList(m)).get(0); String valid = "a_1.2-Z__A"; assertThat(sanitizedMetric.getConfig().getName(), is(equalTo(valid))); Tag tag = sanitizedMetric.getConfig().getTags().iterator().next(); assertThat(tag.getKey(), is(equalTo(valid))); assertThat(tag.getValue(), is(equalTo(valid))); } private List<Metric> generateMetrics(int numberOfMetrics) { List<Metric> metrics = new ArrayList<>(); for (int i = 0; i < numberOfMetrics; i++) metrics.add(metricWithType("foo" + i, DataSourceType.GAUGE)); return metrics; } private void expectTotalBatches(MockRestServiceServer mockServer, int totalBatchesExpected) { for (int i = 0; i < totalBatchesExpected; i++) { mockServer .expect(MockRestRequestMatchers.requestTo("atlas/api/v1/publish")) .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) .andRespond( MockRestResponseCreators.withSuccess("{\"status\" : \"OK\"}", MediaType.APPLICATION_JSON)); } } }