/*
* Copyright (c) 2015 Spotify AB.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.spotify.heroic.test;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.spotify.heroic.HeroicConfig;
import com.spotify.heroic.HeroicCore;
import com.spotify.heroic.HeroicCoreInstance;
import com.spotify.heroic.common.DateRange;
import com.spotify.heroic.common.GroupMember;
import com.spotify.heroic.common.OptionalLimit;
import com.spotify.heroic.common.Series;
import com.spotify.heroic.dagger.LoadingComponent;
import com.spotify.heroic.filter.TrueFilter;
import com.spotify.heroic.suggest.KeySuggest;
import com.spotify.heroic.suggest.MatchOptions;
import com.spotify.heroic.suggest.SuggestBackend;
import com.spotify.heroic.suggest.SuggestManagerModule;
import com.spotify.heroic.suggest.SuggestModule;
import com.spotify.heroic.suggest.TagKeyCount;
import com.spotify.heroic.suggest.TagSuggest;
import com.spotify.heroic.suggest.TagValueSuggest;
import com.spotify.heroic.suggest.TagValuesSuggest;
import com.spotify.heroic.suggest.WriteSuggest;
import eu.toolchain.async.AsyncFramework;
import eu.toolchain.async.AsyncFuture;
import eu.toolchain.async.RetryPolicy;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static com.spotify.heroic.filter.Filter.matchKey;
import static org.junit.Assert.assertEquals;
@RunWith(MockitoJUnitRunner.class)
public abstract class AbstractSuggestBackendIT {
private final Series s1 = Series.of("aa1", ImmutableMap.of("role", "foo"));
private final Series s2 = Series.of("aa2", ImmutableMap.of("role", "bar"));
private final Series s3 = Series.of("bb3", ImmutableMap.of("role", "baz"));
protected final DateRange range = new DateRange(0L, 0L);
private final List<Pair<Series, DateRange>> testSeries =
new ArrayList<Pair<Series, DateRange>>() {
{
add(new ImmutablePair<>(s1, range));
add(new ImmutablePair<>(s2, range));
add(new ImmutablePair<>(s3, range));
}
};
private final TagValuesSuggest.Request tagValuesSuggestReq =
new TagValuesSuggest.Request(TrueFilter.get(), range, OptionalLimit.empty(),
OptionalLimit.empty(), ImmutableList.of());
private final TagValueSuggest.Request tagValueSuggestReq =
new TagValueSuggest.Request(TrueFilter.get(), range, OptionalLimit.empty(),
Optional.of("role"));
private final TagKeyCount.Request tagKeyCountReq =
new TagKeyCount.Request(TrueFilter.get(), range, OptionalLimit.empty(),
OptionalLimit.empty());
private final TagSuggest.Request tagSuggestReq =
new TagSuggest.Request(TrueFilter.get(), range, OptionalLimit.empty(),
MatchOptions.builder().build(), Optional.empty(), Optional.of("ba"));
private final KeySuggest.Request keySuggestReq =
new KeySuggest.Request(TrueFilter.get(), range, OptionalLimit.empty(),
MatchOptions.builder().build(), Optional.of("aa"));
private HeroicCoreInstance core;
protected AsyncFramework async;
protected SuggestBackend backend;
protected abstract SuggestModule setupModule() throws Exception;
@Before
public final void abstractSetup() throws Exception {
final HeroicConfig.Builder fragment = HeroicConfig
.builder()
.suggest(SuggestManagerModule.builder().backends(ImmutableList.of(setupModule())));
core = HeroicCore
.builder()
.setupService(false)
.setupShellServer(false)
.configFragment(fragment)
.build()
.newInstance();
core.start().get();
async = core.inject(LoadingComponent::async);
backend = core
.inject(c -> c
.suggestManager()
.groupSet()
.inspectAll()
.stream()
.map(GroupMember::getMember)
.findFirst())
.orElseThrow(() -> new IllegalStateException("Failed to find backend"));
}
@After
public final void abstractTeardown() throws Exception {
core.shutdown().get();
}
@Test
public void tagValuesSuggest() throws Exception {
writeSeries(backend, testSeries);
final TagValuesSuggest result = getTagValuesSuggest(tagValuesSuggestReq);
final TagValuesSuggest.Suggestion s = result.getSuggestions().get(0);
assertEquals(
new TagValuesSuggest.Suggestion("role", ImmutableSortedSet.of("bar", "baz", "foo"),
false), s);
}
@Test
public void tagValueSuggest() throws Exception {
writeSeries(backend, testSeries);
final TagValueSuggest result = getTagValueSuggest(tagValueSuggestReq);
assertEquals(ImmutableSet.of("bar", "baz", "foo"), ImmutableSet.copyOf(result.getValues()));
}
@Test
public void tagKeyCount() throws Exception {
writeSeries(backend, testSeries);
final TagKeyCount result = getTagKeyCount(tagKeyCountReq);
final TagKeyCount.Suggestion s = result.getSuggestions().get(0);
assertEquals("role", s.getKey());
assertEquals(3, s.getCount());
}
@Test
public void tagSuggest() throws Exception {
writeSeries(backend, testSeries);
final Set<Pair<String, String>> result = getTagSuggest(tagSuggestReq);
assertEquals(ImmutableSet.of(Pair.of("role", "bar"), Pair.of("role", "baz")), result);
}
@Test
public void keySuggest() throws Exception {
writeSeries(backend, testSeries);
final Set<String> result = getKeySuggest(keySuggestReq);
assertEquals(ImmutableSet.of(s1.getKey(), s2.getKey()), result);
}
@Test
public void tagValueSuggestNoIdx() throws Exception {
final TagValueSuggest result = getTagValueSuggest(tagValueSuggestReq);
assertEquals(Collections.emptyList(), result.getValues());
}
@Test
public void tagValuesSuggestNoIdx() throws Exception {
final TagValuesSuggest result = getTagValuesSuggest(tagValuesSuggestReq);
assertEquals(Collections.emptyList(), result.getSuggestions());
}
@Test
public void tagKeyCountNoIdx() throws Exception {
final TagKeyCount result = getTagKeyCount(tagKeyCountReq);
assertEquals(Collections.emptyList(), result.getSuggestions());
}
@Test
public void tagSuggestNoIdx() throws Exception {
final Set<Pair<String, String>> result = getTagSuggest(tagSuggestReq);
assertEquals(Collections.emptySet(), result);
}
@Test
public void keySuggestNoIdx() throws Exception {
final Set<String> result = getKeySuggest(keySuggestReq);
assertEquals(Collections.emptySet(), result);
}
private AsyncFuture<Void> writeSeries(
final SuggestBackend suggest, final Series s, final DateRange range
) throws Exception {
return suggest
.write(new WriteSuggest.Request(s, range))
.lazyTransform(r -> async.retryUntilResolved(() -> checks(s),
RetryPolicy.timed(10000, RetryPolicy.exponential(100, 200))))
.directTransform(retry -> null);
}
private AsyncFuture<Void> checks(final Series s) {
final List<AsyncFuture<Void>> checks = new ArrayList<>();
checks.add(backend
.tagSuggest(new TagSuggest.Request(matchKey(s.getKey()), range, OptionalLimit.empty(),
MatchOptions.builder().build(), Optional.empty(), Optional.empty()))
.directTransform(result -> {
if (result.getSuggestions().isEmpty()) {
throw new IllegalStateException("No suggestion available for the given series");
}
return null;
}));
checks.add(backend
.keySuggest(new KeySuggest.Request(matchKey(s.getKey()), range, OptionalLimit.empty(),
MatchOptions.builder().build(), Optional.empty()))
.directTransform(result -> {
if (result.getSuggestions().isEmpty()) {
throw new IllegalStateException("No suggestion available for the given series");
}
return null;
}));
return async.collectAndDiscard(checks);
}
private void writeSeries(final SuggestBackend backend, final List<Pair<Series, DateRange>> data)
throws Exception {
final List<AsyncFuture<Void>> writes = new ArrayList<>();
for (Pair<Series, DateRange> p : data) {
writes.add(writeSeries(backend, p.getKey(), p.getValue()));
}
async.collectAndDiscard(writes).get();
}
private TagValuesSuggest getTagValuesSuggest(final TagValuesSuggest.Request req)
throws ExecutionException, InterruptedException {
return backend.tagValuesSuggest(req).get();
}
private TagValueSuggest getTagValueSuggest(final TagValueSuggest.Request req)
throws ExecutionException, InterruptedException {
return backend.tagValueSuggest(req).get();
}
private TagKeyCount getTagKeyCount(final TagKeyCount.Request req)
throws ExecutionException, InterruptedException {
return backend.tagKeyCount(req).get();
}
private Set<Pair<String, String>> getTagSuggest(final TagSuggest.Request req)
throws ExecutionException, InterruptedException {
return backend
.tagSuggest(req)
.get()
.getSuggestions()
.stream()
.map(s -> Pair.of(s.getKey(), s.getValue()))
.collect(Collectors.toSet());
}
private Set<String> getKeySuggest(final KeySuggest.Request req)
throws ExecutionException, InterruptedException {
return backend
.keySuggest(req)
.get()
.getSuggestions()
.stream()
.map(s -> s.getKey())
.collect(Collectors.toSet());
}
}