Package org.kiji.schema.cassandra

Source Code of org.kiji.schema.cassandra.TestingCassandraFactory

/**
* (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;
  }
}
TOP

Related Classes of org.kiji.schema.cassandra.TestingCassandraFactory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.