Skip to content

Commit 62b3d71

Browse files
committed
Add an extra init wait for single node clusters
1 parent 23f8095 commit 62b3d71

File tree

4 files changed

+84
-4
lines changed

4 files changed

+84
-4
lines changed

src/Couchbase.Aspire.Hosting/Api/CouchbaseApi.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,27 @@ public async Task AddNodeAsync(CouchbaseServerResource server, string hostname,
129129
};
130130

131131
var response = await SendRequestAsync(server.GetManagementEndpoint(),
132-
HttpMethod.Post,
133-
"/controller/addNode",
134-
new FormUrlEncodedContent(parameters),
135-
cancellationToken: cancellationToken).ConfigureAwait(false);
132+
HttpMethod.Post,
133+
"/controller/addNode",
134+
new FormUrlEncodedContent(parameters),
135+
cancellationToken: cancellationToken).ConfigureAwait(false);
136+
137+
await ThrowOnFailureAsync(response, cancellationToken).ConfigureAwait(false);
138+
}
139+
140+
public async Task<NodeServices> GetNodeServicesAsync(CouchbaseServerResource server, CancellationToken cancellationToken = default)
141+
{
142+
ArgumentNullException.ThrowIfNull(server);
143+
144+
var response = await SendRequestAsync(server.GetManagementEndpoint(),
145+
HttpMethod.Get,
146+
"pools/default/nodeServices",
147+
cancellationToken: cancellationToken).ConfigureAwait(false);
136148

137149
await ThrowOnFailureAsync(response, cancellationToken).ConfigureAwait(false);
150+
151+
var nodeServicesResponse = await response.Content.ReadFromJsonAsync<NodeServicesResponse>(cancellationToken).ConfigureAwait(false);
152+
return nodeServicesResponse!.NodesExt.First(p => p.ThisNode);
138153
}
139154

140155
public async Task SetupAlternateAddressesAsync(CouchbaseServerResource server, string hostname, Dictionary<string, string> ports,

src/Couchbase.Aspire.Hosting/Api/ICouchbaseApi.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ internal interface ICouchbaseApi
99
Task<Bucket?> GetBucketAsync(CouchbaseServerResource server, string bucketName, CancellationToken cancellationToken);
1010
Task FlushBucketAsync(CouchbaseServerResource server, string bucketName, CancellationToken cancellationToken);
1111
Task<Pool> GetClusterNodesAsync(CouchbaseServerResource server, CancellationToken cancellationToken = default);
12+
Task<NodeServices> GetNodeServicesAsync(CouchbaseServerResource server, CancellationToken cancellationToken = default);
1213
Task<bool> GetDefaultPoolAsync(CouchbaseServerResource server, bool preferInsecure = false, CancellationToken cancellationToken = default);
1314
Task<RebalanceStatus> GetRebalanceProgressAsync(CouchbaseServerResource server, CancellationToken cancellationToken = default);
1415
Task InitializeClusterAsync(CouchbaseServerResource server, CouchbaseClusterSettings settings, CancellationToken cancellationToken = default);

src/Couchbase.Aspire.Hosting/Api/Pool.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,21 @@ internal sealed class Node
1313
[JsonPropertyName("hostname")]
1414
public string Hostname { get; set; } = null!;
1515
}
16+
17+
internal sealed class NodeServices
18+
{
19+
[JsonPropertyName("hostname")]
20+
public string Hostname { get; set; } = null!;
21+
22+
[JsonPropertyName("services")]
23+
public Dictionary<string, int>? Services { get; set; }
24+
25+
[JsonPropertyName("thisNode")]
26+
public bool ThisNode { get; set; }
27+
}
28+
29+
internal sealed class NodeServicesResponse
30+
{
31+
[JsonPropertyName("nodesExt")]
32+
public List<NodeServices> NodesExt { get; set; } = null!;
33+
}

src/Couchbase.Aspire.Hosting/Orchestration/CouchbaseClusterOrchestrator.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,14 @@ await _resourceNotificationService.PublishUpdateAsync(cluster, s => s with
382382
await RebalanceAsync(api, primaryServer, cancellationToken).ConfigureAwait(false);
383383
}
384384
}
385+
else
386+
{
387+
// A single node cluster may not have all services come online immediately, which can result in
388+
// health checks that never pass when the SDK bootstraps with a subset of services. Wait for
389+
// all services to appear in the ports list
390+
391+
await WaitForServicesAsync(api, primaryServer, cancellationToken).ConfigureAwait(false);
392+
}
385393

386394
await _orchestratorEvents.PublishAsync(new OnCouchbaseResourceStartedEvent(cluster), cancellationToken).ConfigureAwait(false);
387395

@@ -453,6 +461,44 @@ public async Task InitializeClusterAsync(ICouchbaseApi api, CouchbaseServerResou
453461
await api.InitializeClusterAsync(primaryServer, settings, cancellationToken).ConfigureAwait(false);
454462
}
455463

464+
public async Task WaitForServicesAsync(ICouchbaseApi api, CouchbaseServerResource server, CancellationToken cancellationToken = default)
465+
{
466+
while (true)
467+
{
468+
cancellationToken.ThrowIfCancellationRequested();
469+
470+
if (!server.TryGetEndpoints(out var endpoints))
471+
{
472+
throw new InvalidOperationException("Failed to get node endpoints.");
473+
}
474+
475+
var node = await api.GetNodeServicesAsync(server, cancellationToken).ConfigureAwait(false);
476+
477+
if (node.Services is not null)
478+
{
479+
var allServicesFound = true;
480+
foreach (var endpoint in endpoints)
481+
{
482+
if (CouchbaseEndpointNames.EndpointNameServiceMappings.TryGetValue(endpoint.Name, out var serviceName))
483+
{
484+
if (!node.Services.ContainsKey(serviceName))
485+
{
486+
allServicesFound = false;
487+
break;
488+
}
489+
}
490+
}
491+
492+
if (allServicesFound)
493+
{
494+
return;
495+
}
496+
}
497+
498+
await Task.Delay(250, cancellationToken).ConfigureAwait(false);
499+
}
500+
}
501+
456502
/// <returns><c>true</c> if the node was added, <c>false</c> if it is already part of the cluster.</returns>
457503
private async Task<bool> AddNodeAsync(ICouchbaseApi api, CouchbaseServerResource primaryServer, CouchbaseServerResource addServer,
458504
List<string> existingNodes, CancellationToken cancellationToken)

0 commit comments

Comments
 (0)