TechnitiumSoftware/DnsServer · error · DnsServerException

Failed to ask Secondary node to leave: the specified node is

Error message

Failed to ask Secondary node to leave: the specified node is the Cluster Primary node and cannot be removed.

What it means

Thrown by AskSecondaryNodeToLeaveClusterAsync when the node resolved by secondaryNodeId has Type == ClusterNodeType.Primary. The Primary node cannot be asked to leave — it is the authoritative owner of the cluster and must be removed via DeleteCluster instead. This guard runs after the node-exists check.

Source

Thrown at DnsServerCore/Cluster/ClusterManager.cs:756

            TriggerRecordUpdateForClusterCatalogMemberZones();

            return secondaryNode;
        }

        public async Task<ClusterNode> AskSecondaryNodeToLeaveClusterAsync(int secondaryNodeId)
        {
            if (!ClusterInitialized)
                throw new DnsServerException("Failed to ask Secondary node to leave: the Cluster is not initialized.");

            if (GetSelfNode().Type != ClusterNodeType.Primary)
                throw new DnsServerException("Failed to ask Secondary node to leave: only a Primary node can ask a Secondary node to leave the Cluster.");

            //find existing secondary node
            if (!_clusterNodes.TryGetValue(secondaryNodeId, out ClusterNode secondaryNode))
                throw new DnsServerException("Failed to ask Secondary node to leave: the specified node does not exist in the Cluster.");

            if (secondaryNode.Type == ClusterNodeType.Primary)
                throw new DnsServerException("Failed to ask Secondary node to leave: the specified node is the Cluster Primary node and cannot be removed.");

            //ask secondary node to leave the cluster
            await secondaryNode.AskSecondaryNodeToLeaveClusterAsync();

            return secondaryNode;
        }

        public ClusterNode DeleteSecondaryNode(int secondaryNodeId)
        {
            if (!ClusterInitialized)
                throw new DnsServerException("Failed to delete Secondary node: the Cluster is not initialized.");

            if (GetSelfNode().Type != ClusterNodeType.Primary)
                throw new DnsServerException("Failed to delete Secondary node: only a Primary node can delete a Secondary node from the Cluster.");

            //find existing secondary node
            IReadOnlyDictionary<int, ClusterNode> existingClusterNodes = _clusterNodes;

View on GitHub (pinned to d0484b6c1e)

Solutions

  1. Pass a Secondary node's ID — use the node list to find one with Type == ClusterNodeType.Secondary.
  2. If you want to remove the Primary, use DeleteCluster instead.
  3. Filter the target node by type before calling: ensure secondaryNode.Type != ClusterNodeType.Primary.

Example fix

// before
await _clusterManager.AskSecondaryNodeToLeaveClusterAsync(primaryNodeId);
// after — target a Secondary, not the Primary
var target = _clusterManager.ClusterNodes.First(n => n.Value.Type == ClusterNodeType.Secondary);
await _clusterManager.AskSecondaryNodeToLeaveClusterAsync(target.Key);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the target is a Secondary, not the Primary
var target = _dnsWebService.ClusterManager.ClusterNodes
    .FirstOrDefault(n => n.Key == nodeId);
if (target.Value is null)
    throw new InvalidOperationException("Node not found.");
if (target.Value.Type == ClusterNodeType.Primary)
    throw new InvalidOperationException("Cannot ask the Primary to leave; use DeleteCluster instead.");
await _dnsWebService.ClusterManager.AskSecondaryNodeToLeaveClusterAsync(nodeId);

Type guard

static bool IsSecondaryNode(ClusterManager cm, int nodeId)
    => cm.ClusterNodes.TryGetValue(nodeId, out var node) && node.Type == ClusterNodeType.Secondary;

Try / catch

try
{
    await _clusterManager.AskSecondaryNodeToLeaveClusterAsync(nodeId);
}
catch (DnsServerException ex) when (ex.Message.Contains("Cluster Primary node and cannot be removed"))
{
    throw new InvalidOperationException("The target is the Primary node. Use DeleteCluster to remove the entire cluster.", ex);
}

Prevention

When it happens

Trigger: AskSecondaryNodeToLeaveClusterAsync is called with the Primary node's ID. The lookup at line 752 succeeds but the type check at line 755 (secondaryNode.Type == ClusterNodeType.Primary) fires.

Common situations: Client or UI passes the Primary node's ID by mistake; iterating over all node IDs and calling leave without filtering by type; confusion between leave (for Secondaries) and delete (for the whole cluster).

Related errors


AI-assisted analysis of TechnitiumSoftware/DnsServer@d0484b6c1e (2026-08-13). Data as JSON: /api/errors/c9021ad95532d18f. Report an issue: GitHub.