{"record":{"id":"7a8836b737c82313","repo":"apache/cassandra","slug":"node-should-be-either-source-or-destination-in-the","errorCode":null,"errorMessage":"Node should be either source or destination in the movement map ","messagePattern":"Node should be either source or destination in the movement map ","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/tcm/sequences/Move.java","lineNumber":357,"sourceCode":"\n                        String[] cfNames = StreamPlan.nonAccordTablesForKeyspace(ks);\n                        for (Map.Entry<Replica, Replica> e : endpoints.flattenEntries())\n                        {\n                            Replica destination = e.getKey();\n                            Replica source = e.getValue();\n                            logger.info(\"Stream source: {} destination: {}\", source, destination);\n                            assert !source.endpoint().equals(destination.endpoint()) : String.format(\"Source %s should not be the same as destionation %s\", source, destination);\n                            if (source.isSelf())\n                                streamPlan.transferRanges(destination.endpoint(), ks.name, RangesAtEndpoint.of(destination), cfNames);\n                            else if (destination.isSelf())\n                            {\n                                if (destination.isFull())\n                                    streamPlan.requestRanges(source.endpoint(), ks.name, RangesAtEndpoint.of(destination), RangesAtEndpoint.empty(destination.endpoint()), cfNames);\n                                else\n                                    streamPlan.requestRanges(source.endpoint(), ks.name, RangesAtEndpoint.empty(destination.endpoint()), RangesAtEndpoint.of(destination), cfNames);\n                            }\n                            else\n                                throw new IllegalStateException(\"Node should be either source or destination in the movement map \" + endpoints);\n                        }\n                    }\n\n                    StreamResultFuture streamResult = streamPlan.execute();\n\n                    Future<?> accordReady = AccordService.instance().epochReadyFor(metadata, EpochReady::reads);\n                    Future<?> ready = FutureCombiner.allOf(streamResult, accordReady);\n                    ready.get();\n                    StorageService.instance.repairPaxosForTopologyChange(\"move\");\n                }\n                catch (InterruptedException e)\n                {\n                    return continuable();\n                }\n                catch (ExecutionException e)\n                {\n                    StorageService.instance.markMoveFailed();\n                    throw new RuntimeException(\"Unable to move\", e);","sourceCodeStart":339,"sourceCodeEnd":375,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/tcm/sequences/Move.java#L339-L375","documentation":"During Move.executeNext, when building the streaming plan, every destination endpoint in the movement map must appear either as a source or a destination for the streamed ranges. If a node is in the movement map but plays neither role for the given range, the code throws IllegalStateException, indicating the computed movement map is inconsistent. It is an internal invariant failure in range/endpoint calculation.","triggerScenarios":"executeNext() streaming step encounters a movement-map entry whose endpoint is neither listed in sources nor as destination for a range; typically caused by inconsistent token metadata, a concurrent topology change mid-move, or a bug in movementMap computation.","commonSituations":"nodetool move on a node while another topology operation (bootstrap/decommission) is in flight; corrupted replication metadata after a failed earlier move; running move on a cluster with mismatched replication factor settings.","solutions":["Ensure no other topology operations are running and retry the move after the cluster is quiescent","Verify token metadata consistency (nodetool status, describecluster) before moving","Cancel/roll back the stuck move sequence via the TCM cancel path, then re-run","If reproducible on a quiescent cluster, capture the movement map and file a Cassandra bug"],"exampleFix":"// before\nstreamPlan.requestRanges(rangesFor(destination.endpoint()), rangesFor(destination.endpoint()));\n// after\nboolean isSource = rangesFor(destination.endpoint()).contains(range);\nboolean isDest = destination.endpoint().equals(destination.endpoint());\nif (!isSource && !isDest)\n    throw new IllegalStateException(\"Node should be either source or destination in the movement map \" + endpoints); // keep guard, but validate movementMap upstream","handlingStrategy":"validation","validationCode":"if (ClusterMetadata.current().inProgressSequences.size() > 0)\n    throw new IllegalStateException(\"Wait for in-progress topology operations to finish before nodetool move\");","typeGuard":null,"tryCatchPattern":"try { executeMove(); } catch (IllegalStateException e) { logger.error(\"Move plan inconsistent: {}\", e.getMessage()); markMoveFailed(); alertOperator(); }","preventionTips":["Run only one topology operation at a time","Verify token metadata consistency before moving","Roll back and re-run a move after any failed attempt rather than layering operations"],"tags":["illegal-state","move","streaming","topology"],"backgroundTag":"internal-invariant-violation","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}