Skip to content

Commit 7d0e1f2

Browse files
committed
JCBC-2198, KCBC-192, SCBC-490: Operational SDK prevent connection to Analytics 2.0 Cluster
Use the prodName field introduced into the global and bucket topologies, to prevent the operational SDK performing operations against an Enterprise Analytics cluster. Change-Id: I945a7934b83e346dc6eb63ab4e756f1964fce07a Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/229751 Reviewed-by: Graham Pople <[email protected]> Tested-by: Graham Pople <[email protected]>
1 parent c135121 commit 7d0e1f2

File tree

4 files changed

+65
-8
lines changed

4 files changed

+65
-8
lines changed

core-io/src/main/java/com/couchbase/client/core/topology/ClusterIdentifier.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.couchbase.client.core.topology;
1717

18+
import com.couchbase.client.core.annotation.SinceCouchbase;
1819
import com.couchbase.client.core.annotation.Stability;
1920
import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.JsonNode;
2021
import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.node.ObjectNode;
@@ -26,19 +27,23 @@
2627
public class ClusterIdentifier {
2728
private final String clusterUuid;
2829
private final String clusterName;
30+
@SinceCouchbase("8.0")
31+
private final @Nullable String prodName;
2932

30-
ClusterIdentifier(String clusterUuid, String clusterName) {
33+
ClusterIdentifier(String clusterUuid, String clusterName, @Nullable String prodName) {
3134
this.clusterUuid = clusterUuid;
3235
this.clusterName = clusterName;
36+
this.prodName = prodName;
3337
}
3438

3539
public static @Nullable ClusterIdentifier parse(ObjectNode config) {
3640
JsonNode clusterUuid = config.path("clusterUUID");
3741
JsonNode clusterName = config.path("clusterName");
42+
JsonNode prodName = config.path("prodName");
3843
if (clusterUuid.isMissingNode() || clusterName.isMissingNode()) {
3944
return null;
4045
}
41-
return new ClusterIdentifier(clusterUuid.asText(), clusterName.asText());
46+
return new ClusterIdentifier(clusterUuid.asText(), clusterName.asText(), prodName.isMissingNode() ? null : prodName.asText());
4247
}
4348

4449
public String clusterUuid() {
@@ -49,11 +54,16 @@ public String clusterName() {
4954
return clusterName;
5055
}
5156

57+
public @Nullable String prodName() {
58+
return prodName;
59+
}
60+
5261
@Override
5362
public String toString() {
5463
return "ClusterIdent{" +
5564
"clusterUuid='" + clusterUuid + '\'' +
5665
", clusterName='" + redactMeta(clusterName) + '\'' +
66+
", prodName='" + prodName + '\'' +
5767
'}';
5868
}
5969
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2025 Couchbase, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.couchbase.client.core.topology;
17+
18+
import com.couchbase.client.core.annotation.Stability;
19+
20+
// Known values for the prodName cluster field added in MB-67103
21+
@Stability.Internal
22+
public class ClusterProdName {
23+
private ClusterProdName() {}
24+
25+
public static final String COUCHBASE_SERVER = "Couchbase Server";
26+
public static final String ENTERPRISE_ANALYTICS = "Enterprise Analytics";
27+
}

core-io/src/main/java/com/couchbase/client/core/topology/ClusterTopologyBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public final class ClusterTopologyBuilder {
4444
private NetworkResolution networkResolution = NetworkResolution.DEFAULT;
4545
private String clusterName = "fake-cluster";
4646
private String clusterUuid = "fake-cluster-uuid";
47+
private String prodName = ClusterProdName.COUCHBASE_SERVER;
4748
private Set<ClusterCapability> capabilities = EnumSet.allOf(ClusterCapability.class);
4849
private final List<HostAndServicePorts> nodes = new ArrayList<>();
4950

@@ -54,7 +55,7 @@ public ClusterTopology build() {
5455
private ClusterTopology buildWithOrWithoutBucket(@Nullable BucketTopology bucket) {
5556
return ClusterTopology.of(
5657
revision,
57-
new ClusterIdentifier(clusterUuid, clusterName),
58+
new ClusterIdentifier(clusterUuid, clusterName, prodName),
5859
nodes,
5960
capabilities,
6061
networkResolution,

java-client/src/main/java/com/couchbase/client/java/analytics/AnalyticsAccessor.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818

1919
import com.couchbase.client.core.Core;
2020
import com.couchbase.client.core.Reactor;
21+
import com.couchbase.client.core.error.CouchbaseException;
2122
import com.couchbase.client.core.msg.analytics.AnalyticsRequest;
2223
import com.couchbase.client.core.msg.analytics.AnalyticsResponse;
24+
import com.couchbase.client.core.topology.ClusterProdName;
2325
import com.couchbase.client.java.codec.JsonSerializer;
2426
import reactor.core.publisher.Mono;
2527

@@ -52,11 +54,28 @@ public static Mono<ReactiveAnalyticsResult> analyticsQueryReactive(final Core co
5254
}
5355

5456
private static Mono<AnalyticsResponse> analyticsQueryInternal(final Core core, final AnalyticsRequest request) {
55-
core.send(request);
56-
return Reactor
57-
.wrap(request, request.response(), true)
58-
.doOnNext(ignored -> request.context().logicallyComplete())
59-
.doOnError(err -> request.context().logicallyComplete(err));
57+
return core.waitForClusterTopology(request.timeout())
58+
.flatMap(clusterTopology -> {
59+
60+
if (clusterTopology.id() != null
61+
&& clusterTopology.id().prodName() != null
62+
&& !clusterTopology.id().prodName().startsWith(ClusterProdName.COUCHBASE_SERVER)) {
63+
StringBuilder sb = new StringBuilder();
64+
sb.append("This '");
65+
sb.append(clusterTopology.id().prodName());
66+
sb.append("' cluster cannot be used with this SDK, which is intended for use with operational clusters");
67+
if (clusterTopology.id().prodName().startsWith(ClusterProdName.ENTERPRISE_ANALYTICS)) {
68+
sb.append(". For this cluster, an Enterprise Analytics SDK should be used.");
69+
}
70+
return Mono.error(new CouchbaseException(sb.toString()));
71+
}
72+
73+
return Mono.defer(() -> {
74+
core.send(request);
75+
return Reactor.wrap(request, request.response(), true);
76+
}).doOnNext(ignored -> request.context().logicallyComplete())
77+
.doOnError(err -> request.context().logicallyComplete(err));
78+
});
6079
}
6180

6281
}

0 commit comments

Comments
 (0)