/*
* Copyright © 2015-2016 Cask Data, 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 co.cask.cdap.data2.metadata.store;
import co.cask.cdap.common.conf.CConfiguration;
import co.cask.cdap.common.conf.Constants;
import co.cask.cdap.common.guice.LocationRuntimeModule;
import co.cask.cdap.common.namespace.guice.NamespaceClientRuntimeModule;
import co.cask.cdap.data.runtime.DataSetsModules;
import co.cask.cdap.data.runtime.SystemDatasetRuntimeModule;
import co.cask.cdap.data2.audit.AuditModule;
import co.cask.cdap.data2.audit.InMemoryAuditPublisher;
import co.cask.cdap.data2.audit.payload.builder.MetadataPayloadBuilder;
import co.cask.cdap.kafka.KafkaTester;
import co.cask.cdap.proto.Id;
import co.cask.cdap.proto.ProgramType;
import co.cask.cdap.proto.audit.AuditMessage;
import co.cask.cdap.proto.audit.AuditType;
import co.cask.cdap.proto.codec.NamespacedIdCodec;
import co.cask.cdap.proto.id.NamespaceId;
import co.cask.cdap.proto.id.NamespacedId;
import co.cask.cdap.proto.metadata.Metadata;
import co.cask.cdap.proto.metadata.MetadataChangeRecord;
import co.cask.cdap.proto.metadata.MetadataRecord;
import co.cask.cdap.proto.metadata.MetadataScope;
import co.cask.cdap.proto.metadata.MetadataSearchResultRecord;
import co.cask.tephra.TransactionManager;
import co.cask.tephra.runtime.TransactionInMemoryModule;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.util.Modules;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Tests for {@link MetadataStore}
*/
public class MetadataStoreTest {
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(Id.NamespacedId.class, new NamespacedIdCodec())
.create();
@ClassRule
public static final KafkaTester KAFKA_TESTER = new KafkaTester(
ImmutableMap.of(Constants.Metadata.UPDATES_PUBLISH_ENABLED, "true"),
ImmutableList.of(
Modules.override(
new DataSetsModules().getInMemoryModules()).with(new AbstractModule() {
@Override
protected void configure() {
// Need the distributed metadata store.
bind(MetadataStore.class).to(DefaultMetadataStore.class);
}
}),
new LocationRuntimeModule().getInMemoryModules(),
new TransactionInMemoryModule(),
new SystemDatasetRuntimeModule().getInMemoryModules(),
new NamespaceClientRuntimeModule().getInMemoryModules(),
new AuditModule().getInMemoryModules()
),
1,
Constants.Metadata.UPDATES_KAFKA_BROKER_LIST
);
private static final Type METADATA_CHANGE_RECORD_TYPE = new TypeToken<MetadataChangeRecord>() { }.getType();
private final Id.Application app = Id.Application.from(Id.Namespace.DEFAULT, "app");
private final Id.Program flow = Id.Program.from(app, ProgramType.FLOW, "flow");
private final Id.DatasetInstance dataset = Id.DatasetInstance.from(Id.Namespace.DEFAULT, "ds");
private final Id.Stream stream = Id.Stream.from(Id.Namespace.DEFAULT, "stream");
private final Set<String> datasetTags = ImmutableSet.of("dTag");
private final Map<String, String> appProperties = ImmutableMap.of("aKey", "aValue");
private final Set<String> appTags = ImmutableSet.of("aTag");
private final Map<String, String> streamProperties = ImmutableMap.of("stKey", "stValue");
private final Map<String, String> updatedStreamProperties = ImmutableMap.of("stKey", "stV");
private final Set<String> flowTags = ImmutableSet.of("fTag");
private final MetadataChangeRecord change1 = new MetadataChangeRecord(
new MetadataRecord(dataset, MetadataScope.USER),
new MetadataChangeRecord.MetadataDiffRecord(
new MetadataRecord(dataset, MetadataScope.USER, ImmutableMap.<String, String>of(), datasetTags),
new MetadataRecord(dataset, MetadataScope.USER)
),
System.currentTimeMillis()
);
private final MetadataChangeRecord change2 = new MetadataChangeRecord(
new MetadataRecord(app, MetadataScope.USER),
new MetadataChangeRecord.MetadataDiffRecord(
new MetadataRecord(app, MetadataScope.USER, appProperties, ImmutableSet.<String>of()),
new MetadataRecord(app, MetadataScope.USER)
),
System.currentTimeMillis()
);
private final MetadataChangeRecord change3 = new MetadataChangeRecord(
new MetadataRecord(app, MetadataScope.USER, appProperties, ImmutableSet.<String>of()),
new MetadataChangeRecord.MetadataDiffRecord(
new MetadataRecord(app, MetadataScope.USER, ImmutableMap.<String, String>of(), appTags),
new MetadataRecord(app, MetadataScope.USER)
),
System.currentTimeMillis()
);
private final MetadataChangeRecord change4 = new MetadataChangeRecord(
new MetadataRecord(stream, MetadataScope.USER),
new MetadataChangeRecord.MetadataDiffRecord(
new MetadataRecord(stream, MetadataScope.USER, streamProperties, ImmutableSet.<String>of()),
new MetadataRecord(stream, MetadataScope.USER)
),
System.currentTimeMillis()
);
private final MetadataChangeRecord change5 = new MetadataChangeRecord(
new MetadataRecord(stream, MetadataScope.USER, streamProperties, ImmutableSet.<String>of()),
new MetadataChangeRecord.MetadataDiffRecord(
new MetadataRecord(stream, MetadataScope.USER),
new MetadataRecord(stream, MetadataScope.USER)
),
System.currentTimeMillis()
);
private final MetadataChangeRecord change6 = new MetadataChangeRecord(
new MetadataRecord(stream, MetadataScope.USER, streamProperties, ImmutableSet.<String>of()),
new MetadataChangeRecord.MetadataDiffRecord(
new MetadataRecord(stream, MetadataScope.USER, updatedStreamProperties, ImmutableSet.<String>of()),
new MetadataRecord(stream, MetadataScope.USER, streamProperties, ImmutableSet.<String>of())
),
System.currentTimeMillis()
);
private final MetadataChangeRecord change7 = new MetadataChangeRecord(
new MetadataRecord(flow, MetadataScope.USER),
new MetadataChangeRecord.MetadataDiffRecord(
new MetadataRecord(flow, MetadataScope.USER, ImmutableMap.<String, String>of(), flowTags),
new MetadataRecord(flow, MetadataScope.USER)
),
System.currentTimeMillis()
);
private final MetadataChangeRecord change8 = new MetadataChangeRecord(
new MetadataRecord(flow, MetadataScope.USER, ImmutableMap.<String, String>of(), flowTags),
new MetadataChangeRecord.MetadataDiffRecord(
new MetadataRecord(flow, MetadataScope.USER),
new MetadataRecord(flow, MetadataScope.USER, ImmutableMap.<String, String>of(), flowTags)
),
System.currentTimeMillis()
);
private final MetadataChangeRecord change9 = new MetadataChangeRecord(
new MetadataRecord(dataset, MetadataScope.USER, ImmutableMap.<String, String>of(), datasetTags),
new MetadataChangeRecord.MetadataDiffRecord(
new MetadataRecord(dataset, MetadataScope.USER),
new MetadataRecord(dataset, MetadataScope.USER, ImmutableMap.<String, String>of(), datasetTags)
),
System.currentTimeMillis()
);
private final MetadataChangeRecord change10 = new MetadataChangeRecord(
new MetadataRecord(stream, MetadataScope.USER, updatedStreamProperties, ImmutableSet.<String>of()),
new MetadataChangeRecord.MetadataDiffRecord(
new MetadataRecord(stream, MetadataScope.USER),
new MetadataRecord(stream, MetadataScope.USER, updatedStreamProperties, ImmutableSet.<String>of())
),
System.currentTimeMillis()
);
private final MetadataChangeRecord change11 = new MetadataChangeRecord(
new MetadataRecord(app, MetadataScope.USER, appProperties, appTags),
new MetadataChangeRecord.MetadataDiffRecord(
new MetadataRecord(app, MetadataScope.USER, ImmutableMap.<String, String>of(), ImmutableSet.<String>of()),
new MetadataRecord(app, MetadataScope.USER, appProperties, appTags)
),
System.currentTimeMillis()
);
private final List<MetadataChangeRecord> expectedChanges = ImmutableList.of(
change1, change2, change3, change4, change5, change6, change7, change8, change9, change10, change11);
private int kafkaOffset = 0;
private static TransactionManager txManager;
private static MetadataStore store;
private static InMemoryAuditPublisher auditPublisher;
@BeforeClass
public static void setup() throws IOException {
Injector injector = KAFKA_TESTER.getInjector();
txManager = injector.getInstance(TransactionManager.class);
txManager.startAndWait();
store = injector.getInstance(MetadataStore.class);
auditPublisher = injector.getInstance(InMemoryAuditPublisher.class);
}
@Before
public void clearAudit() throws Exception {
auditPublisher.popMessages();
}
@Test
public void testPublishing() throws InterruptedException {
generateMetadataUpdates();
String topic = KAFKA_TESTER.getCConf().get(Constants.Metadata.UPDATES_KAFKA_TOPIC);
List<MetadataChangeRecord> publishedChanges =
KAFKA_TESTER.getPublishedMessages(topic, expectedChanges.size(), METADATA_CHANGE_RECORD_TYPE, GSON);
for (int i = 0; i < expectedChanges.size(); i++) {
MetadataChangeRecord expected = expectedChanges.get(i);
MetadataChangeRecord actual = publishedChanges.get(i);
Assert.assertEquals(expected.getPrevious(), actual.getPrevious());
Assert.assertEquals(expected.getChanges(), actual.getChanges());
}
// note kafka offset
kafkaOffset += publishedChanges.size();
// Verify audit publishing for the metadata changes
Function<MetadataChangeRecord, AuditMessage> metadataChangeRecordToAuditMessage =
new Function<MetadataChangeRecord, AuditMessage>() {
@Override
public AuditMessage apply(MetadataChangeRecord input) {
MetadataPayloadBuilder builder = new MetadataPayloadBuilder();
builder.addPrevious(input.getPrevious());
builder.addAdditions(input.getChanges().getAdditions());
builder.addDeletions(input.getChanges().getDeletions());
return new AuditMessage(0, input.getPrevious().getEntityId().toEntityId(), "",
AuditType.METADATA_CHANGE, builder.build());
}
};
// Audit messages for metadata changes
List<AuditMessage> expectedAuditMessages = Lists.transform(expectedChanges, metadataChangeRecordToAuditMessage);
List<AuditMessage> actualAuditMessages = new ArrayList<>();
for (AuditMessage auditMessage : auditPublisher.popMessages()) {
// Ignore system audit messages
if (auditMessage.getEntityId() instanceof NamespacedId) {
String systemNs = NamespaceId.SYSTEM.getNamespace();
if (!((NamespacedId) auditMessage.getEntityId()).getNamespace().equals(systemNs)) {
actualAuditMessages.add(auditMessage);
}
}
}
Assert.assertEquals(expectedAuditMessages, actualAuditMessages);
}
@Test
public void testPublishingDisabled() throws InterruptedException {
CConfiguration cConf = KAFKA_TESTER.getCConf();
boolean publishEnabled = cConf.getBoolean(Constants.Metadata.UPDATES_PUBLISH_ENABLED);
cConf.setBoolean(Constants.Metadata.UPDATES_PUBLISH_ENABLED, false);
generateMetadataUpdates();
String topic = cConf.get(Constants.Metadata.UPDATES_KAFKA_TOPIC);
try {
List<MetadataChangeRecord> publishedChanges =
KAFKA_TESTER.getPublishedMessages(topic, expectedChanges.size(), METADATA_CHANGE_RECORD_TYPE,
GSON, kafkaOffset);
Assert.fail(String.format("Expected no changes to be published, but found %d changes: %s.",
publishedChanges.size(), publishedChanges));
} catch (AssertionError e) {
// expected
}
// reset config
cConf.setBoolean(Constants.Metadata.UPDATES_PUBLISH_ENABLED, publishEnabled);
}
@Test
public void testSearchWeight() throws Exception {
Id.Program flow1 = Id.Program.from("ns1", "app1", ProgramType.FLOW, "flow1");
Id.Stream stream1 = Id.Stream.from("ns1", "s1");
Id.DatasetInstance dataset1 = Id.DatasetInstance.from("ns1", "ds1");
// Add metadata
String multiWordValue = "aV1 av2 , - , av3 - av4_av5 av6";
Map<String, String> flowUserProps = ImmutableMap.of("key1", "value1",
"key2", "value2",
"multiword", multiWordValue);
Map<String, String> flowSysProps = ImmutableMap.of("sysKey1", "sysValue1");
Set<String> flowUserTags = ImmutableSet.of("tag1", "tag2");
Set<String> flowSysTags = ImmutableSet.of("sysTag1");
store.setProperties(MetadataScope.USER, flow1, flowUserProps);
store.setProperties(MetadataScope.SYSTEM, flow1, flowSysProps);
store.addTags(MetadataScope.USER, flow1, flowUserTags.toArray(new String[flowUserTags.size()]));
store.addTags(MetadataScope.SYSTEM, flow1, flowSysTags.toArray(new String[flowSysTags.size()]));
Map<String, String> streamUserProps = ImmutableMap.of("sKey1", "sValue1 sValue2",
"Key1", "Value1");
store.setProperties(MetadataScope.USER, stream1, streamUserProps);
Map<String, String> datasetUserProps = ImmutableMap.of("sKey1", "sValuee1 sValuee2");
store.setProperties(MetadataScope.USER, dataset1, datasetUserProps);
// Test score and metadata match
List<MetadataSearchResultRecord> actual = Lists.newArrayList(store.searchMetadata("ns1", "value1 multiword:av2"));
Map<MetadataScope, Metadata> expectedFlowMetadata =
ImmutableMap.of(MetadataScope.USER, new Metadata(flowUserProps, flowUserTags),
MetadataScope.SYSTEM, new Metadata(flowSysProps, flowSysTags));
Map<MetadataScope, Metadata> expectedStreamMetadata =
ImmutableMap.of(MetadataScope.USER, new Metadata(streamUserProps, Collections.<String>emptySet()));
Map<MetadataScope, Metadata> expectedDatasetMetadata =
ImmutableMap.of(MetadataScope.USER, new Metadata(datasetUserProps, Collections.<String>emptySet()));
List<MetadataSearchResultRecord> expected =
Lists.newArrayList(
new MetadataSearchResultRecord(flow1,
expectedFlowMetadata),
new MetadataSearchResultRecord(stream1,
expectedStreamMetadata)
);
Assert.assertEquals(expected, actual);
actual = Lists.newArrayList(store.searchMetadata("ns1", "value1 sValue*"));
expected = Lists.newArrayList(
new MetadataSearchResultRecord(stream1,
expectedStreamMetadata),
new MetadataSearchResultRecord(dataset1,
expectedDatasetMetadata),
new MetadataSearchResultRecord(flow1,
expectedFlowMetadata)
);
Assert.assertEquals(expected, actual);
}
@AfterClass
public static void teardown() {
txManager.stopAndWait();
}
private void generateMetadataUpdates() {
store.addTags(MetadataScope.USER, dataset, datasetTags.iterator().next());
store.setProperties(MetadataScope.USER, app, appProperties);
store.addTags(MetadataScope.USER, app, appTags.iterator().next());
store.setProperties(MetadataScope.USER, stream, streamProperties);
store.setProperties(MetadataScope.USER, stream, streamProperties);
store.setProperties(MetadataScope.USER, stream, updatedStreamProperties);
store.addTags(MetadataScope.USER, flow, flowTags.iterator().next());
store.removeTags(MetadataScope.USER, flow);
store.removeTags(MetadataScope.USER, dataset, datasetTags.iterator().next());
store.removeProperties(MetadataScope.USER, stream);
store.removeMetadata(MetadataScope.USER, app);
}
}