Java tutorial
/** * (c) Copyright 2012 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.schema.cassandra; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.util.ArrayList; import java.util.Arrays; import java.util.Map; import com.datastax.driver.core.Cluster; import com.datastax.driver.core.Session; import com.datastax.driver.core.SocketOptions; import com.datastax.driver.core.policies.LoggingRetryPolicy; import com.datastax.driver.core.policies.Policies; import com.google.common.base.Preconditions; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.service.EmbeddedCassandraService; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.kiji.delegation.Priority; import org.kiji.schema.KijiIOException; import org.kiji.schema.KijiURI; import org.kiji.schema.impl.cassandra.CassandraAdminFactory; import org.kiji.schema.impl.cassandra.DefaultCassandraFactory; import org.kiji.schema.impl.cassandra.TestingCassandraAdminFactory; /** Factory for Cassandra instances based on URIs. */ public final class TestingCassandraFactory implements CassandraFactory { private static final Logger LOG = LoggerFactory.getLogger(TestingCassandraFactory.class); /** Factory to delegate to. */ private static final CassandraFactory DELEGATE = new DefaultCassandraFactory(); /** * Singleton Cassandra mSession for testing. * * Lazily instantiated when the first test requests a C* mSession for a .fake Kiji instance. * * Once started, will remain alive until the JVM shuts down. */ private static Session mCassandraSession = null; /** * Public constructor. This should not be directly invoked by users; you should * use CassandraFactory.get(), which retains a singleton instance. * * This constructor needs to be public because the Java service loader must be able to * instantiate it. */ public TestingCassandraFactory() { } /** URIs for fake HBase instances are "kiji://.fake.[fake-id]/instance/table". */ private static final String FAKE_CASSANDRA_ID_PREFIX = ".fake."; //------------------------------------------------------------------------------------------------ // URI stuff /** {@inheritDoc} */ @Override public CassandraAdminFactory getCassandraAdminFactory(KijiURI uri) { if (isFakeCassandraURI(uri)) { LOG.debug("URI is a fake C* URI -> Creating FakeCassandraAdminFactory..."); // Make sure that the EmbeddedCassandraService is started try { startEmbeddedCassandraServiceIfNotRunningAndOpenSession(); } catch (Exception e) { throw new KijiIOException("Problem with embedded Cassandra Session! " + e); } // Get an admin factory that will work with the embedded service return createFakeCassandraAdminFactory(); } else { LOG.debug("URI is not a fake Cassandra URI."); return DELEGATE.getCassandraAdminFactory(uri); } } /** * Check whether this is the URI for a fake Cassandra instance. * * @param uri The URI in question. * @return Whether the URI is for a fake instance or not. */ private static boolean isFakeCassandraURI(KijiURI uri) { if (uri.getZookeeperQuorum().size() != 1) { return false; } final String zkHost = uri.getZookeeperQuorum().get(0); if (!zkHost.startsWith(FAKE_CASSANDRA_ID_PREFIX)) { return false; } return true; } //------------------------------------------------------------------------------------------------ // Stuff for starting up C* /** * Return a fake C* admin factory for testing. * @return A C* admin factory that will produce C* admins that will all use the shared * EmbeddedCassandraService. */ private CassandraAdminFactory createFakeCassandraAdminFactory() { Preconditions.checkNotNull(mCassandraSession); return TestingCassandraAdminFactory.get(mCassandraSession); } /** * Ensure that the EmbeddedCassandraService for unit tests is running. If it is not, then start * it. */ private void startEmbeddedCassandraServiceIfNotRunningAndOpenSession() throws Exception { LOG.debug("Ready to start a C* service if necessary..."); if (null != mCassandraSession) { LOG.debug("C* is already running, no need to start the service."); //Preconditions.checkNotNull(mCassandraSession); return; } LOG.debug("Starting EmbeddedCassandra!"); try { LOG.info("Starting EmbeddedCassandraService..."); // Use a custom YAML file that specifies different ports from normal for RPC and thrift. InputStream yamlStream = getClass().getResourceAsStream("/cassandra.yaml"); LOG.debug("Checking that we can load cassandra.yaml as a stream..."); Preconditions.checkNotNull(yamlStream, "Unable to load resource /cassandra.yaml as a stream"); LOG.debug("Looks good to load it as a stream!"); // Update cassandra.yaml to use available ports. String cassandraYaml = IOUtils.toString(yamlStream); final int storagePort = findOpenPort(); // Normally 7000. final int sslStoragePort = findOpenPort(); // Normally 7001. final int nativeTransportPort = findOpenPort(); // Normally 9042. final int rpcPort = findOpenPort(); // Normally 9160. cassandraYaml = updateCassandraYamlWithPort(cassandraYaml, "__STORAGE_PORT__", storagePort); cassandraYaml = updateCassandraYamlWithPort(cassandraYaml, "__SSL_STORAGE_PORT__", sslStoragePort); cassandraYaml = updateCassandraYamlWithPort(cassandraYaml, "__NATIVE_TRANSPORT_PORT__", nativeTransportPort); cassandraYaml = updateCassandraYamlWithPort(cassandraYaml, "__RPC_PORT__", rpcPort); // Write out the YAML contents to a temp file. File yamlFile = File.createTempFile("cassandra", ".yaml"); LOG.info("Writing cassandra.yaml to {}", yamlFile); final BufferedWriter bw = new BufferedWriter(new FileWriter(yamlFile)); try { bw.write(cassandraYaml); } finally { bw.close(); } Preconditions.checkArgument(yamlFile.exists()); System.setProperty("cassandra.config", "file:" + yamlFile.getAbsolutePath()); System.setProperty("cassandra-foreground", "true"); // Make sure that all of the directories for the commit log, data, and caches are empty. // Thank goodness there are methods to get this information (versus parsing the YAML // directly). ArrayList<String> directoriesToDelete = new ArrayList<String>( Arrays.asList(DatabaseDescriptor.getAllDataFileLocations())); directoriesToDelete.add(DatabaseDescriptor.getCommitLogLocation()); directoriesToDelete.add(DatabaseDescriptor.getSavedCachesLocation()); for (String dirName : directoriesToDelete) { FileUtils.deleteDirectory(new File(dirName)); } EmbeddedCassandraService embeddedCassandraService = new EmbeddedCassandraService(); embeddedCassandraService.start(); } catch (IOException ioe) { throw new KijiIOException("Cannot start embedded C* service!"); } try { // Use different port from normal here to avoid conflicts with any locally-running C* cluster. // Port settings are controlled in "cassandra.yaml" in test resources. // Also change the timeouts and retry policies. Since we have only a single thread here for // this test process, it can slow down dramatically if it has to do a compaction (see // SCHEMA-959 and SCHEMA-969 for examples of the flakiness this case cause in unit tests). // No builder for `SocketOptions`: final SocketOptions socketOptions = new SocketOptions(); // Setting this to 0 disables read timeouts. socketOptions.setReadTimeoutMillis(0); // This defaults to 5 s. Increase to a minute. socketOptions.setConnectTimeoutMillis(60 * 1000); Cluster cluster = Cluster.builder().addContactPoints(DatabaseDescriptor.getListenAddress()) .withPort(DatabaseDescriptor.getNativeTransportPort()).withSocketOptions(socketOptions) // Let's at least log all of the retries so we can see what is happening. .withRetryPolicy(new LoggingRetryPolicy(Policies.defaultRetryPolicy())) // The default reconnection policy (exponential) looks fine. .build(); mCassandraSession = cluster.connect(); } catch (Exception exc) { throw new KijiIOException("Started embedded C* service, but cannot connect to cluster. " + exc); } } /** * Update the cassandra.yaml contents to substitute a label with an open port. * * @param yamlContents Contents of the YAML file before substitution. * @param portLabelInFile String "label" to replace with the free port number (e.g., * "__NATIVE_TRANSPORT_PORT__"). * @param freePort Port to use. * @return The contents of the YAML file after the substitution. */ private static String updateCassandraYamlWithPort(String yamlContents, String portLabelInFile, int freePort) { String yamlContentsAfterSub = yamlContents.replace(portLabelInFile, Integer.toString(freePort)); Preconditions.checkArgument(!yamlContentsAfterSub.equals(yamlContents)); return yamlContentsAfterSub; } /** * Find an available port. * * @return an open port number. * @throws IllegalArgumentException if it can't find an open port. */ private static int findOpenPort() { try { ServerSocket serverSocket = new ServerSocket(0); int portNumber = serverSocket.getLocalPort(); serverSocket.setReuseAddress(true); serverSocket.close(); LOG.debug("Found usable port {}", portNumber); return portNumber; } catch (IOException ioe) { throw new RuntimeException("Could not find open port."); } } /** {@inheritDoc} */ @Override public int getPriority(Map<String, String> runtimeHints) { // Higher priority than default factory. return Priority.HIGH; } }