{"record":{"id":"f9710f98a1c18faa","repo":"TechnitiumSoftware/DnsServer","slug":"failed-to-delete-cluster-please-remove-all-second","errorCode":null,"errorMessage":"Failed to delete Cluster: please remove all Secondary nodes before deleting the Cluster.","messagePattern":"Failed to delete Cluster: please remove all Secondary nodes before deleting the Cluster\\.","errorType":"exception","errorClass":"DnsServerException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Cluster/ClusterManager.cs","lineNumber":678,"sourceCode":"            //finalize\n            _dnsWebService.DnsServer.ServerDomain = selfPrimaryNode.Name;\n\n            //save all changes\n            _dnsWebService.DnsServer.SaveConfigFile(true);\n            _dnsWebService.AuthManager.SaveConfigFile(true);\n            SaveConfigFile();\n        }\n\n        public void DeleteCluster(bool forceDelete)\n        {\n            if (!ClusterInitialized)\n                throw new DnsServerException(\"Failed to delete Cluster: the Cluster is not initialized.\");\n\n            if (GetSelfNode().Type != ClusterNodeType.Primary)\n                throw new DnsServerException(\"Failed to delete Cluster: only a Primary node can delete the Cluster.\");\n\n            if (!forceDelete && (_clusterNodes.Count > 1))\n                throw new DnsServerException(\"Failed to delete Cluster: please remove all Secondary nodes before deleting the Cluster.\");\n\n            DeleteAllClusterConfig();\n        }\n\n        public ClusterNode JoinCluster(int secondaryNodeId, Uri secondaryNodeUrl, IReadOnlyList<IPAddress> secondaryNodeIpAddresses, X509Certificate2 secondaryNodeCertificate)\n        {\n            if (!ClusterInitialized)\n                throw new DnsServerException(\"Failed to add Secondary node: the Cluster is not initialized.\");\n\n            if (GetSelfNode().Type != ClusterNodeType.Primary)\n                throw new DnsServerException(\"Failed to add Secondary node: only a Primary node can add a Secondary node to the Cluster.\");\n\n            string secondaryNodeDomain = secondaryNodeUrl.Host.ToLowerInvariant();\n\n            if (!secondaryNodeDomain.EndsWith(\".\" + _clusterDomain, StringComparison.OrdinalIgnoreCase))\n                throw new DnsServerException(\"Failed to add Secondary node: the Secondary node domain name must be a subdomain of the Cluster domain name.\");\n\n            IReadOnlyDictionary<int, ClusterNode> existingClusterNodes = _clusterNodes;","sourceCodeStart":660,"sourceCodeEnd":696,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Cluster/ClusterManager.cs#L660-L696","documentation":"Thrown by DeleteCluster when forceDelete is false and more than one node exists in the cluster (_clusterNodes.Count > 1). This safety guard prevents orphaning Secondary nodes that would lose their Primary and be left in an inconsistent state. The Primary node alone counts as 1, so any Secondary pushes the count above the threshold.","triggerScenarios":"DeleteCluster(false) is called when _clusterNodes contains the Primary plus one or more Secondary nodes. The guard at line 677 fires after the ClusterInitialized and Primary-node checks pass.","commonSituations":"Attempting a cluster teardown without first draining Secondary nodes; a scripted cleanup that skips the node-removal step.","solutions":["Remove all Secondary nodes first using DeleteSecondaryNode or AskSecondaryNodeToLeaveClusterAsync, then call DeleteCluster(false).","If you intentionally want to force teardown with Secondaries still present, call DeleteCluster(true) to bypass the guard."],"exampleFix":"// before\n_dnsWebService.ClusterManager.DeleteCluster(false);\n// after — drain secondaries first\nforeach (var node in _dnsWebService.ClusterManager.ClusterNodes)\n{\n    if (node.Value.Type == ClusterNodeType.Secondary)\n        _dnsWebService.ClusterManager.DeleteSecondaryNode(node.Key);\n}\n_dnsWebService.ClusterManager.DeleteCluster(false);","handlingStrategy":"validation","validationCode":"// Drain all secondaries before deleting, or force-delete\nforeach (var node in _dnsWebService.ClusterManager.ClusterNodes)\n{\n    if (node.Value.Type == ClusterNodeType.Secondary)\n        _dnsWebService.ClusterManager.DeleteSecondaryNode(node.Key);\n}\n_dnsWebService.ClusterManager.DeleteCluster(false);\n// or: _dnsWebService.ClusterManager.DeleteCluster(true); // force","typeGuard":"static bool IsClusterDrained(ClusterManager cm)\n    => cm.ClusterNodes.Count(n => n.Value.Type == ClusterNodeType.Secondary) == 0;","tryCatchPattern":"try\n{\n    _dnsWebService.ClusterManager.DeleteCluster(false);\n}\ncatch (DnsServerException ex) when (ex.Message.Contains(\"remove all Secondary nodes\"))\n{\n    // either drain secondaries and retry, or call DeleteCluster(true) to force\n    throw;\n}","preventionTips":["Always drain Secondary nodes before cluster teardown unless force-delete is intentional.","Build a teardown script that removes secondaries in a loop, then deletes the cluster.","Document the forceDelete flag clearly so operators know the trade-off."],"tags":["cluster","delete","secondary-nodes","safety-guard","drain"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}