/**
* Copyright 2015 Confluent 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 io.confluent.kafkarest.integration;
import io.confluent.common.utils.IntegrationTest;
import io.confluent.kafkarest.*;
import org.apache.kafka.common.protocol.SecurityProtocol;
import org.apache.kafka.common.security.JaasUtils;
import org.apache.kafka.common.utils.Time;
import org.eclipse.jetty.server.Server;
import org.junit.After;
import org.junit.Before;
import org.junit.experimental.categories.Category;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import io.confluent.kafka.schemaregistry.avro.AvroCompatibilityLevel;
import io.confluent.kafka.schemaregistry.rest.SchemaRegistryConfig;
import io.confluent.kafka.schemaregistry.rest.SchemaRegistryRestApplication;
import kafka.server.KafkaConfig;
import kafka.server.KafkaServer;
import kafka.utils.CoreUtils;
import kafka.utils.TestUtils;
import kafka.utils.ZkUtils;
import kafka.zk.EmbeddedZookeeper;
import scala.Option;
import scala.collection.JavaConversions;
/**
* Test harness to run against a real, local Kafka cluster and REST proxy. This is essentially
* Kafka's ZookeeperTestHarness and KafkaServerTestHarness traits combined and ported to Java with
* the addition of the REST proxy. Defaults to a 1-ZK, 3-broker, 1 REST proxy cluster.
*/
@Category(IntegrationTest.class)
public abstract class ClusterTestHarness {
public static final int DEFAULT_NUM_BROKERS = 1;
/**
* Choose a number of random available ports
*/
public static int[] choosePorts(int count) {
try {
ServerSocket[] sockets = new ServerSocket[count];
int[] ports = new int[count];
for (int i = 0; i < count; i++) {
sockets[i] = new ServerSocket(0);
ports[i] = sockets[i].getLocalPort();
}
for (int i = 0; i < count; i++)
sockets[i].close();
return ports;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Choose an available port
*/
public static int choosePort() {
return choosePorts(1)[0];
}
private int numBrokers;
private boolean withSchemaRegistry;
// ZK Config
protected String zkConnect;
protected EmbeddedZookeeper zookeeper;
protected ZkUtils zkUtils;
protected int zkConnectionTimeout = 6000;
protected int zkSessionTimeout = 6000;
// Kafka Config
protected List<KafkaConfig> configs = null;
protected List<KafkaServer> servers = null;
protected String brokerList = null;
// Schema registry config
protected String schemaRegCompatibility = AvroCompatibilityLevel.NONE.name;
protected Properties schemaRegProperties = null;
protected String schemaRegConnect = null;
protected SchemaRegistryRestApplication schemaRegApp = null;
protected Server schemaRegServer = null;
protected Properties restProperties = null;
protected KafkaRestConfig restConfig = null;
protected TestKafkaRestApplication restApp = null;
protected Server restServer = null;
protected String restConnect = null;
public ClusterTestHarness() {
this(DEFAULT_NUM_BROKERS, false);
}
public ClusterTestHarness(int numBrokers, boolean withSchemaRegistry) {
this.numBrokers = numBrokers;
this.withSchemaRegistry = withSchemaRegistry;
schemaRegProperties = new Properties();
restProperties = new Properties();
}
public Properties overrideBrokerProperties(int i, Properties props) {
return props;
}
@Before
public void setUp() throws Exception {
zookeeper = new EmbeddedZookeeper();
zkConnect = String.format("127.0.0.1:%d", zookeeper.port());
zkUtils = ZkUtils.apply(
zkConnect, zkSessionTimeout, zkConnectionTimeout,
JaasUtils.isZkSecurityEnabled());
configs = new Vector<>();
servers = new Vector<>();
for (int i = 0; i < numBrokers; i++) {
final Option<java.io.File> noFile = scala.Option.apply(null);
final Option<SecurityProtocol> noInterBrokerSecurityProtocol = scala.Option.apply(null);
Properties props = TestUtils.createBrokerConfig(
i, zkConnect, false, false, TestUtils.RandomPort(), noInterBrokerSecurityProtocol,
noFile, Option.<Properties>empty(), true, false, TestUtils.RandomPort(), false,
TestUtils.RandomPort(), false, TestUtils.RandomPort(), Option.<String>empty());
props.setProperty("auto.create.topics.enable", "false");
// We *must* override this to use the port we allocated (Kafka currently allocates one port
// that it always uses for ZK
props.setProperty("zookeeper.connect", this.zkConnect);
props = overrideBrokerProperties(i, props);
KafkaConfig config = KafkaConfig.fromProps(props);
configs.add(config);
KafkaServer server = TestUtils.createServer(config, Time.SYSTEM);
servers.add(server);
}
brokerList =
TestUtils.getBrokerListStrFromServers(JavaConversions.asScalaBuffer(servers),
SecurityProtocol.PLAINTEXT);
if (withSchemaRegistry) {
int schemaRegPort = choosePort();
schemaRegProperties.put(SchemaRegistryConfig.PORT_CONFIG,
((Integer) schemaRegPort).toString());
schemaRegProperties.put(SchemaRegistryConfig.KAFKASTORE_CONNECTION_URL_CONFIG,
zkConnect);
schemaRegProperties.put(SchemaRegistryConfig.KAFKASTORE_TOPIC_CONFIG,
SchemaRegistryConfig.DEFAULT_KAFKASTORE_TOPIC);
schemaRegProperties.put(SchemaRegistryConfig.COMPATIBILITY_CONFIG,
schemaRegCompatibility);
schemaRegConnect = String.format("http://localhost:%d", schemaRegPort);
schemaRegApp =
new SchemaRegistryRestApplication(new SchemaRegistryConfig(schemaRegProperties));
schemaRegServer = schemaRegApp.createServer();
schemaRegServer.start();
}
int restPort = choosePort();
restProperties.put(KafkaRestConfig.PORT_CONFIG, ((Integer) restPort).toString());
restProperties.put(KafkaRestConfig.ZOOKEEPER_CONNECT_CONFIG, zkConnect);
if (withSchemaRegistry) {
restProperties.put(KafkaRestConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegConnect);
}
restConnect = String.format("http://localhost:%d", restPort);
restConfig = new KafkaRestConfig(restProperties);
restApp = new TestKafkaRestApplication(restConfig, getZkUtils(restConfig),
getMetadataObserver(restConfig),
getProducerPool(restConfig),
getConsumerManager(restConfig),
getSimpleConsumerFactory(restConfig),
getSimpleConsumerManager(restConfig));
restServer = restApp.createServer();
restServer.start();
}
protected ZkUtils getZkUtils(KafkaRestConfig appConfig) {
return null;
}
protected MetadataObserver getMetadataObserver(KafkaRestConfig appConfig) {
return null;
}
protected ProducerPool getProducerPool(KafkaRestConfig appConfig) {
return null;
}
protected ConsumerManager getConsumerManager(KafkaRestConfig appConfig) {
return null;
}
protected SimpleConsumerFactory getSimpleConsumerFactory(KafkaRestConfig appConfig) {
return null;
}
protected SimpleConsumerManager getSimpleConsumerManager(KafkaRestConfig appConfig) {
return null;
}
@After
public void tearDown() throws Exception {
if (restServer != null) {
restServer.stop();
restServer.join();
}
if (schemaRegServer != null) {
schemaRegServer.stop();
schemaRegServer.join();
}
for (KafkaServer server : servers) {
server.shutdown();
}
for (KafkaServer server : servers) {
CoreUtils.delete(server.config().logDirs());
}
zkUtils.close();
zookeeper.shutdown();
}
protected Invocation.Builder request(String path) {
return request(path, null, null, null);
}
protected Invocation.Builder request(String path, Map<String, String> queryParams) {
return request(path, null, null, queryParams);
}
protected Invocation.Builder request(String path, String templateName, Object templateValue) {
return request(path, templateName, templateValue, null);
}
protected Invocation.Builder request(String path, String templateName, Object templateValue,
Map<String, String> queryParams) {
Client client = ClientBuilder.newClient();
// Only configure base application here because as a client we shouldn't need the resources
// registered
restApp.configureBaseApplication(client);
WebTarget target;
URI pathUri = null;
try {
pathUri = new URI(path);
} catch (URISyntaxException e) {
// Ignore, use restConnect and assume this is a valid path part
}
if (pathUri != null && pathUri.isAbsolute()) {
target = client.target(path);
} else {
target = client.target(restConnect).path(path);
}
if (templateName != null && templateValue != null) {
target = target.resolveTemplate(templateName, templateValue);
}
if (queryParams != null) {
for (Map.Entry<String, String> queryParam : queryParams.entrySet()) {
target = target.queryParam(queryParam.getKey(), queryParam.getValue());
}
}
return target.request();
}
}