/** * (c) Copyright 2013 WibiData, Inc. * * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * 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.kiji.mapreduce.kvstore.lib; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.SerializationException; import org.apache.hadoop.conf.Configuration; import org.junit.Before; import org.junit.Test; import org.kiji.mapreduce.KijiMRTestLayouts; import org.kiji.mapreduce.KijiMapReduceJob; import org.kiji.mapreduce.kvstore.KeyValueStore; import org.kiji.mapreduce.kvstore.KeyValueStoreReader; import org.kiji.mapreduce.kvstore.RequiredStores; import org.kiji.mapreduce.kvstore.framework.KeyValueStoreConfiguration; import org.kiji.mapreduce.output.MapReduceJobOutputs; import org.kiji.mapreduce.produce.KijiProduceJobBuilder; import org.kiji.mapreduce.produce.KijiProducer; import org.kiji.mapreduce.produce.ProducerContext; import org.kiji.schema.KijiClientTest; import org.kiji.schema.KijiDataRequest; import org.kiji.schema.KijiRowData; import org.kiji.schema.KijiTable; import org.kiji.schema.KijiTableReader; import org.kiji.schema.KijiURI; import org.kiji.schema.layout.KijiTableLayout; import org.kiji.schema.util.InstanceBuilder; public class TestInMemoryMapKeyValueStore extends KijiClientTest { /** Number of entries in our large KV Store. */ private static final int LARGE_KV_SIZE = 1024; /** * Producer designed to test kvstores. It doesn't actually read any data from the table. */ public static class TestingProducer extends KijiProducer { @Override public Map<String, KeyValueStore<?, ?>> getRequiredStores() { // We'll use UnconfiguredKeyValueStores, the real ones being // set by the job builder. return RequiredStores.with("small", UnconfiguredKeyValueStore.get()) .with("large", UnconfiguredKeyValueStore.get()); } @Override public KijiDataRequest getDataRequest() { // We won't actually use this so it's moot. return KijiDataRequest.create("info"); } @Override public String getOutputColumn() { return "info:first_name"; } @Override public void produce(KijiRowData input, ProducerContext context) throws IOException { // Ignore the input. Just retrieve our kv stores and confirm their contents. KeyValueStoreReader<String, String> smallStore = context.getStore("small"); assertEquals("Small store contains incorrect.", "ipsum", smallStore.get("lorem")); KeyValueStoreReader<String, String> largeStore = context.getStore("large"); for (int i = 0; i < LARGE_KV_SIZE; i++) { assertEquals("Large store contains incorrect value.", Integer.toString(i), largeStore.get(Integer.toString(i))); } smallStore.close(); largeStore.close(); // Write some data back to the table so we can be sure the producer ran. context.put("lorem"); } } @Before public final void setupTestProducer() throws Exception { // Get the test table layouts. final KijiTableLayout layout = KijiTableLayout.newLayout(KijiMRTestLayouts.getTestLayout()); // Populate the environment. A small table with one row. new InstanceBuilder(getKiji()) .withTable("test", layout) .withRow("Marsellus Wallace") .withFamily("info") .withQualifier("first_name").withValue("Marsellus") .withQualifier("last_name").withValue("Wallace") .withQualifier("zip_code").withValue(94110) .build(); } /** * A test that makes sure a producer can use this kvstore when it's * set-up by a job builder. */ @Test public void testProducer() throws Exception { // Create some maps for KV stores for this test. // A small map whose value will be checked. final Map<String, String> smallMap = new HashMap<String, String>(); smallMap.put("lorem", "ipsum"); // A larger map to ensure that we can safely encode non-trivial amounts of data. final Map<String, String> largeMap = new HashMap<String, String>(LARGE_KV_SIZE); for (int i = 0; i < LARGE_KV_SIZE; i++) { largeMap.put(Integer.toString(i), Integer.toString(i)); } final KijiURI tableURI = KijiURI.newBuilder(getKiji().getURI()).withTableName("test").build(); final KijiMapReduceJob job = KijiProduceJobBuilder.create() .withConf(getConf()) .withProducer(TestingProducer.class) .withInputTable(tableURI) .withOutput(MapReduceJobOutputs.newDirectKijiTableMapReduceJobOutput(tableURI)) .withStore("small", InMemoryMapKeyValueStore.fromMap(smallMap)) .withStore("large", InMemoryMapKeyValueStore.fromMap(largeMap)) .build(); // Be sure the job runs successfully and to completion. // If the producer finishes, the first_name of the main row will be changed to "lorem". assertTrue(job.run()); final KijiTable table = getKiji().openTable("test"); final KijiTableReader reader = table.openTableReader(); String value = reader .get(table.getEntityId("Marsellus Wallace"), KijiDataRequest.create("info", "first_name")) .getMostRecentValue("info", "first_name").toString(); assertEquals("Expected producer output not present. Did producer run successfully?", "lorem", value); reader.close(); table.release(); } @Test public void testNonSerializableTypes() throws Exception { /** A simple class which is not serializable. */ final class NotSerialized { } final Map<String, NotSerialized> map = new HashMap<String, NotSerialized>(); map.put("lorem", new NotSerialized()); final InMemoryMapKeyValueStore<String, NotSerialized> kvStore = InMemoryMapKeyValueStore.fromMap(map); final Configuration conf = new Configuration(false); final KeyValueStoreConfiguration kvConf = KeyValueStoreConfiguration.fromConf(conf); try { kvStore.storeToConf(kvConf); fail("Should have thrown a SerializationException"); } catch (SerializationException se) { assertEquals( "InMemoryKeyValueStore requires that its keys and values are Serializable", se.getMessage()); } } @Test public void simpleKVStoreTest() throws Exception { // Make a store and serialize it. final Map<String, Integer> map = new HashMap<String, Integer>(); map.put("one", 1); final InMemoryMapKeyValueStore<String, Integer> kvStore = InMemoryMapKeyValueStore.fromMap(map); final Configuration conf = new Configuration(false); final KeyValueStoreConfiguration kvConf = KeyValueStoreConfiguration.fromConf(conf); kvStore.storeToConf(kvConf); // Deserialize the store and read the value back. final InMemoryMapKeyValueStore<String, Integer> outKvStore = new InMemoryMapKeyValueStore<String, Integer>(); outKvStore.initFromConf(kvConf); final KeyValueStoreReader<String, Integer> reader = outKvStore.open(); assertEquals("Couldn't deserialize correct value!", new Integer(1), reader.get("one")); } }