From 6881e5c53cb2b596dd015d835e2867a47fcc65c1 Mon Sep 17 00:00:00 2001 From: "goodolclint-claude[bot]" <323206664+goodolclint-claude[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 06:20:55 +0000 Subject: [PATCH] feat: add ClusterConfigService.WaitForQuorum Polls GET /cluster/status for the cluster entry with quorate = 1, bounded by a timeout (60 s default) and tolerating PveApiException while pmxcfs and corosync restart during cluster formation. Co-Authored-By: Claude Opus 5 (1M context) --- .../Services/ClusterConfigService.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/PSProxmoxVE.Core/Services/ClusterConfigService.cs b/src/PSProxmoxVE.Core/Services/ClusterConfigService.cs index a66f603..e288816 100644 --- a/src/PSProxmoxVE.Core/Services/ClusterConfigService.cs +++ b/src/PSProxmoxVE.Core/Services/ClusterConfigService.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Threading; using Newtonsoft.Json.Linq; using PSProxmoxVE.Core.Authentication; using PSProxmoxVE.Core.Client; +using PSProxmoxVE.Core.Exceptions; using PSProxmoxVE.Core.Models.Cluster; using PSProxmoxVE.Core.Utilities; @@ -16,6 +18,9 @@ namespace PSProxmoxVE.Core.Services { private readonly IPveHttpClient? _injectedClient; + private static readonly TimeSpan DefaultQuorumTimeout = TimeSpan.FromSeconds(60); + private static readonly TimeSpan QuorumPollInterval = TimeSpan.FromSeconds(2); + /// /// Initializes a new instance of the class. /// @@ -378,6 +383,48 @@ namespace PSProxmoxVE.Core.Services } } + /// + /// Blocks until the cluster reports quorum (GET /cluster/status, quorate = 1). + /// + /// The authenticated PVE session. + /// Maximum time to wait. Defaults to 60 seconds. + /// + /// The cluster-create task completes before corosync converges; until the node + /// is quorate it rejects joins with "cluster not ready - no quorum?". API errors + /// during that window are transient and are retried until the deadline. + /// + /// Quorum was not reached before the deadline. + public void WaitForQuorum(PveSession session, TimeSpan? timeout = null) + { + if (session == null) throw new ArgumentNullException(nameof(session)); + + var effectiveTimeout = timeout ?? DefaultQuorumTimeout; + var deadline = DateTime.UtcNow.Add(effectiveTimeout); + + while (true) + { + try + { + foreach (var entry in GetClusterStatus(session)) + { + if (string.Equals(entry.Type, "cluster", StringComparison.OrdinalIgnoreCase) + && entry.Quorate == 1) + return; + } + } + catch (PveApiException) + { + // pmxcfs and corosync restart while the cluster forms. + } + + if (DateTime.UtcNow >= deadline) + throw new TimeoutException( + $"Cluster did not reach quorum within {effectiveTimeout.TotalSeconds:0} seconds."); + + Thread.Sleep(QuorumPollInterval); + } + } + /// /// Returns the next available VM/CT ID (GET /cluster/nextid). ///