| /* |
| * 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.calcite.test; |
| |
| import org.apache.calcite.config.CalciteSystemProperty; |
| import org.apache.calcite.util.Sources; |
| import org.apache.calcite.util.TestUtil; |
| |
| import org.apache.cassandra.concurrent.Stage; |
| import org.apache.cassandra.config.DatabaseDescriptor; |
| import org.apache.cassandra.service.CassandraDaemon; |
| import org.apache.cassandra.service.StorageService; |
| import org.apache.commons.lang3.SystemUtils; |
| |
| import com.datastax.oss.driver.api.core.CqlSession; |
| import com.google.common.collect.ImmutableMap; |
| |
| import org.cassandraunit.utils.EmbeddedCassandraServerHelper; |
| import org.junit.jupiter.api.extension.ConditionEvaluationResult; |
| import org.junit.jupiter.api.extension.ExecutionCondition; |
| import org.junit.jupiter.api.extension.ExtensionConfigurationException; |
| import org.junit.jupiter.api.extension.ExtensionContext; |
| import org.junit.jupiter.api.extension.ParameterContext; |
| import org.junit.jupiter.api.extension.ParameterResolutionException; |
| import org.junit.jupiter.api.extension.ParameterResolver; |
| |
| import java.io.IOException; |
| import java.io.UncheckedIOException; |
| import java.lang.reflect.Field; |
| import java.time.Duration; |
| import java.util.Locale; |
| import java.util.Objects; |
| import java.util.concurrent.ExecutionException; |
| |
| /** |
| * JUnit5 extension to start and stop embedded Cassandra server. |
| * |
| * <p>Note that tests will be skipped if running on JDK11+, Eclipse OpenJ9 JVM or Windows |
| * (the first isn't supported by cassandra-unit, the last two by Cassandra) see |
| * <a href="https://github.com/jsevellec/cassandra-unit/issues/294">cassandra-unit issue #294</a> |
| * <a href="https://issues.apache.org/jira/browse/CASSANDRA-14883">CASSANDRA-14883</a>, |
| * and <a href="https://issues.apache.org/jira/browse/CASSANDRA-16956">CASSANDRA-16956</a>, |
| * respectively. |
| */ |
| class CassandraExtension implements ParameterResolver, ExecutionCondition { |
| |
| private static final ExtensionContext.Namespace NAMESPACE = |
| ExtensionContext.Namespace.create(CassandraExtension.class); |
| |
| private static final String KEY = "cassandra"; |
| |
| @Override public boolean supportsParameter(final ParameterContext parameterContext, |
| final ExtensionContext extensionContext) throws ParameterResolutionException { |
| final Class<?> type = parameterContext.getParameter().getType(); |
| return CqlSession.class.isAssignableFrom(type); |
| } |
| |
| @Override public Object resolveParameter(final ParameterContext parameterContext, |
| final ExtensionContext extensionContext) throws ParameterResolutionException { |
| |
| Class<?> type = parameterContext.getParameter().getType(); |
| if (CqlSession.class.isAssignableFrom(type)) { |
| return getOrCreate(extensionContext).session; |
| } |
| |
| throw new ExtensionConfigurationException( |
| String.format(Locale.ROOT, "%s supports only %s but yours was %s", |
| CassandraExtension.class.getSimpleName(), CqlSession.class.getName(), type.getName())); |
| } |
| |
| static ImmutableMap<String, String> getDataset(String resourcePath) { |
| return ImmutableMap.of("model", |
| Sources.of(Objects.requireNonNull(CassandraExtension.class.getResource(resourcePath))) |
| .file().getAbsolutePath()); |
| } |
| |
| /** Registers a Cassandra resource in root context, so it can be shared with |
| * other tests. */ |
| private static CassandraResource getOrCreate(ExtensionContext context) { |
| // same cassandra instance should be shared across all extension instances |
| return context.getRoot() |
| .getStore(NAMESPACE) |
| .getOrComputeIfAbsent(KEY, key -> new CassandraResource(), CassandraResource.class); |
| } |
| |
| /** |
| * Whether to run this test. |
| * |
| * <p>Enabled by default, unless explicitly disabled |
| * from command line ({@code -Dcalcite.test.cassandra=false}) or running on incompatible JDK |
| * version or JVM (see below). |
| * |
| * <p>cassandra-unit does not support JDK11+ yet, therefore all cassandra tests will be skipped |
| * if this JDK version is used. |
| * |
| * @see <a href="https://github.com/jsevellec/cassandra-unit/issues/294">cassandra-unit issue #294</a> |
| * |
| * <p>Cassandra does not support Eclipse OpenJ9 JVM, therefore all cassandra tests will be |
| * skipped if this JVM version is used. |
| * |
| * @see <a href="https://issues.apache.org/jira/browse/CASSANDRA-14883">CASSANDRA-14883</a> |
| * |
| * <p>Cassandra requires method |
| * {@link com.google.common.collect.ImmutableSet#builderWithExpectedSize(int)} |
| * and therefore Guava 23 or higher. |
| * |
| * <p>Cassandra dropped support for Windows in 4.1.x |
| * @see <a href="https://issues.apache.org/jira/browse/CASSANDRA-16956">CASSANDRA-16956</a> |
| * |
| * @return {@code true} if test is compatible with current environment, |
| * {@code false} otherwise |
| */ |
| @Override public ConditionEvaluationResult evaluateExecutionCondition( |
| final ExtensionContext context) { |
| boolean enabled = CalciteSystemProperty.TEST_CASSANDRA.value(); |
| // remove JDK version check once cassandra-unit supports JDK11+ |
| boolean compatibleJdk = TestUtil.getJavaMajorVersion() < 11; |
| boolean compatibleGuava = TestUtil.getGuavaMajorVersion() >= 23; |
| // remove JVM check once Cassandra supports Eclipse OpenJ9 JVM |
| boolean compatibleJVM = !"Eclipse OpenJ9".equals(TestUtil.getJavaVirtualMachineVendor()); |
| boolean compatibleOS = !SystemUtils.IS_OS_WINDOWS; |
| if (enabled && compatibleJdk && compatibleGuava && compatibleJVM && compatibleOS) { |
| return ConditionEvaluationResult.enabled("Cassandra tests enabled"); |
| } |
| |
| final StringBuilder sb = new StringBuilder("Cassandra tests disabled:"); |
| if (!compatibleJdk) { |
| sb.append("\n - JDK11+ is not supported"); |
| } |
| if (!compatibleGuava) { |
| sb.append("\n - Guava versions < 23 are not supported"); |
| } |
| if (!compatibleJVM) { |
| sb.append("\n - Eclipse OpenJ9 JVM is not supported"); |
| } |
| if (!compatibleOS) { |
| sb.append("\n - Cassandra doesn't support Windows"); |
| } |
| return ConditionEvaluationResult.disabled(sb.toString()); |
| } |
| |
| /** Cassandra resource. */ |
| private static class CassandraResource |
| implements ExtensionContext.Store.CloseableResource { |
| private final CqlSession session; |
| |
| private CassandraResource() { |
| startCassandra(); |
| this.session = EmbeddedCassandraServerHelper.getSession(); |
| } |
| |
| /** |
| * Best effort to gracefully shutdown <strong>embedded</strong> cassandra |
| * cluster. |
| * |
| * <p>Since it uses many static variables as well as {@link System#exit(int)} |
| * during close, clean shutdown (as part of unit test) is not |
| * straightforward. |
| */ |
| @Override public void close() { |
| session.close(); |
| |
| CassandraDaemon daemon = extractDaemon(); |
| daemon.stopNativeTransport(); |
| |
| StorageService storage = StorageService.instance; |
| storage.setRpcReady(false); |
| storage.stopClient(); |
| storage.stopTransports(); |
| try { |
| storage.drain(); // try to close all resources |
| } catch (IOException | ExecutionException e) { |
| throw new RuntimeException(e); |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| } |
| Stage.shutdownNow(); |
| } |
| |
| private static void startCassandra() { |
| // This static init is necessary otherwise tests fail with CassandraUnit in IntelliJ (jdk10) |
| // should be called right after constructor |
| // NullPointerException for DatabaseDescriptor.getDiskFailurePolicy |
| // for more info see |
| // https://github.com/jsevellec/cassandra-unit/issues/249 |
| // https://github.com/jsevellec/cassandra-unit/issues/221 |
| DatabaseDescriptor.daemonInitialization(); |
| |
| // Apache Jenkins often fails with |
| // Cassandra daemon did not start within timeout (20 sec by default) |
| try { |
| EmbeddedCassandraServerHelper.startEmbeddedCassandra(Duration.ofMinutes(2).toMillis()); |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| } |
| |
| /** |
| * Extract {@link CassandraDaemon} instance using reflection. It will be used |
| * to shutdown the cluster |
| */ |
| private static CassandraDaemon extractDaemon() { |
| try { |
| Field field = EmbeddedCassandraServerHelper.class.getDeclaredField("cassandraDaemon"); |
| field.setAccessible(true); |
| CassandraDaemon daemon = (CassandraDaemon) field.get(null); |
| |
| if (daemon == null) { |
| throw new IllegalStateException("Cassandra daemon was not initialized by " |
| + EmbeddedCassandraServerHelper.class.getSimpleName()); |
| } |
| return daemon; |
| } catch (NoSuchFieldException | IllegalAccessException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| } |