This is an automated email from the ASF dual-hosted git repository.
pauloricardomg pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-sidecar.git
The following commit(s) were added to refs/heads/trunk by this push:
new 969cbcd3 CASSSIDECAR-376: Update OperationalJob to support
cluster-wide operations (#327)
969cbcd3 is described below
commit 969cbcd37bd948a87d2ade9674f94ec4a4ee6b94
Author: Brandon Ho <[email protected]>
AuthorDate: Fri May 15 10:19:24 2026 -0400
CASSSIDECAR-376: Update OperationalJob to support cluster-wide operations
(#327)
CASSSIDECAR-376: Update OperationalJob class to support cluster-wide
operations
- Add nodeId, startTime, lastUpdate, and node tracking lists to
OperationalJob
- Add hostId to NodeSettings; remove separate
CassandraAdapterDelegate.hostId()
- Add Builder pattern to OperationalJobResponse and extend with new fields
- Update handlers to use new fields and delegate.nodeSettings().hostId()
- Use Instant for startTime/lastUpdate with ISO-8601 JSON serialization
patch by Brandon Ho; reviewed by Paulo Motta, Francisco Guerrero for
CASSSIDECAR-376
---
.../sidecar/common/response/NodeSettings.java | 25 +-
.../common/response/OperationalJobResponse.java | 254 ++++++++++++++++++++-
.../common/utils/InstantIso8601Deserializer.java | 40 ++++
.../common/utils/InstantIso8601Serializer.java | 46 ++++
.../response/OperationalJobResponseTest.java | 145 ++++++++++++
.../sidecar/cluster/CassandraAdapterDelegate.java | 8 +
.../handlers/ListOperationalJobsHandler.java | 14 +-
.../sidecar/handlers/NodeDecommissionHandler.java | 10 +-
.../sidecar/handlers/NodeDrainHandler.java | 10 +-
.../sidecar/handlers/NodeMoveHandler.java | 10 +-
.../cassandra/sidecar/job/NodeDecommissionJob.java | 8 +-
.../apache/cassandra/sidecar/job/NodeDrainJob.java | 8 +-
.../apache/cassandra/sidecar/job/NodeMoveJob.java | 8 +-
.../cassandra/sidecar/job/OperationalJob.java | 131 +++++++++++
.../sidecar/utils/OperationalJobUtils.java | 26 ++-
.../org/apache/cassandra/sidecar/TestModule.java | 2 +
.../CassandraClientTokenRingProviderTest.java | 2 +
.../cassandra/sidecar/handlers/CommonTest.java | 5 +
.../sidecar/handlers/NodeDrainHandlerTest.java | 6 +
.../sidecar/handlers/NodeMoveHandlerTest.java | 6 +
.../cassandra/sidecar/job/OperationalJobTest.java | 2 +
21 files changed, 751 insertions(+), 15 deletions(-)
diff --git
a/client-common/src/main/java/org/apache/cassandra/sidecar/common/response/NodeSettings.java
b/client-common/src/main/java/org/apache/cassandra/sidecar/common/response/NodeSettings.java
index ab988be1..2fc83a9a 100644
---
a/client-common/src/main/java/org/apache/cassandra/sidecar/common/response/NodeSettings.java
+++
b/client-common/src/main/java/org/apache/cassandra/sidecar/common/response/NodeSettings.java
@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.cassandra.sidecar.common.DataObjectBuilder;
@@ -48,6 +49,8 @@ public class NodeSettings
private final Set<String> tokens;
@JsonProperty("sidecar")
private final Map<String, String> sidecar;
+ @JsonProperty("hostId")
+ private final UUID hostId;
/**
* Constructs a new {@link NodeSettings}.
@@ -71,6 +74,7 @@ public class NodeSettings
rpcPort = builder.rpcPort;
tokens = builder.tokens;
sidecar = builder.sidecar;
+ hostId = builder.hostId;
}
@JsonProperty("releaseVersion")
@@ -120,6 +124,12 @@ public class NodeSettings
return tokens;
}
+ @JsonProperty("hostId")
+ public UUID hostId()
+ {
+ return hostId;
+ }
+
/**
* {@inheritDoc}
*/
@@ -142,6 +152,7 @@ public class NodeSettings
&& Objects.equals(this.rpcAddress, that.rpcAddress)
&& Objects.equals(this.rpcPort, that.rpcPort)
&& Objects.equals(this.tokens, that.tokens)
+ && Objects.equals(this.hostId, that.hostId)
;
}
@@ -151,7 +162,7 @@ public class NodeSettings
@Override
public int hashCode()
{
- return Objects.hash(releaseVersion, partitioner, sidecar, datacenter,
rpcAddress, rpcPort, tokens);
+ return Objects.hash(releaseVersion, partitioner, sidecar, datacenter,
rpcAddress, rpcPort, tokens, hostId);
}
/**
@@ -174,6 +185,7 @@ public class NodeSettings
private int rpcPort;
private Set<String> tokens;
private Map<String, String> sidecar;
+ private UUID hostId;
private Builder()
{
@@ -262,6 +274,17 @@ public class NodeSettings
return update(b -> b.sidecar = sidecar);
}
+ /**
+ * Sets the {@code hostId} and returns a reference to this Builder
enabling method chaining.
+ *
+ * @param hostId the {@code hostId} to set
+ * @return a reference to this Builder
+ */
+ public Builder hostId(UUID hostId)
+ {
+ return update(b -> b.hostId = hostId);
+ }
+
/**
* Sets the {@code sidecarVersion} in the {@code sidecar} map and
returns a reference to this Builder
* enabling method chaining.
diff --git
a/client-common/src/main/java/org/apache/cassandra/sidecar/common/response/OperationalJobResponse.java
b/client-common/src/main/java/org/apache/cassandra/sidecar/common/response/OperationalJobResponse.java
index 271f398a..d33c9886 100644
---
a/client-common/src/main/java/org/apache/cassandra/sidecar/common/response/OperationalJobResponse.java
+++
b/client-common/src/main/java/org/apache/cassandra/sidecar/common/response/OperationalJobResponse.java
@@ -18,13 +18,20 @@
package org.apache.cassandra.sidecar.common.response;
+import java.time.Instant;
+import java.util.List;
import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.apache.cassandra.sidecar.common.DataObjectBuilder;
import org.apache.cassandra.sidecar.common.data.OperationalJobStatus;
+import org.apache.cassandra.sidecar.common.utils.InstantIso8601Deserializer;
+import org.apache.cassandra.sidecar.common.utils.InstantIso8601Serializer;
/**
* Response structure of the operational jobs API
@@ -37,17 +44,61 @@ public class OperationalJobResponse
private final OperationalJobStatus status;
private final String operation;
private final String reason;
+ @JsonSerialize(using = InstantIso8601Serializer.class)
+ @JsonDeserialize(using = InstantIso8601Deserializer.class)
+ private final Instant startTime;
+ private final List<UUID> nodesPending;
+ private final List<UUID> nodesExecuting;
+ private final List<UUID> nodesSucceeded;
+ private final List<UUID> nodesFailed;
+ @JsonSerialize(using = InstantIso8601Serializer.class)
+ @JsonDeserialize(using = InstantIso8601Deserializer.class)
+ private final Instant lastUpdate;
@JsonCreator
public OperationalJobResponse(@JsonProperty("jobId") UUID jobId,
@JsonProperty("jobStatus")
OperationalJobStatus status,
@JsonProperty("operation") String operation,
- @JsonProperty("reason") String reason)
+ @JsonProperty("reason") String reason,
+ @JsonProperty("startTime") Instant startTime,
+ @JsonProperty("nodesPending") List<UUID>
nodesPending,
+ @JsonProperty("nodesExecuting") List<UUID>
nodesExecuting,
+ @JsonProperty("nodesSucceeded") List<UUID>
nodesSucceeded,
+ @JsonProperty("nodesFailed") List<UUID>
nodesFailed,
+ @JsonProperty("lastUpdate") Instant
lastUpdate)
{
this.jobId = jobId;
this.status = status;
this.operation = operation;
this.reason = reason;
+ this.startTime = startTime;
+ this.nodesPending = nodesPending;
+ this.nodesExecuting = nodesExecuting;
+ this.nodesSucceeded = nodesSucceeded;
+ this.nodesFailed = nodesFailed;
+ this.lastUpdate = lastUpdate;
+ }
+
+ private OperationalJobResponse(Builder builder)
+ {
+ this.jobId = builder.jobId;
+ this.status = builder.status;
+ this.operation = builder.operation;
+ this.reason = builder.reason;
+ this.startTime = builder.startTime;
+ this.nodesPending = builder.nodesPending;
+ this.nodesExecuting = builder.nodesExecuting;
+ this.nodesSucceeded = builder.nodesSucceeded;
+ this.nodesFailed = builder.nodesFailed;
+ this.lastUpdate = builder.lastUpdate;
+ }
+
+ /**
+ * @return a new {@link Builder} instance
+ */
+ public static Builder builder()
+ {
+ return new Builder();
}
/**
@@ -86,4 +137,205 @@ public class OperationalJobResponse
return reason;
}
+ /**
+ * @return the time this job started execution
+ */
+ @JsonProperty("startTime")
+ public Instant startTime()
+ {
+ return startTime;
+ }
+
+ /**
+ * @return the list of nodes pending execution
+ */
+ @JsonProperty("nodesPending")
+ public List<UUID> nodesPending()
+ {
+ return nodesPending;
+ }
+
+ /**
+ * @return the list of nodes currently executing
+ */
+ @JsonProperty("nodesExecuting")
+ public List<UUID> nodesExecuting()
+ {
+ return nodesExecuting;
+ }
+
+ /**
+ * @return the list of nodes that have succeeded
+ */
+ @JsonProperty("nodesSucceeded")
+ public List<UUID> nodesSucceeded()
+ {
+ return nodesSucceeded;
+ }
+
+ /**
+ * @return the list of nodes that have failed
+ */
+ @JsonProperty("nodesFailed")
+ public List<UUID> nodesFailed()
+ {
+ return nodesFailed;
+ }
+
+ /**
+ * @return the time of the last status update
+ */
+ @JsonProperty("lastUpdate")
+ public Instant lastUpdate()
+ {
+ return lastUpdate;
+ }
+
+ /**
+ * {@code OperationalJobResponse} builder static inner class.
+ */
+ public static final class Builder implements DataObjectBuilder<Builder,
OperationalJobResponse>
+ {
+ private UUID jobId;
+ private OperationalJobStatus status;
+ private String operation;
+ private String reason;
+ private Instant startTime;
+ private List<UUID> nodesPending;
+ private List<UUID> nodesExecuting;
+ private List<UUID> nodesSucceeded;
+ private List<UUID> nodesFailed;
+ private Instant lastUpdate;
+
+ private Builder()
+ {
+ }
+
+ @Override
+ public Builder self()
+ {
+ return this;
+ }
+
+ /**
+ * Sets the {@code jobId} and returns a reference to this Builder
enabling method chaining.
+ *
+ * @param jobId the {@code jobId} to set
+ * @return a reference to this Builder
+ */
+ public Builder jobId(UUID jobId)
+ {
+ return update(b -> b.jobId = jobId);
+ }
+
+ /**
+ * Sets the {@code status} and returns a reference to this Builder
enabling method chaining.
+ *
+ * @param status the {@code status} to set
+ * @return a reference to this Builder
+ */
+ public Builder status(OperationalJobStatus status)
+ {
+ return update(b -> b.status = status);
+ }
+
+ /**
+ * Sets the {@code operation} and returns a reference to this Builder
enabling method chaining.
+ *
+ * @param operation the {@code operation} to set
+ * @return a reference to this Builder
+ */
+ public Builder operation(String operation)
+ {
+ return update(b -> b.operation = operation);
+ }
+
+ /**
+ * Sets the {@code reason} and returns a reference to this Builder
enabling method chaining.
+ *
+ * @param reason the {@code reason} to set
+ * @return a reference to this Builder
+ */
+ public Builder reason(String reason)
+ {
+ return update(b -> b.reason = reason);
+ }
+
+ /**
+ * Sets the {@code startTime} and returns a reference to this Builder
enabling method chaining.
+ *
+ * @param startTime the {@code startTime} to set
+ * @return a reference to this Builder
+ */
+ public Builder startTime(Instant startTime)
+ {
+ return update(b -> b.startTime = startTime);
+ }
+
+ /**
+ * Sets the {@code nodesPending} and returns a reference to this
Builder enabling method chaining.
+ *
+ * @param nodesPending the {@code nodesPending} to set
+ * @return a reference to this Builder
+ */
+ public Builder nodesPending(List<UUID> nodesPending)
+ {
+ return update(b -> b.nodesPending = nodesPending);
+ }
+
+ /**
+ * Sets the {@code nodesExecuting} and returns a reference to this
Builder enabling method chaining.
+ *
+ * @param nodesExecuting the {@code nodesExecuting} to set
+ * @return a reference to this Builder
+ */
+ public Builder nodesExecuting(List<UUID> nodesExecuting)
+ {
+ return update(b -> b.nodesExecuting = nodesExecuting);
+ }
+
+ /**
+ * Sets the {@code nodesSucceeded} and returns a reference to this
Builder enabling method chaining.
+ *
+ * @param nodesSucceeded the {@code nodesSucceeded} to set
+ * @return a reference to this Builder
+ */
+ public Builder nodesSucceeded(List<UUID> nodesSucceeded)
+ {
+ return update(b -> b.nodesSucceeded = nodesSucceeded);
+ }
+
+ /**
+ * Sets the {@code nodesFailed} and returns a reference to this
Builder enabling method chaining.
+ *
+ * @param nodesFailed the {@code nodesFailed} to set
+ * @return a reference to this Builder
+ */
+ public Builder nodesFailed(List<UUID> nodesFailed)
+ {
+ return update(b -> b.nodesFailed = nodesFailed);
+ }
+
+ /**
+ * Sets the {@code lastUpdate} and returns a reference to this Builder
enabling method chaining.
+ *
+ * @param lastUpdate the {@code lastUpdate} to set
+ * @return a reference to this Builder
+ */
+ public Builder lastUpdate(Instant lastUpdate)
+ {
+ return update(b -> b.lastUpdate = lastUpdate);
+ }
+
+ /**
+ * Returns a {@code OperationalJobResponse} built from the parameters
previously set.
+ *
+ * @return a {@code OperationalJobResponse} built with parameters of
this {@code Builder}
+ */
+ @Override
+ public OperationalJobResponse build()
+ {
+ return new OperationalJobResponse(this);
+ }
+ }
}
diff --git
a/client-common/src/main/java/org/apache/cassandra/sidecar/common/utils/InstantIso8601Deserializer.java
b/client-common/src/main/java/org/apache/cassandra/sidecar/common/utils/InstantIso8601Deserializer.java
new file mode 100644
index 00000000..2d0d94c5
--- /dev/null
+++
b/client-common/src/main/java/org/apache/cassandra/sidecar/common/utils/InstantIso8601Deserializer.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.common.utils;
+
+import java.io.IOException;
+import java.time.Instant;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+
+/**
+ * Deserializes an ISO-8601 string (e.g. {@code 2026-05-12T14:30:00Z}) into a
{@link java.time.Instant}.
+ * This avoids a dependency on {@code
com.fasterxml.jackson.datatype:jackson-datatype-jsr310}.
+ */
+public class InstantIso8601Deserializer extends JsonDeserializer<Instant>
+{
+ @Override
+ public Instant deserialize(JsonParser p, DeserializationContext ctx)
throws IOException
+ {
+ String text = p.getText();
+ return text == null ? null : Instant.parse(text);
+ }
+}
diff --git
a/client-common/src/main/java/org/apache/cassandra/sidecar/common/utils/InstantIso8601Serializer.java
b/client-common/src/main/java/org/apache/cassandra/sidecar/common/utils/InstantIso8601Serializer.java
new file mode 100644
index 00000000..d5e4d7f9
--- /dev/null
+++
b/client-common/src/main/java/org/apache/cassandra/sidecar/common/utils/InstantIso8601Serializer.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.common.utils;
+
+import java.io.IOException;
+import java.time.Instant;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+/**
+ * Serializes a {@link java.time.Instant} as an ISO-8601 string (e.g. {@code
2026-05-12T14:30:00Z}).
+ * This avoids a dependency on {@code
com.fasterxml.jackson.datatype:jackson-datatype-jsr310}.
+ */
+public class InstantIso8601Serializer extends JsonSerializer<Instant>
+{
+ @Override
+ public void serialize(Instant value, JsonGenerator gen, SerializerProvider
provider) throws IOException
+ {
+ if (value == null)
+ {
+ gen.writeNull();
+ }
+ else
+ {
+ gen.writeString(value.toString());
+ }
+ }
+}
diff --git
a/client-common/src/test/java/org/apache/cassandra/sidecar/common/response/OperationalJobResponseTest.java
b/client-common/src/test/java/org/apache/cassandra/sidecar/common/response/OperationalJobResponseTest.java
new file mode 100644
index 00000000..811abe98
--- /dev/null
+++
b/client-common/src/test/java/org/apache/cassandra/sidecar/common/response/OperationalJobResponseTest.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.common.response;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.cassandra.sidecar.common.data.OperationalJobStatus;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Unit tests for {@link OperationalJobResponse} JSON serialization and
deserialization.
+ */
+class OperationalJobResponseTest
+{
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Test
+ void testSerializeStartTimeAsIso8601() throws Exception
+ {
+ Instant startTime = Instant.parse("2026-05-12T14:30:00Z");
+ OperationalJobResponse response = OperationalJobResponse.builder()
+
.jobId(UUID.randomUUID())
+
.status(OperationalJobStatus.RUNNING)
+
.operation("drain")
+
.startTime(startTime)
+ .build();
+
+ String json = objectMapper.writeValueAsString(response);
+
+ assertThat(json).contains("\"startTime\":\"2026-05-12T14:30:00Z\"");
+ }
+
+ @Test
+ void testSerializeLastUpdateAsIso8601() throws Exception
+ {
+ Instant lastUpdate = Instant.parse("2026-05-12T14:35:00Z");
+ OperationalJobResponse response = OperationalJobResponse.builder()
+
.jobId(UUID.randomUUID())
+
.status(OperationalJobStatus.RUNNING)
+
.operation("drain")
+
.lastUpdate(lastUpdate)
+ .build();
+
+ String json = objectMapper.writeValueAsString(response);
+
+ assertThat(json).contains("\"lastUpdate\":\"2026-05-12T14:35:00Z\"");
+ }
+
+ @Test
+ void testDeserializeIso8601ToInstant() throws Exception
+ {
+ String json = "{\"jobId\":\"6ba7b810-9dad-11d1-80b4-00c04fd430c8\","
+ + "\"jobStatus\":\"RUNNING\","
+ + "\"operation\":\"drain\","
+ + "\"startTime\":\"2026-05-12T14:30:00Z\","
+ + "\"lastUpdate\":\"2026-05-12T14:35:00Z\"}";
+
+ OperationalJobResponse response = objectMapper.readValue(json,
OperationalJobResponse.class);
+
+
assertThat(response.startTime()).isEqualTo(Instant.parse("2026-05-12T14:30:00Z"));
+
assertThat(response.lastUpdate()).isEqualTo(Instant.parse("2026-05-12T14:35:00Z"));
+ }
+
+ @Test
+ void testRoundTripSerialization() throws Exception
+ {
+ Instant startTime = Instant.parse("2026-05-12T14:30:00Z");
+ Instant lastUpdate = Instant.parse("2026-05-12T14:35:00Z");
+ UUID jobId = UUID.randomUUID();
+ UUID nodeId = UUID.randomUUID();
+ List<UUID> nodesSucceeded = Arrays.asList(nodeId);
+
+ OperationalJobResponse original = OperationalJobResponse.builder()
+ .jobId(jobId)
+
.status(OperationalJobStatus.SUCCEEDED)
+
.operation("decommission")
+
.startTime(startTime)
+
.lastUpdate(lastUpdate)
+
.nodesSucceeded(nodesSucceeded)
+ .build();
+
+ String json = objectMapper.writeValueAsString(original);
+ OperationalJobResponse deserialized = objectMapper.readValue(json,
OperationalJobResponse.class);
+
+ assertThat(deserialized.jobId()).isEqualTo(jobId);
+
assertThat(deserialized.status()).isEqualTo(OperationalJobStatus.SUCCEEDED);
+ assertThat(deserialized.operation()).isEqualTo("decommission");
+ assertThat(deserialized.startTime()).isEqualTo(startTime);
+ assertThat(deserialized.lastUpdate()).isEqualTo(lastUpdate);
+ assertThat(deserialized.nodesSucceeded()).isEqualTo(nodesSucceeded);
+ }
+
+ @Test
+ void testNullTimesOmittedFromJson() throws Exception
+ {
+ OperationalJobResponse response = OperationalJobResponse.builder()
+
.jobId(UUID.randomUUID())
+
.status(OperationalJobStatus.CREATED)
+
.operation("move")
+ .build();
+
+ String json = objectMapper.writeValueAsString(response);
+
+ assertThat(json).doesNotContain("startTime");
+ assertThat(json).doesNotContain("lastUpdate");
+ }
+
+ @Test
+ void testDeserializeUnknownFieldsIgnored() throws Exception
+ {
+ String json = "{\"jobId\":\"6ba7b810-9dad-11d1-80b4-00c04fd430c8\","
+ + "\"jobStatus\":\"SUCCEEDED\","
+ + "\"operation\":\"drain\","
+ + "\"unknownField\":\"someValue\","
+ + "\"startTime\":\"2026-05-12T14:30:00Z\"}";
+
+ OperationalJobResponse response = objectMapper.readValue(json,
OperationalJobResponse.class);
+
+
assertThat(response.status()).isEqualTo(OperationalJobStatus.SUCCEEDED);
+
assertThat(response.startTime()).isEqualTo(Instant.parse("2026-05-12T14:30:00Z"));
+ }
+}
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/CassandraAdapterDelegate.java
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/CassandraAdapterDelegate.java
index 9ce60c42..31ef88f4 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/CassandraAdapterDelegate.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/CassandraAdapterDelegate.java
@@ -26,6 +26,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import javax.management.Notification;
@@ -307,6 +308,7 @@ public class CassandraAdapterDelegate implements
ICassandraAdapter, Host.StateLi
String partitionerName = storageOperations.getPartitionerName();
List<String> tokens = maybeGetTokens(storageOperations);
String dataCenter = endpointSnitchOperations.getDatacenter();
+ UUID hostId = UUID.fromString(storageOperations.getLocalHostId());
return NodeSettings.builder()
.releaseVersion(releaseVersion)
@@ -316,6 +318,7 @@ public class CassandraAdapterDelegate implements
ICassandraAdapter, Host.StateLi
.tokens(new LinkedHashSet<>(tokens))
.rpcAddress(localNativeTransportAddress.getAddress())
.rpcPort(localNativeTransportAddress.getPort())
+ .hostId(hostId)
.build();
}
@@ -689,6 +692,11 @@ public class CassandraAdapterDelegate implements
ICassandraAdapter, Host.StateLi
* @return a collection of tokens formatted as strings
*/
List<String> getTokens();
+
+ /**
+ * @return the local host UUID as a string
+ */
+ String getLocalHostId();
}
/**
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/handlers/ListOperationalJobsHandler.java
b/server/src/main/java/org/apache/cassandra/sidecar/handlers/ListOperationalJobsHandler.java
index 484039e5..ca840703 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/handlers/ListOperationalJobsHandler.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/handlers/ListOperationalJobsHandler.java
@@ -35,7 +35,6 @@ import
org.apache.cassandra.sidecar.utils.CassandraInputValidator;
import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
import org.jetbrains.annotations.NotNull;
-import static
org.apache.cassandra.sidecar.common.data.OperationalJobStatus.RUNNING;
/**
* Handler for retrieving the all the jobs running on the sidecar
@@ -72,7 +71,18 @@ public class ListOperationalJobsHandler extends
AbstractHandler<Void> implements
ListOperationalJobsResponse listResponse = new
ListOperationalJobsResponse();
jobManager.allInflightJobs()
.stream()
- .map(job -> new OperationalJobResponse(job.jobId(), RUNNING,
job.name(), null))
+ .map(job -> OperationalJobResponse.builder()
+ .jobId(job.jobId())
+ .status(job.status())
+ .operation(job.name())
+ .startTime(job.startTime())
+
.nodesPending(job.nodesPending())
+
.nodesExecuting(job.nodesExecuting())
+
.nodesSucceeded(job.nodesSucceeded())
+
.nodesFailed(job.nodesFailed())
+
.lastUpdate(job.lastUpdate())
+ .build()
+ )
.forEach(listResponse::addJob);
context.json(listResponse);
}
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/handlers/NodeDecommissionHandler.java
b/server/src/main/java/org/apache/cassandra/sidecar/handlers/NodeDecommissionHandler.java
index d2c522eb..2c9bfd48 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/handlers/NodeDecommissionHandler.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/handlers/NodeDecommissionHandler.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.sidecar.handlers;
import java.util.Collections;
import java.util.Set;
+import java.util.UUID;
import com.datastax.driver.core.utils.UUIDs;
import com.google.inject.Inject;
@@ -28,6 +29,8 @@ import io.vertx.core.net.SocketAddress;
import io.vertx.ext.auth.authorization.Authorization;
import io.vertx.ext.web.RoutingContext;
import org.apache.cassandra.sidecar.acl.authorization.BasicPermissions;
+import org.apache.cassandra.sidecar.cluster.CassandraAdapterDelegate;
+import org.apache.cassandra.sidecar.common.response.NodeSettings;
import org.apache.cassandra.sidecar.common.server.StorageOperations;
import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
import org.apache.cassandra.sidecar.config.ServiceConfiguration;
@@ -83,8 +86,11 @@ public class NodeDecommissionHandler extends
AbstractHandler<Boolean> implements
SocketAddress remoteAddress,
Boolean isForce)
{
- StorageOperations operations =
metadataFetcher.delegate(host).storageOperations();
- NodeDecommissionJob job = new NodeDecommissionJob(UUIDs.timeBased(),
operations, isForce);
+ CassandraAdapterDelegate delegate = metadataFetcher.delegate(host);
+ StorageOperations operations = delegate.storageOperations();
+ NodeSettings nodeSettings = delegate.nodeSettings();
+ UUID nodeId = nodeSettings.hostId();
+ NodeDecommissionJob job = new NodeDecommissionJob(UUIDs.timeBased(),
nodeId, operations, isForce);
this.jobManager.trySubmitJob(job,
(completedJob, exception) ->
OperationalJobUtils.sendStatusBasedResponse(context, completedJob, exception),
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/handlers/NodeDrainHandler.java
b/server/src/main/java/org/apache/cassandra/sidecar/handlers/NodeDrainHandler.java
index b0de6753..63911d24 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/handlers/NodeDrainHandler.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/handlers/NodeDrainHandler.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.sidecar.handlers;
import java.util.Collections;
import java.util.Set;
+import java.util.UUID;
import com.datastax.driver.core.utils.UUIDs;
import com.google.inject.Inject;
@@ -28,6 +29,8 @@ import io.vertx.core.net.SocketAddress;
import io.vertx.ext.auth.authorization.Authorization;
import io.vertx.ext.web.RoutingContext;
import org.apache.cassandra.sidecar.acl.authorization.BasicPermissions;
+import org.apache.cassandra.sidecar.cluster.CassandraAdapterDelegate;
+import org.apache.cassandra.sidecar.common.response.NodeSettings;
import org.apache.cassandra.sidecar.common.server.StorageOperations;
import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
import org.apache.cassandra.sidecar.config.ServiceConfiguration;
@@ -81,8 +84,11 @@ public class NodeDrainHandler extends AbstractHandler<Void>
implements AccessPro
SocketAddress remoteAddress,
Void unused)
{
- StorageOperations operations =
metadataFetcher.delegate(host).storageOperations();
- NodeDrainJob job = new NodeDrainJob(UUIDs.timeBased(), operations);
+ CassandraAdapterDelegate delegate = metadataFetcher.delegate(host);
+ StorageOperations operations = delegate.storageOperations();
+ NodeSettings nodeSettings = delegate.nodeSettings();
+ UUID nodeId = nodeSettings.hostId();
+ NodeDrainJob job = new NodeDrainJob(UUIDs.timeBased(), nodeId,
operations);
this.jobManager.trySubmitJob(job,
(completedJob, exception) ->
OperationalJobUtils.sendStatusBasedResponse(context, completedJob, exception),
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/handlers/NodeMoveHandler.java
b/server/src/main/java/org/apache/cassandra/sidecar/handlers/NodeMoveHandler.java
index c7679869..73176053 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/handlers/NodeMoveHandler.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/handlers/NodeMoveHandler.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.sidecar.handlers;
import java.util.Collections;
import java.util.Set;
+import java.util.UUID;
import com.datastax.driver.core.utils.UUIDs;
import com.google.inject.Inject;
@@ -32,7 +33,9 @@ import io.vertx.core.net.SocketAddress;
import io.vertx.ext.auth.authorization.Authorization;
import io.vertx.ext.web.RoutingContext;
import org.apache.cassandra.sidecar.acl.authorization.BasicPermissions;
+import org.apache.cassandra.sidecar.cluster.CassandraAdapterDelegate;
import org.apache.cassandra.sidecar.common.request.data.NodeMoveRequestPayload;
+import org.apache.cassandra.sidecar.common.response.NodeSettings;
import org.apache.cassandra.sidecar.common.server.StorageOperations;
import org.apache.cassandra.sidecar.common.utils.StringUtils;
import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
@@ -99,8 +102,11 @@ public class NodeMoveHandler extends
AbstractHandler<String> implements AccessPr
SocketAddress remoteAddress,
String newToken)
{
- StorageOperations operations =
metadataFetcher.delegate(host).storageOperations();
- NodeMoveJob job = new NodeMoveJob(UUIDs.timeBased(), newToken,
operations);
+ CassandraAdapterDelegate delegate = metadataFetcher.delegate(host);
+ StorageOperations operations = delegate.storageOperations();
+ NodeSettings nodeSettings = delegate.nodeSettings();
+ UUID nodeId = nodeSettings.hostId();
+ NodeMoveJob job = new NodeMoveJob(UUIDs.timeBased(), nodeId, newToken,
operations);
this.jobManager.trySubmitJob(job,
(completedJob, exception) ->
OperationalJobUtils.sendStatusBasedResponse(context, completedJob, exception),
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/job/NodeDecommissionJob.java
b/server/src/main/java/org/apache/cassandra/sidecar/job/NodeDecommissionJob.java
index 778ea450..e042200f 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/job/NodeDecommissionJob.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/job/NodeDecommissionJob.java
@@ -28,6 +28,7 @@ import io.vertx.core.Future;
import org.apache.cassandra.sidecar.common.data.OperationalJobStatus;
import org.apache.cassandra.sidecar.common.server.StorageOperations;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
/**
* Implementation of {@link OperationalJob} to perform node decommission
operation.
@@ -41,7 +42,12 @@ public class NodeDecommissionJob extends OperationalJob
public NodeDecommissionJob(UUID jobId, StorageOperations storageOps,
boolean isForce)
{
- super(jobId);
+ this(jobId, null, storageOps, isForce);
+ }
+
+ public NodeDecommissionJob(UUID jobId, @Nullable UUID nodeId,
StorageOperations storageOps, boolean isForce)
+ {
+ super(jobId, nodeId);
this.storageOperations = storageOps;
this.isForce = isForce;
}
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/job/NodeDrainJob.java
b/server/src/main/java/org/apache/cassandra/sidecar/job/NodeDrainJob.java
index 16fe02e2..b5b1db92 100644
--- a/server/src/main/java/org/apache/cassandra/sidecar/job/NodeDrainJob.java
+++ b/server/src/main/java/org/apache/cassandra/sidecar/job/NodeDrainJob.java
@@ -31,6 +31,7 @@ import
org.apache.cassandra.sidecar.common.data.OperationalJobStatus;
import org.apache.cassandra.sidecar.common.server.StorageOperations;
import
org.apache.cassandra.sidecar.common.server.exceptions.OperationalJobException;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
/**
* Implementation of {@link OperationalJob} to perform node drain operation.
@@ -79,7 +80,12 @@ public class NodeDrainJob extends OperationalJob
public NodeDrainJob(UUID jobId, StorageOperations storageOps)
{
- super(jobId);
+ this(jobId, null, storageOps);
+ }
+
+ public NodeDrainJob(UUID jobId, @Nullable UUID nodeId, StorageOperations
storageOps)
+ {
+ super(jobId, nodeId);
this.storageOperations = storageOps;
}
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/job/NodeMoveJob.java
b/server/src/main/java/org/apache/cassandra/sidecar/job/NodeMoveJob.java
index d0690095..c35f13ef 100644
--- a/server/src/main/java/org/apache/cassandra/sidecar/job/NodeMoveJob.java
+++ b/server/src/main/java/org/apache/cassandra/sidecar/job/NodeMoveJob.java
@@ -29,6 +29,7 @@ import io.vertx.core.Future;
import org.apache.cassandra.sidecar.common.server.StorageOperations;
import
org.apache.cassandra.sidecar.common.server.exceptions.OperationalJobException;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
/**
* Implementation of {@link OperationalJob} to perform node move operation.
@@ -43,7 +44,12 @@ public class NodeMoveJob extends OperationalJob
public NodeMoveJob(UUID jobId, String newToken, StorageOperations
storageOps)
{
- super(jobId);
+ this(jobId, null, newToken, storageOps);
+ }
+
+ public NodeMoveJob(UUID jobId, @Nullable UUID nodeId, String newToken,
StorageOperations storageOps)
+ {
+ super(jobId, nodeId);
this.newToken = newToken;
this.storageOperations = storageOps;
}
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/job/OperationalJob.java
b/server/src/main/java/org/apache/cassandra/sidecar/job/OperationalJob.java
index 660f7db0..8fad806f 100644
--- a/server/src/main/java/org/apache/cassandra/sidecar/job/OperationalJob.java
+++ b/server/src/main/java/org/apache/cassandra/sidecar/job/OperationalJob.java
@@ -18,6 +18,9 @@
package org.apache.cassandra.sidecar.job;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.UUID;
@@ -34,6 +37,7 @@ import
org.apache.cassandra.sidecar.common.utils.Preconditions;
import org.apache.cassandra.sidecar.concurrent.TaskExecutorPool;
import org.apache.cassandra.sidecar.tasks.Task;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
/**
* An abstract class representing operational jobs that run on Cassandra
@@ -44,9 +48,17 @@ public abstract class OperationalJob implements Task<Void>
// use v1 time-based uuid
private final UUID jobId;
+ @Nullable
+ private final UUID nodeId;
private final Promise<Void> executionPromise;
private volatile boolean isExecuting = false;
+ private volatile Instant startTime;
+ private volatile Instant lastUpdate;
+ private volatile List<UUID> nodesPending;
+ private volatile List<UUID> nodesExecuting;
+ private volatile List<UUID> nodesSucceeded;
+ private volatile List<UUID> nodesFailed;
/**
* Constructs a job with a unique UUID, in Pending state
@@ -54,10 +66,26 @@ public abstract class OperationalJob implements Task<Void>
* @param jobId UUID representing the Job to be created
*/
protected OperationalJob(UUID jobId)
+ {
+ this(jobId, null);
+ }
+
+ /**
+ * Constructs a job with a unique UUID and an associated node identifier,
in Pending state
+ *
+ * @param jobId UUID representing the Job to be created
+ * @param nodeId UUID of the node this job is running on, or {@code null}
if not applicable
+ */
+ protected OperationalJob(UUID jobId, @Nullable UUID nodeId)
{
Preconditions.checkArgument(jobId.version() == 1, "OperationalJob
accepts only time-based UUID");
this.jobId = jobId;
+ this.nodeId = nodeId;
this.executionPromise = Promise.promise();
+ this.nodesPending = nodeId != null ? new
ArrayList<>(Collections.singletonList(nodeId)) : Collections.emptyList();
+ this.nodesExecuting = Collections.emptyList();
+ this.nodesSucceeded = Collections.emptyList();
+ this.nodesFailed = Collections.emptyList();
}
public UUID jobId()
@@ -65,6 +93,84 @@ public abstract class OperationalJob implements Task<Void>
return jobId;
}
+ /**
+ * @return the node UUID associated with this job, or {@code null} if not
applicable
+ */
+ public @Nullable UUID nodeId()
+ {
+ return nodeId;
+ }
+
+ /**
+ * @return the time this job started execution, or {@code null} if not yet
started
+ */
+ public @Nullable Instant startTime()
+ {
+ return startTime;
+ }
+
+ /**
+ * @return the time of the last status update for this job, or {@code
null} if not yet started
+ */
+ public @Nullable Instant lastUpdate()
+ {
+ return lastUpdate;
+ }
+
+ /**
+ * Updates the last update time for this job.
+ *
+ * @param lastUpdate the new last update time
+ */
+ protected void lastUpdate(Instant lastUpdate)
+ {
+ this.lastUpdate = lastUpdate;
+ }
+
+ /**
+ * @return the list of nodes pending execution for this job
+ */
+ @NotNull
+ public List<UUID> nodesPending()
+ {
+ return nodesPending;
+ }
+
+ /**
+ * @return the list of nodes currently executing this job
+ */
+ @NotNull
+ public List<UUID> nodesExecuting()
+ {
+ return nodesExecuting;
+ }
+
+ /**
+ * @return the list of nodes that have succeeded executing this job
+ */
+ @NotNull
+ public List<UUID> nodesSucceeded()
+ {
+ return nodesSucceeded;
+ }
+
+ /**
+ * @return the list of nodes that have failed executing this job
+ */
+ @NotNull
+ public List<UUID> nodesFailed()
+ {
+ return nodesFailed;
+ }
+
+ /**
+ * @return whether this job requires coordination across multiple nodes
+ */
+ public boolean requiresCoordination()
+ {
+ return false;
+ }
+
@Override
public final Void result()
{
@@ -203,6 +309,13 @@ public abstract class OperationalJob implements Task<Void>
public void execute(Promise<Void> promise)
{
isExecuting = true;
+ startTime = Instant.now();
+ lastUpdate = startTime;
+ if (nodeId != null)
+ {
+ nodesPending = Collections.emptyList();
+ nodesExecuting = Collections.singletonList(nodeId);
+ }
LOGGER.info("Executing job. jobId={}", jobId);
promise.future().onComplete(executionPromise);
try
@@ -211,11 +324,23 @@ public abstract class OperationalJob implements Task<Void>
internalFuture.onComplete(ar -> {
if (ar.succeeded())
{
+ if (nodeId != null)
+ {
+ nodesExecuting = Collections.emptyList();
+ nodesSucceeded = Collections.singletonList(nodeId);
+ }
+ lastUpdate = Instant.now();
promise.tryComplete();
LOGGER.info("Complete job execution. jobId={} status={}",
jobId, status());
}
else
{
+ if (nodeId != null)
+ {
+ nodesExecuting = Collections.emptyList();
+ nodesFailed = Collections.singletonList(nodeId);
+ }
+ lastUpdate = Instant.now();
promise.tryFail(ar.cause());
LOGGER.error("Job execution failed. jobId={} reason={}",
jobId, ar.cause().getMessage());
}
@@ -224,6 +349,12 @@ public abstract class OperationalJob implements Task<Void>
catch (Throwable e)
{
OperationalJobException oje = OperationalJobException.wraps(e);
+ if (nodeId != null)
+ {
+ nodesExecuting = Collections.emptyList();
+ nodesFailed = Collections.singletonList(nodeId);
+ }
+ lastUpdate = Instant.now();
LOGGER.error("Job execution failed. jobId={} reason={}", jobId,
oje.getMessage(), oje);
promise.tryFail(oje);
}
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/utils/OperationalJobUtils.java
b/server/src/main/java/org/apache/cassandra/sidecar/utils/OperationalJobUtils.java
index 0edd5fd1..52d43262 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/utils/OperationalJobUtils.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/utils/OperationalJobUtils.java
@@ -54,7 +54,18 @@ public class OperationalJobUtils
String reason = exception.getMessage();
LOGGER.error("Conflicting job encountered. reason={}", reason);
context.response().setStatusCode(HttpResponseStatus.CONFLICT.code());
- context.json(new OperationalJobResponse(job.jobId(),
OperationalJobStatus.FAILED, job.name(), reason));
+ context.json(OperationalJobResponse.builder()
+ .jobId(job.jobId())
+
.status(OperationalJobStatus.FAILED)
+ .operation(job.name())
+ .reason(reason)
+ .startTime(job.startTime())
+
.nodesPending(job.nodesPending())
+
.nodesExecuting(job.nodesExecuting())
+
.nodesSucceeded(job.nodesSucceeded())
+ .nodesFailed(job.nodesFailed())
+ .lastUpdate(job.lastUpdate())
+ .build());
return;
}
@@ -74,6 +85,17 @@ public class OperationalJobUtils
{
reason = job.asyncResult().cause().getMessage();
}
- context.json(new OperationalJobResponse(job.jobId(), status,
job.name(), reason));
+ context.json(OperationalJobResponse.builder()
+ .jobId(job.jobId())
+ .status(status)
+ .operation(job.name())
+ .reason(reason)
+ .startTime(job.startTime())
+ .nodesPending(job.nodesPending())
+
.nodesExecuting(job.nodesExecuting())
+
.nodesSucceeded(job.nodesSucceeded())
+ .nodesFailed(job.nodesFailed())
+ .lastUpdate(job.lastUpdate())
+ .build());
}
}
diff --git a/server/src/test/java/org/apache/cassandra/sidecar/TestModule.java
b/server/src/test/java/org/apache/cassandra/sidecar/TestModule.java
index 0494bbe9..5e5d308e 100644
--- a/server/src/test/java/org/apache/cassandra/sidecar/TestModule.java
+++ b/server/src/test/java/org/apache/cassandra/sidecar/TestModule.java
@@ -22,6 +22,7 @@ import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@@ -218,6 +219,7 @@ public class TestModule extends AbstractModule
.rpcAddress(InetAddress.getLoopbackAddress())
.rpcPort(6475)
.tokens(Collections.singleton("testToken"))
+ .hostId(UUID.randomUUID())
.build());
}
delegate.setIsNativeUp(isUp);
diff --git
a/server/src/test/java/org/apache/cassandra/sidecar/cluster/CassandraClientTokenRingProviderTest.java
b/server/src/test/java/org/apache/cassandra/sidecar/cluster/CassandraClientTokenRingProviderTest.java
index e4283280..789bd7a1 100644
---
a/server/src/test/java/org/apache/cassandra/sidecar/cluster/CassandraClientTokenRingProviderTest.java
+++
b/server/src/test/java/org/apache/cassandra/sidecar/cluster/CassandraClientTokenRingProviderTest.java
@@ -31,6 +31,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.UUID;
import java.util.stream.Collectors;
import com.google.common.collect.Range;
@@ -338,6 +339,7 @@ public class CassandraClientTokenRingProviderTest
.partitioner("org.apache.cassandra.dht.Murmur3Partitioner")
.sidecarVersion("1.0-TEST")
.datacenter("DC1")
+
.hostId(UUID.randomUUID())
.build());
when(instanceMetadata.delegate().version()).thenReturn(SimpleCassandraVersion.create("4.0.0.68"));
when(instanceMetadata.delegate().metadata()).thenReturn(metadata);
diff --git
a/server/src/test/java/org/apache/cassandra/sidecar/handlers/CommonTest.java
b/server/src/test/java/org/apache/cassandra/sidecar/handlers/CommonTest.java
index 3be4d4f6..86cca506 100644
--- a/server/src/test/java/org/apache/cassandra/sidecar/handlers/CommonTest.java
+++ b/server/src/test/java/org/apache/cassandra/sidecar/handlers/CommonTest.java
@@ -19,6 +19,7 @@
package org.apache.cassandra.sidecar.handlers;
import java.util.List;
+import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -40,6 +41,7 @@ import org.apache.cassandra.sidecar.TestModule;
import org.apache.cassandra.sidecar.cluster.CassandraAdapterDelegate;
import org.apache.cassandra.sidecar.cluster.InstancesMetadata;
import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
+import org.apache.cassandra.sidecar.common.response.NodeSettings;
import org.apache.cassandra.sidecar.common.server.StorageOperations;
import org.apache.cassandra.sidecar.modules.SidecarModules;
import org.apache.cassandra.sidecar.server.Server;
@@ -118,6 +120,9 @@ public class CommonTest
this.delegate = mock(CassandraAdapterDelegate.class);
this.storageOperations = storageOperations;
when(delegate.storageOperations()).thenReturn(storageOperations);
+ when(delegate.nodeSettings()).thenReturn(NodeSettings.builder()
+
.hostId(UUID.randomUUID())
+ .build());
}
@Provides
diff --git
a/server/src/test/java/org/apache/cassandra/sidecar/handlers/NodeDrainHandlerTest.java
b/server/src/test/java/org/apache/cassandra/sidecar/handlers/NodeDrainHandlerTest.java
index 49e1604a..d65f746e 100644
---
a/server/src/test/java/org/apache/cassandra/sidecar/handlers/NodeDrainHandlerTest.java
+++
b/server/src/test/java/org/apache/cassandra/sidecar/handlers/NodeDrainHandlerTest.java
@@ -19,6 +19,7 @@
package org.apache.cassandra.sidecar.handlers;
import java.util.Collections;
+import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -45,6 +46,7 @@ import org.apache.cassandra.sidecar.TestModule;
import org.apache.cassandra.sidecar.cluster.CassandraAdapterDelegate;
import org.apache.cassandra.sidecar.cluster.InstancesMetadata;
import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
+import org.apache.cassandra.sidecar.common.response.NodeSettings;
import org.apache.cassandra.sidecar.common.response.OperationalJobResponse;
import org.apache.cassandra.sidecar.common.server.StorageOperations;
import org.apache.cassandra.sidecar.modules.SidecarModules;
@@ -229,7 +231,11 @@ public class NodeDrainHandlerTest
CassandraAdapterDelegate delegate =
mock(CassandraAdapterDelegate.class);
+ NodeSettings mockNodeSettings = NodeSettings.builder()
+
.hostId(UUID.randomUUID())
+ .build();
when(delegate.storageOperations()).thenReturn(mockStorageOperations);
+ when(delegate.nodeSettings()).thenReturn(mockNodeSettings);
when(instanceMetadata.delegate()).thenReturn(delegate);
InstancesMetadata mockInstancesMetadata =
mock(InstancesMetadata.class);
diff --git
a/server/src/test/java/org/apache/cassandra/sidecar/handlers/NodeMoveHandlerTest.java
b/server/src/test/java/org/apache/cassandra/sidecar/handlers/NodeMoveHandlerTest.java
index 1ff8294c..e22ddf40 100644
---
a/server/src/test/java/org/apache/cassandra/sidecar/handlers/NodeMoveHandlerTest.java
+++
b/server/src/test/java/org/apache/cassandra/sidecar/handlers/NodeMoveHandlerTest.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.sidecar.handlers;
import java.io.IOException;
import java.util.Collections;
+import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -46,6 +47,7 @@ import org.apache.cassandra.sidecar.TestModule;
import org.apache.cassandra.sidecar.cluster.CassandraAdapterDelegate;
import org.apache.cassandra.sidecar.cluster.InstancesMetadata;
import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
+import org.apache.cassandra.sidecar.common.response.NodeSettings;
import org.apache.cassandra.sidecar.common.response.OperationalJobResponse;
import org.apache.cassandra.sidecar.common.server.StorageOperations;
import org.apache.cassandra.sidecar.modules.SidecarModules;
@@ -398,7 +400,11 @@ public class NodeMoveHandlerTest
CassandraAdapterDelegate delegate =
mock(CassandraAdapterDelegate.class);
+ NodeSettings mockNodeSettings = NodeSettings.builder()
+
.hostId(UUID.randomUUID())
+ .build();
when(delegate.storageOperations()).thenReturn(mockStorageOperations);
+ when(delegate.nodeSettings()).thenReturn(mockNodeSettings);
when(instanceMetadata.delegate()).thenReturn(delegate);
InstancesMetadata mockInstancesMetadata =
mock(InstancesMetadata.class);
diff --git
a/server/src/test/java/org/apache/cassandra/sidecar/job/OperationalJobTest.java
b/server/src/test/java/org/apache/cassandra/sidecar/job/OperationalJobTest.java
index f3f495b7..c047d9db 100644
---
a/server/src/test/java/org/apache/cassandra/sidecar/job/OperationalJobTest.java
+++
b/server/src/test/java/org/apache/cassandra/sidecar/job/OperationalJobTest.java
@@ -170,6 +170,7 @@ class OperationalJobTest
assertThat(future.succeeded()).isTrue();
assertThat(job.asyncResult().succeeded()).isTrue();
assertThat(job.status()).isEqualTo(OperationalJobStatus.SUCCEEDED);
+ assertThat(job.lastUpdate()).isNotNull();
}
@Test
@@ -204,6 +205,7 @@ class OperationalJobTest
assertThat(failingJob.asyncResult().cause())
.isExactlyInstanceOf(OperationalJobException.class)
.hasMessage(msg);
+ assertThat(failingJob.lastUpdate()).isNotNull();
}
@Test
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]