{"record":{"id":"124ddb44fa4bb799","repo":"TechnitiumSoftware/DnsServer","slug":"failed-to-ask-secondary-node-to-leave-only-a-prim","errorCode":null,"errorMessage":"Failed to ask Secondary node to leave: only a Primary node can ask a Secondary node to leave the Cluster.","messagePattern":"Failed to ask Secondary node to leave: only a Primary node can ask a Secondary node to leave the Cluster\\.","errorType":"exception","errorClass":"DnsServerException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Cluster/ClusterManager.cs","lineNumber":749,"sourceCode":"            //save all changes\n            SaveConfigFile();\n\n            //notify all secondary nodes\n            TriggerNotifyAllSecondaryNodes();\n\n            //trigger NS and SOA update for member zones\n            TriggerRecordUpdateForClusterCatalogMemberZones();\n\n            return secondaryNode;\n        }\n\n        public async Task<ClusterNode> AskSecondaryNodeToLeaveClusterAsync(int secondaryNodeId)\n        {\n            if (!ClusterInitialized)\n                throw new DnsServerException(\"Failed to ask Secondary node to leave: the Cluster is not initialized.\");\n\n            if (GetSelfNode().Type != ClusterNodeType.Primary)\n                throw new DnsServerException(\"Failed to ask Secondary node to leave: only a Primary node can ask a Secondary node to leave the Cluster.\");\n\n            //find existing secondary node\n            if (!_clusterNodes.TryGetValue(secondaryNodeId, out ClusterNode secondaryNode))\n                throw new DnsServerException(\"Failed to ask Secondary node to leave: the specified node does not exist in the Cluster.\");\n\n            if (secondaryNode.Type == ClusterNodeType.Primary)\n                throw new DnsServerException(\"Failed to ask Secondary node to leave: the specified node is the Cluster Primary node and cannot be removed.\");\n\n            //ask secondary node to leave the cluster\n            await secondaryNode.AskSecondaryNodeToLeaveClusterAsync();\n\n            return secondaryNode;\n        }\n\n        public ClusterNode DeleteSecondaryNode(int secondaryNodeId)\n        {\n            if (!ClusterInitialized)\n                throw new DnsServerException(\"Failed to delete Secondary node: the Cluster is not initialized.\");","sourceCodeStart":731,"sourceCodeEnd":767,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Cluster/ClusterManager.cs#L731-L767","documentation":"Thrown by AskSecondaryNodeToLeaveClusterAsync when the current server's self node is not the Primary node. Only the Primary can instruct a Secondary to gracefully leave, because the leave handshake involves the Primary updating its node list, zone records, and catalog zone.","triggerScenarios":"AskSecondaryNodeToLeaveClusterAsync is called from a Secondary node. The guard at line 748 fires after the ClusterInitialized check.","commonSituations":"The leave API request is sent to a Secondary instead of the Primary; wrong server targeted in a multi-node setup.","solutions":["Run AskSecondaryNodeToLeaveClusterAsync on the cluster Primary node.","Verify GetSelfNode().Type == ClusterNodeType.Primary before calling."],"exampleFix":"// before\nawait _clusterManager.AskSecondaryNodeToLeaveClusterAsync(nodeId);\n// after\nif (_clusterManager.GetSelfNode().Type != ClusterNodeType.Primary)\n    throw new InvalidOperationException(\"Run on the Primary node.\");\nawait _clusterManager.AskSecondaryNodeToLeaveClusterAsync(nodeId);","handlingStrategy":"validation","validationCode":"if (_dnsWebService.ClusterManager.GetSelfNode().Type != ClusterNodeType.Primary)\n    throw new InvalidOperationException(\"AskSecondaryNodeToLeaveClusterAsync must run on the Primary node.\");\nawait _dnsWebService.ClusterManager.AskSecondaryNodeToLeaveClusterAsync(nodeId);","typeGuard":"static bool IsPrimaryNode(ClusterManager cm)\n    => cm.ClusterInitialized && cm.GetSelfNode().Type == ClusterNodeType.Primary;","tryCatchPattern":"try\n{\n    await _clusterManager.AskSecondaryNodeToLeaveClusterAsync(nodeId);\n}\ncatch (DnsServerException ex) when (ex.Message.Contains(\"only a Primary node can ask\"))\n{\n    throw new InvalidOperationException(\"Redirect this request to the cluster Primary node.\", ex);\n}","preventionTips":["Route all leave requests to the Primary node.","Check GetSelfNode().Type before calling node-removal methods.","Pin cluster-management endpoints to the Primary in load-balanced setups."],"tags":["cluster","authorization","primary-node","leave","rbac"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}