Merge branch 'cassandra-5.0' into trunk
diff --git a/CHANGES.txt b/CHANGES.txt
index e4ed0d3..b4996c2 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -329,6 +329,7 @@
  * Enforce CQL message size limit on multiframe messages (CASSANDRA-20052)
  * Fix race condition in DecayingEstimatedHistogramReservoir during rescale (CASSANDRA-19365)
 Merged from 4.0:
+ * Leveled Compaction doesn't validate maxBytesForLevel when the table is altered/created (CASSANDRA-20570)
  * Updated dtest-api to 0.0.18 and removed JMX-related classes that now live in the dtest-api (CASSANDRA-20884)
  * Fixed incorrect error message constant for keyspace name length validation (CASSANDRA-20915)
  * Prevent too long table names not fitting file names (CASSANDRA-20389)
diff --git a/src/java/org/apache/cassandra/db/compaction/LeveledCompactionStrategy.java b/src/java/org/apache/cassandra/db/compaction/LeveledCompactionStrategy.java
index 1509aa2..e0664a9 100644
--- a/src/java/org/apache/cassandra/db/compaction/LeveledCompactionStrategy.java
+++ b/src/java/org/apache/cassandra/db/compaction/LeveledCompactionStrategy.java
@@ -18,6 +18,7 @@
 package org.apache.cassandra.db.compaction;
 
 import java.util.*;
+import java.math.BigInteger;
 
 
 import com.google.common.annotations.VisibleForTesting;
@@ -43,6 +44,7 @@
 import org.apache.cassandra.io.sstable.format.SSTableReader;
 
 import static org.apache.cassandra.config.CassandraRelevantProperties.TOLERATE_SSTABLE_SIZE;
+import static org.apache.cassandra.db.compaction.LeveledGenerations.MAX_LEVEL_COUNT;
 
 public class LeveledCompactionStrategy extends AbstractCompactionStrategy
 {
@@ -569,10 +571,14 @@
     {
         Map<String, String> uncheckedOptions = AbstractCompactionStrategy.validateOptions(options);
 
+        int ssSize;
+        int fanoutSize;
+
+        // Validate the sstable_size option
         String size = options.containsKey(SSTABLE_SIZE_OPTION) ? options.get(SSTABLE_SIZE_OPTION) : "1";
         try
         {
-            int ssSize = Integer.parseInt(size);
+            ssSize = Integer.parseInt(size);
             if (ssSize < 1)
             {
                 throw new ConfigurationException(String.format("%s must be larger than 0, but was %s", SSTABLE_SIZE_OPTION, ssSize));
@@ -589,7 +595,7 @@
         String levelFanoutSize = options.containsKey(LEVEL_FANOUT_SIZE_OPTION) ? options.get(LEVEL_FANOUT_SIZE_OPTION) : String.valueOf(DEFAULT_LEVEL_FANOUT_SIZE);
         try
         {
-            int fanoutSize = Integer.parseInt(levelFanoutSize);
+            fanoutSize = Integer.parseInt(levelFanoutSize);
             if (fanoutSize < 1)
             {
                 throw new ConfigurationException(String.format("%s must be larger than 0, but was %s", LEVEL_FANOUT_SIZE_OPTION, fanoutSize));
@@ -597,7 +603,23 @@
         }
         catch (NumberFormatException ex)
         {
-            throw new ConfigurationException(String.format("%s is not a parsable int (base10) for %s", size, LEVEL_FANOUT_SIZE_OPTION), ex);
+            throw new ConfigurationException(String.format("%s is not a parsable int (base10) for %s", levelFanoutSize, LEVEL_FANOUT_SIZE_OPTION), ex);
+        }
+
+        // Validate max Bytes for a level
+        try
+        {
+            long maxSSTableSizeInBytes = Math.multiplyExact(ssSize, 1024L * 1024L); // Convert MB to Bytes
+            BigInteger fanoutPower = BigInteger.valueOf(fanoutSize).pow(MAX_LEVEL_COUNT - 1);
+            BigInteger maxBytes = fanoutPower.multiply(BigInteger.valueOf(maxSSTableSizeInBytes));
+            BigInteger longMaxValue = BigInteger.valueOf(Long.MAX_VALUE);
+            if (maxBytes.compareTo(longMaxValue) > 0)
+                throw new ConfigurationException(String.format("At most %s bytes may be in a compaction level; " +
+                        "your maxSSTableSize must be absurdly high to compute %s", Long.MAX_VALUE, maxBytes));
+        }
+        catch (ArithmeticException ex)
+        {
+            throw new ConfigurationException(String.format("sstable_size_in_mb=%d is too large; resulting bytes exceed Long.MAX_VALUE (%d)", ssSize, Long.MAX_VALUE), ex);
         }
 
         uncheckedOptions.remove(LEVEL_FANOUT_SIZE_OPTION);
diff --git a/test/unit/org/apache/cassandra/db/compaction/LeveledCompactionStrategyTest.java b/test/unit/org/apache/cassandra/db/compaction/LeveledCompactionStrategyTest.java
index 5951f2c..e000b9a 100644
--- a/test/unit/org/apache/cassandra/db/compaction/LeveledCompactionStrategyTest.java
+++ b/test/unit/org/apache/cassandra/db/compaction/LeveledCompactionStrategyTest.java
@@ -975,6 +975,24 @@
         }
     }
 
+    @Test()
+    public void testInvalidFanoutAndSSTableSize()
+    {
+        try
+        {
+            Map<String, String> options = new HashMap<>();
+            options.put("class", "LeveledCompactionStrategy");
+            options.put("fanout_size", "90");
+            options.put("sstable_size_in_mb", "1089");
+            LeveledCompactionStrategy.validateOptions(options);
+            Assert.fail("fanout_sizeed and sstable_size_in_mb are invalid, but did not throw ConfigurationException");
+        }
+        catch (ConfigurationException e)
+        {
+            assertTrue(e.getMessage().contains("your maxSSTableSize must be absurdly high to compute"));
+        }
+    }
+
     @Test
     public void testReduceScopeL0()
     {
diff --git a/test/unit/org/apache/cassandra/schema/CreateTableValidationTest.java b/test/unit/org/apache/cassandra/schema/CreateTableValidationTest.java
index 63d0f96..0688b4a 100644
--- a/test/unit/org/apache/cassandra/schema/CreateTableValidationTest.java
+++ b/test/unit/org/apache/cassandra/schema/CreateTableValidationTest.java
@@ -134,6 +134,13 @@
                              String.format("CREATE TABLE %s.\"    \" (key int PRIMARY KEY, val int)", KEYSPACE));
     }
 
+    @Test
+    public void testInvalidCompactionOptions()
+    {
+        expectedFailure(ConfigurationException.class, "CREATE TABLE %s (k int PRIMARY KEY, v int) WITH compaction = {'class': 'LeveledCompactionStrategy', 'fanout_size': '90', 'sstable_size_in_mb': '1089'}",
+                        "your maxSSTableSize must be absurdly high to compute");
+    }
+
     private void expectedFailure(final Class<? extends RequestValidationException> exceptionType, String statement, String errorMsg)
     {
 
diff --git a/test/unit/org/apache/cassandra/utils/CassandraGenerators.java b/test/unit/org/apache/cassandra/utils/CassandraGenerators.java
index b428e9a..edfbe88 100644
--- a/test/unit/org/apache/cassandra/utils/CassandraGenerators.java
+++ b/test/unit/org/apache/cassandra/utils/CassandraGenerators.java
@@ -52,7 +52,6 @@
 import com.google.common.collect.Sets;
 
 import accord.utils.SortedArrays.SortedArrayList;
-import org.apache.cassandra.db.compaction.LeveledManifest;
 import org.apache.cassandra.schema.*;
 import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
 import org.apache.cassandra.tcm.extensions.ExtensionKey;
@@ -143,6 +142,7 @@
 import static org.apache.cassandra.utils.Generators.TIMESTAMP_NANOS;
 import static org.apache.cassandra.utils.Generators.TINY_TIME_SPAN_NANOS;
 import static org.apache.cassandra.utils.Generators.directAndHeapBytes;
+import static org.junit.Assert.assertTrue;
 
 public final class CassandraGenerators
 {
@@ -585,11 +585,14 @@
                     try
                     {
                         // see org.apache.cassandra.db.compaction.LeveledGenerations.MAX_LEVEL_COUNT for why 8 is hard coded here
-                        LeveledManifest.maxBytesForLevel(8, value, maxSSTableSizeInBytes);
+                        // LeveledManifest.maxBytesForLevel(8, value, maxSSTableSizeInBytes);
+                        options.put(LeveledCompactionStrategy.LEVEL_FANOUT_SIZE_OPTION, value.toString());
+                        LeveledCompactionStrategy.validateOptions(options);
                         break; // value is good, keep it
                     }
-                    catch (RuntimeException e)
+                    catch (ConfigurationException e)
                     {
+                        assertTrue(e.getMessage().contains("your maxSSTableSize must be absurdly high to compute"));
                         // this value is too large... lets shrink it
                         if (value.intValue() == 1)
                             throw new AssertionError("There is no possible fanout size that works with maxSSTableSizeInMB=" + maxSSTableSizeInMB);