|
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