{"record":{"id":"911abb94977cdab9","repo":"neondatabase/neon","slug":"there-are-no-nodes-to-migrate-to","errorCode":null,"errorMessage":"There are no nodes to migrate to","messagePattern":"There are no nodes to migrate to","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"control_plane/storcon_cli/src/main.rs","lineNumber":1095,"sourceCode":"                } else {\n                    node_to_fill_descs.push(desc);\n                }\n            }\n\n            if nodes.len() != node_to_drain_descs.len() {\n                anyhow::bail!(\"Bulk migration requested away from node which doesn't exist.\")\n            }\n\n            node_to_fill_descs.retain(|desc| {\n                matches!(desc.availability, NodeAvailabilityWrapper::Active)\n                    && matches!(\n                        desc.scheduling,\n                        NodeSchedulingPolicy::Active | NodeSchedulingPolicy::Filling\n                    )\n            });\n\n            if node_to_fill_descs.is_empty() {\n                anyhow::bail!(\"There are no nodes to migrate to\")\n            }\n\n            // Set the node scheduling policy to draining for the nodes which\n            // we plan to drain.\n            for node_desc in node_to_drain_descs.iter() {\n                let req = NodeConfigureRequest {\n                    node_id: node_desc.id,\n                    availability: None,\n                    scheduling: Some(NodeSchedulingPolicy::Draining),\n                };\n\n                storcon_client\n                    .dispatch::<_, ()>(\n                        Method::PUT,\n                        format!(\"control/v1/node/{}/config\", node_desc.id),\n                        Some(req),\n                    )\n                    .await?;","sourceCodeStart":1077,"sourceCodeEnd":1113,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/control_plane/storcon_cli/src/main.rs#L1077-L1113","documentation":"After partitioning nodes into drain and fill sets, storcon_cli retains only fill nodes whose availability is Active and whose scheduling policy is Active or Filling — i.e. nodes the scheduler may place shards onto. If that filtered set is empty, there is nowhere to migrate shards to and the command bails before setting any node to Draining.","triggerScenarios":"Running bulk migration when every non-drained node is either Offline, or has scheduling policy Pause/Stop/Essential/Draining (not Active or Filling). Can also happen when all existing nodes are in the drain set, leaving zero fill candidates.","commonSituations":"Draining nodes in a small cluster where the remaining nodes were paused for maintenance; node availability stuck Offline after an outage; scheduling policies left in Pause/Stop from earlier operations; attempting to drain every node in the cluster.","solutions":["List nodes and their availability/scheduling; set at least one non-drain node to availability active and scheduling active (or filling)","Unpause/unstop a candidate fill node (its scheduling must be Active or Filling) and retry","Add or restore a healthy node before draining, or reduce the drain list so a fill node remains"],"exampleFix":"# before: all other nodes are paused\nstorcon_cli tenant migrate-bulk --drain-nodes 1,2\n# after: make node 3 fillable first\nstorcon_cli node configure --node-id 3 --scheduling active\nstorcon_cli tenant migrate-bulk --drain-nodes 1,2","handlingStrategy":"validation","validationCode":"// Reproduce the CLI's fill-node filter client-side and fail early with context\nlet fill_nodes: Vec<_> = nodes_list.into_iter()\n    .filter(|n| !drain_set.contains(&n.id))\n    .filter(|n| matches!(n.availability, NodeAvailabilityWrapper::Active)\n        && matches!(n.scheduling, NodeSchedulingPolicy::Active | NodeSchedulingPolicy::Filling))\n    .collect();\nanyhow::ensure!(!fill_nodes.is_empty(),\n    \"no fillable nodes: set a node to availability=active, scheduling=active before draining\");","typeGuard":"fn is_fill_candidate(desc: &NodeDescribeResponse) -> bool {\n    matches!(desc.availability, NodeAvailabilityWrapper::Active)\n        && matches!(desc.scheduling, NodeSchedulingPolicy::Active | NodeSchedulingPolicy::Filling)\n}","tryCatchPattern":null,"preventionTips":["Before any drain, confirm at least one non-drain node has availability active and scheduling active/filling","Keep spare capacity: never drain when the remaining fleet is paused/offline","Automate a pre-drain capacity check that mirrors this exact filter"],"tags":["storcon","migration","capacity","node-scheduling","cli"],"backgroundTag":"no-available-nodes","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}