blob: e3b76822928a8917471a8f0fea80a7ee79e33334 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.drill.test;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.drill.exec.ExecConstants;
import org.apache.drill.exec.ZookeeperHelper;
import org.apache.drill.exec.server.options.OptionDefinition;
import com.google.common.base.Preconditions;
/**
* Build a Drillbit and client with the options provided. The simplest
* builder starts an embedded Drillbit, with the "dfs" name space,
* a max width (parallelization) of 2.
* <p>
* Designed primarily for unit tests: the builders provide control
* over all aspects of the Drillbit or cluster. Can also be used to
* create an embedded Drillbit, use the zero-argument
* constructor which will omit creating set of test-only directories
* and will skip creating the test-only storage plugins and other
* configuration. In this mode, you should configure the builder
* to read from a config file, or specify all the non-default
* config options needed.
*/
public class ClusterFixtureBuilder {
public static class RuntimeOption {
public String key;
public Object value;
public RuntimeOption(String key, Object value) {
this.key = key;
this.value = value;
}
}
// Values in the drill-module.conf file for values that are customized
// in the defaults.
public static final int DEFAULT_ZK_REFRESH = 500; // ms
protected ConfigBuilder configBuilder = new ConfigBuilder();
protected List<RuntimeOption> sessionOptions;
protected List<RuntimeOption> systemOptions;
protected int bitCount = 1;
protected String bitNames[];
protected int localZkCount;
protected ZookeeperHelper zkHelper;
protected boolean usingZk;
protected Properties clientProps;
protected final BaseDirTestWatcher dirTestWatcher;
public ClusterFixtureBuilder() {
this.dirTestWatcher = null;
}
public ClusterFixtureBuilder(BaseDirTestWatcher dirTestWatcher) {
this.dirTestWatcher = Preconditions.checkNotNull(dirTestWatcher);
}
/**
* The configuration builder which this fixture builder uses.
* @return the configuration builder for use in setting "advanced"
* configuration options.
*/
public ConfigBuilder configBuilder() { return configBuilder; }
/**
* Use the given configuration file, stored as a resource, to start the
* embedded Drillbit. Note that the resource file should have the two
* following settings to work as a test:
* <pre><code>
* drill.exec.sys.store.provider.local.write : false,
* drill.exec.http.enabled : false
* </code></pre>
* It may be more convenient to add your settings to the default
* config settings with {@link #configProperty(String, Object)}.
* @param configResource path to the file that contains the
* config file to be read
* @return this builder
* @see {@link #configProperty(String, Object)}
*/
public ClusterFixtureBuilder configResource(String configResource) {
// TypeSafe gets unhappy about a leading slash, but other functions
// require it. Silently discard the leading slash if given to
// preserve the test writer's sanity.
configBuilder.resource(ClusterFixture.trimSlash(configResource));
return this;
}
public ClusterFixtureBuilder setOptionDefault(String key, Object value) {
String option_name = ExecConstants.OPTION_DEFAULTS_ROOT + key;
configBuilder().put(option_name, value.toString());
return this;
}
/**
* Add an additional boot-time property for the embedded Drillbit.
* @param key config property name
* @param value String property value
* @return this builder
*/
public ClusterFixtureBuilder configProperty(String key, Object value) {
configBuilder.put(key, value.toString());
return this;
}
/**
* Non-string {@link #configProperty}
* @param key config property name
* @param value property value
* @return this builder
*/
public ClusterFixtureBuilder configNonStringProperty(String key, Object value) {
configBuilder.put(key, value);
return this;
}
public ClusterFixtureBuilder putDefinition(OptionDefinition definition) {
configBuilder.putDefinition(definition);
return this;
}
/**
* Add an additional property for the client connection URL. Convert all the values into
* String type.
* @param key config property name
* @param value property value
* @return this builder
*/
public ClusterFixtureBuilder configClientProperty(String key, Object value) {
if (clientProps == null) {
clientProps = new Properties();
}
clientProps.put(key, value.toString());
return this;
}
/**
* Provide a session option to be set once the Drillbit
* is started.
*
* @param key the name of the session option
* @param value the value of the session option
* @return this builder
*/
public ClusterFixtureBuilder sessionOption(String key, Object value) {
if (sessionOptions == null) {
sessionOptions = new ArrayList<>();
}
sessionOptions.add(new RuntimeOption(key, value));
return this;
}
/**
* Provide a system option to be set once the Drillbit
* is started.
*
* @param key the name of the system option
* @param value the value of the system option
* @return this builder
*/
public ClusterFixtureBuilder systemOption(String key, Object value) {
if (systemOptions == null) {
systemOptions = new ArrayList<>();
}
systemOptions.add(new RuntimeOption(key, value));
return this;
}
/**
* Set the maximum parallelization (max width per node). Defaults
* to 2.
*
* @param n the "max width per node" parallelization option.
* @return this builder
*/
public ClusterFixtureBuilder maxParallelization(int n) {
return sessionOption(ExecConstants.MAX_WIDTH_PER_NODE_KEY, n);
}
/**
* The number of Drillbits to start in the cluster.
*
* @param n the desired cluster size
* @return this builder
*/
public ClusterFixtureBuilder clusterSize(int n) {
bitCount = n;
bitNames = null;
return this;
}
/**
* Define a cluster by providing names to the Drillbits.
* The cluster size is the same as the number of names provided.
*
* @param bitNames array of (unique) Drillbit names
* @return this builder
*/
public ClusterFixtureBuilder withBits(String...bitNames) {
this.bitNames = bitNames;
bitCount = bitNames.length;
return this;
}
/**
* By default the embedded Drillbits use an in-memory cluster coordinator.
* Use this option to start an in-memory ZK instance to coordinate the
* Drillbits.
* @return this builder
*/
public ClusterFixtureBuilder withLocalZk() {
return withLocalZk(1);
}
public ClusterFixtureBuilder withLocalZk(int count) {
localZkCount = count;
usingZk = true;
// Using ZK. Turn refresh wait back on.
return configProperty(ExecConstants.ZK_REFRESH, DEFAULT_ZK_REFRESH);
}
public ClusterFixtureBuilder withRemoteZk(String connStr) {
usingZk = true;
return configProperty(ExecConstants.ZK_CONNECTION, connStr);
}
/**
* Run the cluster using a Zookeeper started externally. Use this if
* multiple tests start a cluster: allows ZK to be started once for
* the entire suite rather than once per test case.
*
* @param zk the global Zookeeper to use
* @return this builder
*/
public ClusterFixtureBuilder withZk(ZookeeperHelper zk) {
zkHelper = zk;
usingZk = true;
// Using ZK. Turn refresh wait back on.
configProperty(ExecConstants.ZK_REFRESH, DEFAULT_ZK_REFRESH);
return this;
}
/**
* Enable saving of query profiles. The only way to save them is
* to enable local store provider writes, which also saves the
* storage plugin configs.
*
* @return this builder
*/
public ClusterFixtureBuilder saveProfiles() {
configProperty(ExecConstants.SYS_STORE_PROVIDER_LOCAL_ENABLE_WRITE, true);
systemOption(ExecConstants.ENABLE_QUERY_PROFILE_OPTION, true);
systemOption(ExecConstants.QUERY_PROFILE_DEBUG_OPTION, true);
return this;
}
/**
* Create the embedded Drillbit and client, applying the options set
* in the builder. Best to use this in a try-with-resources block:
* <pre><code>
* FixtureBuilder builder = ClientFixture.newBuilder()
* .property(...)
* .sessionOption(...)
* ;
* try (ClusterFixture cluster = builder.build();
* ClientFixture client = cluster.clientFixture()) {
* // Do the test
* }
* </code></pre>
* Note that you use a single cluster fixture to create any number of
* drillbits in your cluster. If you want multiple clients, create the
* first as above, the others (or even the first) using the
* {@link ClusterFixture#clientBuilder()}. Using the client builder
* also lets you set client-side options in the rare cases that you
* need them.
*/
public ClusterFixture build() {
return new ClusterFixture(this);
}
public ClusterMockStorageFixture buildCustomMockStorage() {
return new ClusterMockStorageFixture(this);
}
}