|
46 | 46 | import com.datastax.oss.driver.api.core.CqlSession; |
47 | 47 | import com.datastax.oss.driver.api.core.cql.ResultSet; |
48 | 48 | import com.datastax.oss.driver.api.core.cql.Row; |
49 | | -import com.datastax.oss.driver.api.core.metadata.TokenMap; |
50 | 49 | import com.datastax.oss.driver.api.core.metadata.Metadata; |
| 50 | +import com.datastax.oss.driver.api.core.metadata.TokenMap; |
51 | 51 | import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; |
52 | 52 | import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; |
53 | 53 | import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; |
@@ -435,32 +435,34 @@ protected Metadata fetchMetadataFromSession(CqlSession cqlSession) { |
435 | 435 |
|
436 | 436 | private void setCqlMetadata(CqlSession cqlSession) { |
437 | 437 | Metadata metadata = fetchMetadataFromSession(cqlSession); |
438 | | - |
| 438 | + |
439 | 439 | // Check for token overlap in the specific keyspace |
440 | | - if (hasTokenOverlap(cqlSession, this.keyspaceName)) { |
441 | | - throw new ClusterConfigurationException( |
442 | | - "Token overlap detected in keyspace '" + this.keyspaceName + "'. This usually happens when multiple nodes " + |
443 | | - "were started simultaneously. To fix: 1) Restart nodes one at a time 2) Run 'nodetool cleanup' " + |
444 | | - "on each node 3) Verify with 'nodetool describering " + this.keyspaceName + "'."); |
| 440 | + // Only run this check if the keyspace name is provided |
| 441 | + if (this.keyspaceName != null && !this.keyspaceName.isEmpty() |
| 442 | + && hasTokenOverlap(cqlSession, this.keyspaceName)) { |
| 443 | + throw new ClusterConfigurationException("Token overlap detected in keyspace '" + this.keyspaceName |
| 444 | + + "'. This usually happens when multiple nodes " |
| 445 | + + "were started simultaneously. To fix: 1) Restart nodes one at a time 2) Run 'nodetool cleanup' " |
| 446 | + + "on each node 3) Verify with 'nodetool describering " + this.keyspaceName + "'."); |
445 | 447 | } |
446 | | - |
| 448 | + |
447 | 449 | // Add proper Optional handling for token map |
448 | 450 | Optional<TokenMap> tokenMapOpt = metadata.getTokenMap(); |
449 | 451 | if (!tokenMapOpt.isPresent()) { |
450 | 452 | throw new ClusterConfigurationException( |
451 | | - "Token map is not available. This could indicate a cluster configuration issue."); |
| 453 | + "Token map is not available. This could indicate a cluster configuration issue."); |
452 | 454 | } |
453 | | - |
| 455 | + |
454 | 456 | try { |
455 | 457 | String partitionerName = tokenMapOpt.get().getPartitionerName(); |
456 | 458 | if (null != partitionerName && partitionerName.endsWith("RandomPartitioner")) |
457 | 459 | this.hasRandomPartitioner = true; |
458 | 460 | else |
459 | 461 | this.hasRandomPartitioner = false; |
460 | 462 | } catch (Exception e) { |
461 | | - throw new ClusterConfigurationException( |
462 | | - "Error accessing token map: " + e.getMessage() + |
463 | | - ". This may indicate token overlap in the Cassandra cluster. Check your cluster configuration.", e); |
| 463 | + throw new ClusterConfigurationException("Error accessing token map: " + e.getMessage() |
| 464 | + + ". This may indicate token overlap in the Cassandra cluster. Check your cluster configuration.", |
| 465 | + e); |
464 | 466 | } |
465 | 467 |
|
466 | 468 | Optional<KeyspaceMetadata> keyspaceMetadataOpt = metadata.getKeyspace(formatName(this.keyspaceName)); |
@@ -588,78 +590,99 @@ protected static ConsistencyLevel mapToConsistencyLevel(String level) { |
588 | 590 |
|
589 | 591 | return retVal; |
590 | 592 | } |
591 | | - |
| 593 | + |
592 | 594 | /** |
593 | 595 | * Checks if the specified keyspace has token overlap issues by querying the system.size_estimates table. |
594 | | - * |
595 | | - * @param cqlSession The CQL session to use for executing the query |
596 | | - * @param keyspaceName The name of the keyspace to check |
| 596 | + * |
| 597 | + * @param cqlSession |
| 598 | + * The CQL session to use for executing the query |
| 599 | + * @param keyspaceName |
| 600 | + * The name of the keyspace to check |
| 601 | + * |
597 | 602 | * @return true if token overlap is detected, false otherwise |
598 | 603 | */ |
599 | 604 | private boolean hasTokenOverlap(CqlSession cqlSession, String keyspaceName) { |
| 605 | + // Return false if either the session or keyspace name is null |
| 606 | + if (cqlSession == null || keyspaceName == null || keyspaceName.isEmpty()) { |
| 607 | + return false; |
| 608 | + } |
| 609 | + |
600 | 610 | try { |
601 | 611 | // Execute query to check for token ranges for the specific keyspace |
602 | 612 | String query = "SELECT start_token, end_token FROM system.size_estimates WHERE keyspace_name = ?"; |
603 | 613 | ResultSet rs = cqlSession.execute(query, keyspaceName); |
604 | | - |
| 614 | + |
| 615 | + // Add null check for ResultSet to handle potential driver issues |
| 616 | + if (rs == null) { |
| 617 | + logger.warn("Unable to query system.size_estimates for keyspace {}: ResultSet is null", keyspaceName); |
| 618 | + return false; |
| 619 | + } |
| 620 | + |
605 | 621 | // Create a list to store token ranges for the keyspace |
606 | 622 | List<TokenRange> ranges = new ArrayList<>(); |
607 | | - |
| 623 | + |
608 | 624 | // Process the results |
609 | 625 | for (Row row : rs) { |
610 | 626 | BigInteger startToken = new BigInteger(row.getString("start_token")); |
611 | 627 | BigInteger endToken = new BigInteger(row.getString("end_token")); |
612 | 628 | ranges.add(new TokenRange(startToken, endToken)); |
613 | 629 | } |
614 | | - |
| 630 | + |
615 | 631 | // Check for overlaps |
616 | 632 | if (hasOverlappingTokens(ranges)) { |
617 | 633 | logger.error("Token overlap detected in keyspace: {}", keyspaceName); |
618 | 634 | return true; |
619 | 635 | } |
620 | | - |
| 636 | + |
621 | 637 | return false; |
622 | 638 | } catch (Exception e) { |
623 | 639 | logger.warn("Could not check for token overlap in keyspace {}: {}", keyspaceName, e.getMessage()); |
624 | 640 | return false; |
625 | 641 | } |
626 | 642 | } |
627 | | - |
| 643 | + |
628 | 644 | /** |
629 | 645 | * Determines if there are overlapping token ranges in the provided list. |
630 | | - * |
631 | | - * @param ranges List of token ranges to check |
| 646 | + * |
| 647 | + * @param ranges |
| 648 | + * List of token ranges to check |
| 649 | + * |
632 | 650 | * @return true if any ranges overlap, false otherwise |
633 | 651 | */ |
634 | 652 | private boolean hasOverlappingTokens(List<TokenRange> ranges) { |
| 653 | + // Return false if ranges is null or empty (no overlap possible) |
| 654 | + if (ranges == null || ranges.isEmpty() || ranges.size() < 2) { |
| 655 | + return false; |
| 656 | + } |
| 657 | + |
635 | 658 | // Sort ranges by start token |
636 | 659 | Collections.sort(ranges); |
637 | | - |
| 660 | + |
638 | 661 | // Check for overlaps |
639 | 662 | for (int i = 0; i < ranges.size() - 1; i++) { |
640 | 663 | TokenRange current = ranges.get(i); |
641 | 664 | TokenRange next = ranges.get(i + 1); |
642 | | - |
| 665 | + |
643 | 666 | if (current.endToken.compareTo(next.startToken) > 0) { |
644 | 667 | return true; |
645 | 668 | } |
646 | 669 | } |
647 | | - |
| 670 | + |
648 | 671 | return false; |
649 | 672 | } |
650 | | - |
| 673 | + |
651 | 674 | /** |
652 | 675 | * Helper class to represent a token range with Comparable implementation for sorting. |
653 | 676 | */ |
654 | 677 | private static class TokenRange implements Comparable<TokenRange> { |
655 | 678 | BigInteger startToken; |
656 | 679 | BigInteger endToken; |
657 | | - |
| 680 | + |
658 | 681 | TokenRange(BigInteger startToken, BigInteger endToken) { |
659 | 682 | this.startToken = startToken; |
660 | 683 | this.endToken = endToken; |
661 | 684 | } |
662 | | - |
| 685 | + |
663 | 686 | @Override |
664 | 687 | public int compareTo(TokenRange other) { |
665 | 688 | return this.startToken.compareTo(other.startToken); |
|
0 commit comments