{"record":{"id":"284d7b39eebbe61c","repo":"apache/hadoop","slug":"failed-to-rename-s-to-s-s-is-a-file","errorCode":null,"errorMessage":"Failed to rename %s to %s, %s is a file","messagePattern":"Failed to rename (.+?) to (.+?), (.+?) is a file","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"hadoop-cloud-storage-project/hadoop-bos/src/main/java/org/apache/hadoop/fs/bos/BaiduBosFileSystem.java","lineNumber":776,"sourceCode":"\n    FileStatus dstStatus;\n    try {\n      dstStatus = getFileStatus(dstPath);\n    } catch (FileNotFoundException fnde) {\n      dstStatus = null;\n    }\n\n    if (dstStatus == null) {\n      Path dstParent = dstPath.getParent();\n      if (dstParent != null) {\n        Path currentPath = dstParent;\n        while (currentPath != null\n            && !currentPath.isRoot()) {\n          FileStatus ancestorStatus;\n          try {\n            ancestorStatus = getFileStatus(currentPath);\n            if (!ancestorStatus.isDirectory()) {\n              throw new IOException(String.format(\n                  \"Failed to rename %s to %s, %s is a file\",\n                  srcPath, dstPath, currentPath));\n            }\n            break;\n          } catch (FileNotFoundException fnde) {\n            currentPath = currentPath.getParent();\n          }\n        }\n      }\n      LOG.debug(\"Parent directory {} does not exist \"\n          + \"or will be implicitly created during rename\",\n          dstPath.getParent());\n    } else {\n      if (dstStatus.isDirectory()) {\n        dstPath = new Path(dstPath, srcPath.getName());\n        FileStatus status;\n        try {\n          status = getFileStatus(dstPath);","sourceCodeStart":758,"sourceCodeEnd":794,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-cloud-storage-project/hadoop-bos/src/main/java/org/apache/hadoop/fs/bos/BaiduBosFileSystem.java#L758-L794","documentation":"ZombieCluster.buildCluster() walks the LoggedNetworkTopology tree and requires every leaf to sit at the same depth; the first time it finds a leaf at a different depth than an earlier leaf it throws IllegalArgumentException. Depth matters because rumen hard-codes the bottom two levels as RackNode -> MachineNode (see 'path[level-1].addChild(current)' and the RackNode/MachineNode split in the second pass), so an unbalanced tree would misassign machine/rack roles. This is a data-quality contract on the topology JSON.","triggerScenarios":"new ZombieCluster(topology, defaultNode) where one branch of the topology ends at depth 1 and another at depth 2 — e.g. one rack entry has bare host children while another has an intermediate switch layer; feeding ClusterTopologyReader a topology file with a stray empty node; hand-authored topology JSON with inconsistent nesting.","commonSituations":"Editing a rumen topology JSON by hand and forgetting a level; combining topology dumps from different cluster generations; clusters modeled with per-rack switch hierarchies only in part of the datacenter.","solutions":["Normalize the topology JSON so every path from root to leaf has the same number of levels (standard: root -> rack -> host).","Locate the imbalance by printing each leaf's depth from the LoggedNetworkTopology tree before constructing ZombieCluster (a 10-line DFS makes the offending branch obvious).","Remove empty/placeholder children entries — an entry with no children is a leaf and any depth mismatch counts.","For genuinely deeper topologies, flatten intermediate switches into rack names (rack/switch naming) to keep the two-level rack/host model."],"exampleFix":"// before (topology.json): unbalanced\n// { \"name\":\"/dc\", \"children\":[\n//   { \"name\":\"/r1\", \"children\":[ {\"name\":\"h1\"} ] },\n//   { \"name\":\"/r2\", \"children\":[\n//     { \"name\":\"/sw\", \"children\":[ {\"name\":\"h2\"} ] } ] } ] }\nnew ZombieCluster(topology, defaultNode); // IllegalArgumentException\n\n// after (topology.json): balanced root -> rack -> host\n// { \"name\":\"/dc\", \"children\":[\n//   { \"name\":\"/r1\", \"children\":[ {\"name\":\"h1\"} ] },\n//   { \"name\":\"/r2\", \"children\":[ {\"name\":\"h2\"} ] } ] }\nnew ZombieCluster(topology, defaultNode);","handlingStrategy":"validation","validationCode":"// Verify all leaves share one depth BEFORE building the ZombieCluster\nstatic int checkBalanced(LoggedNetworkTopology n, int depth, Integer leafDepth,\n    java.util.List<String> badLeaves) {\n  List<LoggedNetworkTopology> kids = n.getChildren();\n  if (kids == null || kids.isEmpty()) {\n    if (leafDepth != null && leafDepth != depth) badLeaves.add(n.getName().getValue());\n    return leafDepth == null ? depth : leafDepth;\n  }\n  for (LoggedNetworkTopology c : kids) leafDepth = checkBalanced(c, depth + 1, leafDepth, badLeaves);\n  return leafDepth;\n}\n// if (!badLeaves.isEmpty()) fail with the offending leaf names","typeGuard":null,"tryCatchPattern":"try {\n  cluster = new ZombieCluster(topology, defaultNode);\n} catch (IllegalArgumentException e) {\n  if (\"Leaf nodes are not on the same level\".equals(e.getMessage())) {\n    throw new IOException(\"Topology file has unbalanced depth; normalize to root/rack/host\", e);\n  }\n  throw e;\n}","preventionTips":["Keep topology JSONs at a fixed three-layer shape: root -> rack -> host.","Automate a balance check in your topology-export pipeline.","Watch for empty children arrays: they create accidental leaves."],"tags":["hadoop","rumen","cluster-topology","tree-depth","data-validation"],"backgroundTag":"uneven-tree-depth","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}