package voldemort.client; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import org.apache.log4j.Logger; import voldemort.TestUtils; import voldemort.cluster.Zone; import voldemort.versioning.ObsoleteVersionException; import voldemort.versioning.Versioned; public class ClientTrafficVerifier implements Runnable { static Logger logger = Logger.getLogger(ClientTrafficVerifier.class); class PrintableHashMap extends HashMap<String, Integer> { @Override public String toString() { StringBuilder builder = new StringBuilder(); for(Object key: this.keySet()) { builder.append(key.toString() + ": " + this.get(key) + "; "); } return builder.toString(); } }; public static int MODE_ALLOW_GET = 0x01; public static int MODE_ALLOW_GETALL = 0x02; public static int MODE_ALLOW_PUT = 0x04; public static int MODE_ALLOW_ALL = MODE_ALLOW_GET | MODE_ALLOW_GETALL | MODE_ALLOW_PUT; int operationMode = MODE_ALLOW_ALL; static final Integer KV_POOL_SIZE = 100; public final StoreClient<String, String> client; final StoreClientFactory factory; boolean shouldStop = false; private boolean stopped = true; // not thread safe by multiple readers Thread thread; public final String clientName; List<String> keys = new ArrayList<String>(KV_POOL_SIZE); Map<String, String> kvMap = new HashMap<String, String>(KV_POOL_SIZE); Map<String, Integer> kvUpdateCount = new HashMap<String, Integer>(KV_POOL_SIZE); public final Map<String, Integer> exceptionCount = new PrintableHashMap(); public final Map<String, Integer> requestCount = new PrintableHashMap(); public ClientTrafficVerifier(String clientName, String bootstrapURL, String storeName, Integer clientZoneId) { ClientConfig config = new ClientConfig().setBootstrapUrls(bootstrapURL); if(clientZoneId != Zone.UNSET_ZONE_ID) { config.setClientZoneId(clientZoneId); } factory = new SocketStoreClientFactory(config); client = factory.getStoreClient(storeName); this.clientName = clientName; int i = 0; while(i < KV_POOL_SIZE) { String k = TestUtils.randomString("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 40); String v = TestUtils.randomString("abcdefghijklmnopqrstuvwxyz", 40); if(kvMap.containsKey(k)) { // prevent duplicate keys continue; } k = clientName + "_" + k; kvMap.put(k, v); kvUpdateCount.put(k, 0); keys.add(k); i++; } requestCount.put("GET", 0); requestCount.put("PUT", 0); requestCount.put("GETALL", 0); } public ClientTrafficVerifier setOperationMode(int mode) { operationMode = mode; return this; } boolean isInitialized = false; public void initialize() { if(isInitialized) return; for(String k: kvMap.keySet()) { client.put(k, kvMap.get(k) + "_" + kvUpdateCount.get(k).toString()); } isInitialized = true; } @SuppressWarnings("serial") @Override public void run() { Random r = new Random(System.currentTimeMillis()); while(!shouldStop) { String k = keys.get(r.nextInt(KV_POOL_SIZE)); try { switch(r.nextInt(3)) { case 0: // update if((operationMode & MODE_ALLOW_PUT) == 0) { break; } int newCount = kvUpdateCount.get(k) + 1; client.put(k, kvMap.get(k) + "_" + newCount); kvUpdateCount.put(k, newCount); requestCount.put("PUT", requestCount.get("PUT") + 1); break; case 1: // get if((operationMode & MODE_ALLOW_GET) == 0) { break; } Versioned<String> value = client.get(k); verifyValue(k, value); requestCount.put("GET", requestCount.get("GET") + 1); break; case 2: // get all if((operationMode & MODE_ALLOW_GETALL) == 0) { break; } String k2 = keys.get(r.nextInt(KV_POOL_SIZE)); Map<String, Versioned<String>> result = client.getAll(Arrays.asList(k, k2)); verifyValue(k, result.get(k)); verifyValue(k2, result.get(k2)); requestCount.put("GETALL", requestCount.get("GETALL") + 1); break; } } catch(ObsoleteVersionException e) { // Theoretically, each thread works with its own set of keys the // ObsoleteVersionException should not happen. But partitions // are moving around nodes and because of the way we // acknowledge writes before all nodes are complete and using // async writes they can be out of sync and the exceptions can // still happen. Did not try digging deeper on this one // as it is irrelevant for the refactoring I am doing. } catch(Exception e) { logger.info("CLIENT EXCEPTION FAILURE on key [" + k + "]", e); String exceptionName = "Key " + k + " " + e.getClass().toString(); if(exceptionCount.containsKey(exceptionName)) { exceptionCount.put(exceptionName, exceptionCount.get(exceptionName) + 1); } else { exceptionCount.put(exceptionName, 1); } } } } public void start() { this.thread = new Thread(this); this.thread.start(); this.stopped = false; } public void stop() { try { this.shouldStop = true; this.thread.join(); this.stopped = true; } catch(InterruptedException e) { e.printStackTrace(); } try { this.factory.close(); } catch(Exception e) { } } public boolean isStopped() { return this.stopped; } private void verifyValue(String key, Versioned<String> value) { if(value == null) { throw new RuntimeException("Versioned is empty for key [" + key + "]") {}; } else if(value.getValue() == null) { throw new RuntimeException("Versioned has empty value inside for key [" + key + "]") {}; } // For some reasons the expected and retrieved values are not the same. // else { // String retrievedValue = value.getValue(); // String expectedValue = kvMap.get(key) + "_" + kvUpdateCount.get(key); // if(retrievedValue.equals(expectedValue) == false) { // throw new RuntimeException("Key " + key + " Expected Value " + // expectedValue // + " Retrieved Value " + retrievedValue) {}; // } // } } public void verifyPostConditions() { // for(String key: kvMap.keySet()) { // Versioned<String> value = client.get(key); // verifyValue(key, value); // } Map<String, Integer> eMap = exceptionCount; logger.info("-------------------------------------------------------------------"); logger.info("Client Operation Info of [" + clientName + "]"); logger.info(requestCount.toString()); if(eMap.size() == 0) { logger.info("No Exception reported by ClientTrafficVerifier(ObsoleteVersionException are ignored)"); logger.info("-------------------------------------------------------------------"); } else { logger.info("Exceptions Count Map of the client: "); logger.info(eMap.toString()); logger.info("-------------------------------------------------------------------"); throw new RuntimeException("Found Exceptions by Client" + eMap); } } }