blob: 576862bb6b3301925aee4ea74c47755fef9d83ef [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.cassandra.config;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Test;
import org.apache.cassandra.utils.Pair;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* Verifies that {@link DatabaseDescriptor#clientInitialization()} } and a couple of <i>apply</i> methods
* do not somehow lazily initialize any unwanted part of Cassandra like schema, commit log or start
* unexpected threads.
*
* {@link DatabaseDescriptor#toolInitialization()} is tested via unit tests extending
* {@link org.apache.cassandra.tools.ToolsTester}.
*/
public class DatabaseDescriptorRefTest
{
static final String[] validClasses = {
"org.apache.cassandra.audit.AuditLogOptions",
"org.apache.cassandra.audit.BinAuditLogger",
"org.apache.cassandra.audit.BinLogAuditLogger",
"org.apache.cassandra.audit.IAuditLogger",
"org.apache.cassandra.auth.AllowAllInternodeAuthenticator",
"org.apache.cassandra.auth.IInternodeAuthenticator",
"org.apache.cassandra.auth.IAuthenticator",
"org.apache.cassandra.auth.IAuthorizer",
"org.apache.cassandra.auth.IRoleManager",
"org.apache.cassandra.auth.INetworkAuthorizer",
"org.apache.cassandra.config.DatabaseDescriptor",
"org.apache.cassandra.config.ConfigurationLoader",
"org.apache.cassandra.config.Config",
"org.apache.cassandra.config.Config$1",
"org.apache.cassandra.config.Config$CommitLogSync",
"org.apache.cassandra.config.Config$CommitFailurePolicy",
"org.apache.cassandra.config.Config$DiskAccessMode",
"org.apache.cassandra.config.Config$DiskFailurePolicy",
"org.apache.cassandra.config.Config$DiskOptimizationStrategy",
"org.apache.cassandra.config.Config$FlushCompression",
"org.apache.cassandra.config.Config$InternodeCompression",
"org.apache.cassandra.config.Config$MemtableAllocationType",
"org.apache.cassandra.config.Config$RepairCommandPoolFullStrategy",
"org.apache.cassandra.config.Config$UserFunctionTimeoutPolicy",
"org.apache.cassandra.config.Config$CorruptedTombstoneStrategy",
"org.apache.cassandra.config.DatabaseDescriptor$ByteUnit",
"org.apache.cassandra.config.ParameterizedClass",
"org.apache.cassandra.config.EncryptionOptions",
"org.apache.cassandra.config.EncryptionOptions$ClientEncryptionOptions",
"org.apache.cassandra.config.EncryptionOptions$ServerEncryptionOptions",
"org.apache.cassandra.config.EncryptionOptions$ServerEncryptionOptions$InternodeEncryption",
"org.apache.cassandra.config.EncryptionOptions$ServerEncryptionOptions$OutgoingEncryptedPortSource",
"org.apache.cassandra.config.YamlConfigurationLoader",
"org.apache.cassandra.config.YamlConfigurationLoader$PropertiesChecker",
"org.apache.cassandra.config.YamlConfigurationLoader$PropertiesChecker$1",
"org.apache.cassandra.config.YamlConfigurationLoader$CustomConstructor",
"org.apache.cassandra.config.TransparentDataEncryptionOptions",
"org.apache.cassandra.db.ConsistencyLevel",
"org.apache.cassandra.db.commitlog.CommitLogSegmentManagerFactory",
"org.apache.cassandra.db.commitlog.DefaultCommitLogSegmentMgrFactory",
"org.apache.cassandra.db.commitlog.AbstractCommitLogSegmentManager",
"org.apache.cassandra.db.commitlog.CommitLogSegmentManagerCDC",
"org.apache.cassandra.db.commitlog.CommitLogSegmentManagerStandard",
"org.apache.cassandra.db.commitlog.CommitLog",
"org.apache.cassandra.db.commitlog.CommitLogMBean",
"org.apache.cassandra.dht.IPartitioner",
"org.apache.cassandra.distributed.api.IInstance",
"org.apache.cassandra.distributed.api.IIsolatedExecutor",
"org.apache.cassandra.distributed.shared.InstanceClassLoader",
"org.apache.cassandra.distributed.impl.InstanceConfig",
"org.apache.cassandra.distributed.api.IInvokableInstance",
"org.apache.cassandra.distributed.impl.InvokableInstance$CallableNoExcept",
"org.apache.cassandra.distributed.impl.InvokableInstance$InstanceFunction",
"org.apache.cassandra.distributed.impl.InvokableInstance$SerializableBiConsumer",
"org.apache.cassandra.distributed.impl.InvokableInstance$SerializableBiFunction",
"org.apache.cassandra.distributed.impl.InvokableInstance$SerializableCallable",
"org.apache.cassandra.distributed.impl.InvokableInstance$SerializableConsumer",
"org.apache.cassandra.distributed.impl.InvokableInstance$SerializableFunction",
"org.apache.cassandra.distributed.impl.InvokableInstance$SerializableRunnable",
"org.apache.cassandra.distributed.impl.InvokableInstance$SerializableTriFunction",
"org.apache.cassandra.distributed.impl.InvokableInstance$TriFunction",
"org.apache.cassandra.distributed.impl.Message",
"org.apache.cassandra.distributed.impl.NetworkTopology",
"org.apache.cassandra.exceptions.ConfigurationException",
"org.apache.cassandra.exceptions.RequestValidationException",
"org.apache.cassandra.exceptions.CassandraException",
"org.apache.cassandra.exceptions.TransportException",
"org.apache.cassandra.fql.FullQueryLogger",
"org.apache.cassandra.fql.FullQueryLoggerOptions",
"org.apache.cassandra.locator.IEndpointSnitch",
"org.apache.cassandra.io.FSWriteError",
"org.apache.cassandra.io.FSError",
"org.apache.cassandra.io.compress.ICompressor",
"org.apache.cassandra.io.compress.ICompressor$Uses",
"org.apache.cassandra.io.compress.LZ4Compressor",
"org.apache.cassandra.io.sstable.metadata.MetadataType",
"org.apache.cassandra.io.util.BufferedDataOutputStreamPlus",
"org.apache.cassandra.io.util.DataOutputBuffer",
"org.apache.cassandra.io.util.DataOutputBufferFixed",
"org.apache.cassandra.io.util.DataOutputStreamPlus",
"org.apache.cassandra.io.util.DataOutputPlus",
"org.apache.cassandra.io.util.DiskOptimizationStrategy",
"org.apache.cassandra.io.util.SpinningDiskOptimizationStrategy",
"org.apache.cassandra.locator.Replica",
"org.apache.cassandra.locator.SimpleSeedProvider",
"org.apache.cassandra.locator.SeedProvider",
"org.apache.cassandra.security.EncryptionContext",
"org.apache.cassandra.service.CacheService$CacheType",
"org.apache.cassandra.utils.binlog.BinLogOptions",
"org.apache.cassandra.utils.FBUtilities",
"org.apache.cassandra.utils.FBUtilities$1",
"org.apache.cassandra.utils.CloseableIterator",
"org.apache.cassandra.utils.Pair",
"org.apache.cassandra.OffsetAwareConfigurationLoader",
"org.apache.cassandra.ConsoleAppender",
"org.apache.cassandra.ConsoleAppender$1",
"org.apache.cassandra.LogbackStatusListener",
"org.apache.cassandra.LogbackStatusListener$ToLoggerOutputStream",
"org.apache.cassandra.LogbackStatusListener$WrappedPrintStream",
"org.apache.cassandra.TeeingAppender",
// generated classes
"org.apache.cassandra.config.ConfigBeanInfo",
"org.apache.cassandra.config.ConfigCustomizer",
"org.apache.cassandra.config.EncryptionOptionsBeanInfo",
"org.apache.cassandra.config.EncryptionOptionsCustomizer",
"org.apache.cassandra.config.EncryptionOptions$ServerEncryptionOptionsBeanInfo",
"org.apache.cassandra.config.EncryptionOptions$ServerEncryptionOptionsCustomizer",
"org.apache.cassandra.ConsoleAppenderBeanInfo",
"org.apache.cassandra.ConsoleAppenderCustomizer",
"org.apache.cassandra.locator.InetAddressAndPort",
"org.apache.cassandra.cql3.statements.schema.AlterKeyspaceStatement",
"org.apache.cassandra.cql3.statements.schema.CreateKeyspaceStatement"
};
static final Set<String> checkedClasses = new HashSet<>(Arrays.asList(validClasses));
@Test
public void testDatabaseDescriptorRef() throws Throwable
{
PrintStream out = System.out;
PrintStream err = System.err;
ThreadMXBean threads = ManagementFactory.getThreadMXBean();
int threadCount = threads.getThreadCount();
ClassLoader delegate = Thread.currentThread().getContextClassLoader();
List<Pair<String, Exception>> violations = Collections.synchronizedList(new ArrayList<>());
ClassLoader cl = new ClassLoader(null)
{
final Map<String, Class<?>> classMap = new HashMap<>();
public URL getResource(String name)
{
return delegate.getResource(name);
}
public InputStream getResourceAsStream(String name)
{
return delegate.getResourceAsStream(name);
}
protected Class<?> findClass(String name) throws ClassNotFoundException
{
if (name.startsWith("java."))
// Java 11 does not allow a "custom" class loader (i.e. user code)
// to define classes in protected packages (like java, java.sql, etc).
// Therefore we have to delegate the call to the delegate class loader
// itself.
return delegate.loadClass(name);
Class<?> cls = classMap.get(name);
if (cls != null)
return cls;
if (name.startsWith("org.apache.cassandra."))
{
// out.println(name);
if (!checkedClasses.contains(name))
violations.add(Pair.create(name, new Exception()));
}
URL url = delegate.getResource(name.replace('.', '/') + ".class");
if (url == null)
{
// For Java 11: system class files are not readable via getResource(), so
// try it this way
cls = Class.forName(name, false, delegate);
classMap.put(name, cls);
return cls;
}
// Java8 way + all non-system class files
try (InputStream in = url.openConnection().getInputStream())
{
ByteArrayOutputStream os = new ByteArrayOutputStream();
int c;
while ((c = in.read()) != -1)
os.write(c);
byte[] data = os.toByteArray();
cls = defineClass(name, data, 0, data.length);
classMap.put(name, cls);
return cls;
}
catch (IOException e)
{
throw new ClassNotFoundException(name, e);
}
}
};
Thread.currentThread().setContextClassLoader(cl);
assertEquals("thread started", threadCount, threads.getThreadCount());
Class cDatabaseDescriptor = Class.forName("org.apache.cassandra.config.DatabaseDescriptor", true, cl);
for (String methodName : new String[]{
"clientInitialization",
"applyAddressConfig",
"applyInitialTokens",
// no seed provider in default configuration for clients
// "applySeedProvider",
// definitely not safe for clients - implicitly instantiates schema
// "applyPartitioner",
// definitely not safe for clients - implicitly instantiates StorageService
// "applySnitch",
"applyEncryptionContext",
// starts "REQUEST-SCHEDULER" thread via RoundRobinScheduler
})
{
Method method = cDatabaseDescriptor.getDeclaredMethod(methodName);
method.invoke(null);
if ("clientInitialization".equals(methodName) &&
threadCount + 2 == threads.getThreadCount())
{
// ignore the "AsyncAppender-Worker-ASYNC" and "logback-1" threads
threadCount = threadCount + 2;
}
if (threadCount != threads.getThreadCount())
{
for (ThreadInfo threadInfo : threads.getThreadInfo(threads.getAllThreadIds()))
out.println("Thread #" + threadInfo.getThreadId() + ": " + threadInfo.getThreadName());
assertEquals("thread started in " + methodName, threadCount, ManagementFactory.getThreadMXBean().getThreadCount());
}
checkViolations(err, violations);
}
}
private void checkViolations(PrintStream err, List<Pair<String, Exception>> violations)
{
if (!violations.isEmpty())
{
for (Pair<String, Exception> violation : new ArrayList<>(violations))
{
err.println();
err.println();
err.println("VIOLATION: " + violation.left);
violation.right.printStackTrace(err);
}
fail();
}
}
}