nodeList = myPerDcLiveNodes.get(dc);
+ if (nodeList == null)
+ {
+ myPerDcLiveNodes.put(dc, new CopyOnWriteArrayList<>(Collections.singletonList(node)));
+ }
+ else
+ {
+ nodeList.addIfAbsent(node);
+ }
+ }
+
+ if (!notInLocalDC.isEmpty())
+ {
+ String nonLocalHosts = Joiner.on(",").join(notInLocalDC);
+ LOG.warn("Some contact points ({}) don't match local data center ({})",
+ nonLocalHosts,
+ getLocalDatacenter());
+ }
+
+ myIndex.set(new SecureRandom().nextInt(Math.max(nodes.size(), 1)));
+ }
+
+ /**
+ * Returns the hosts to use for a new query.
+ *
+ * The returned plan will first return replicas (whose {@code HostDistance} is {@code LOCAL}) for the query if it
+ * can determine them (i.e. mainly if {@code statement.getRoutingKey()} is not {@code null}). Following what it will
+ * return hosts whose {@code HostDistance} is {@code LOCAL} according to a Round-robin algorithm. If no specific
+ * data center is asked for the child policy is used.
+ *
+ * @param request
+ * the query for which to build the plan.
+ * @return the new query plan.
+ */
+ @Override
+ public Queue newQueryPlan(final Request request, final Session session)
+ {
+ final String dataCenter;
+
+ if (request instanceof DataCenterAwareStatement)
+ {
+ dataCenter = ((DataCenterAwareStatement) request).getDataCenter();
+ }
+ else
+ {
+ return super.newQueryPlan(request, session);
+ }
+
+ ByteBuffer partitionKey = request.getRoutingKey();
+ CqlIdentifier keyspace = request.getRoutingKeyspace();
+ if (partitionKey == null || keyspace == null)
+ {
+ return getFallbackQueryPlan(dataCenter);
+ }
+ final Set replicas = session.getMetadata().getTokenMap()
+ .orElseThrow(IllegalStateException::new)
+ .getReplicas(keyspace, partitionKey);
+ if (replicas.isEmpty())
+ {
+ return getFallbackQueryPlan(dataCenter);
+ }
+
+ return getQueryPlan(dataCenter, replicas);
+ }
+
+ private Queue getQueryPlan(final String datacenter, final Set replicas)
+ {
+ Queue queue = new ConcurrentLinkedQueue();
+ for (Node node : replicas)
+ {
+ if (node.getState().equals(NodeState.UP) && distance(node, datacenter).equals(NodeDistance.LOCAL))
+ {
+ queue.add(node);
+ }
+ }
+ // Skip if it was already a local replica
+ Queue fallbackQueue = getFallbackQueryPlan(datacenter);
+ fallbackQueue.stream().filter(n -> !queue.contains(n)).forEachOrdered(n -> queue.add(n));
+ return queue;
+ }
+
+ /**
+ * Return the {@link NodeDistance} for the provided host according to the selected data center.
+ *
+ * @param node
+ * the node of which to return the distance of.
+ * @param dataCenter
+ * the selected data center.
+ * @return the HostDistance to {@code host}.
+ */
+ public NodeDistance distance(final Node node, final String dataCenter)
+ {
+ String dc = getDc(node);
+ if (!getLocalDatacenter().equals(dc) && myAllowedDcs != null && !myAllowedDcs.contains(dc))
+ {
+ return NodeDistance.IGNORED;
+ }
+ if (dc.equals(dataCenter))
+ {
+ return NodeDistance.LOCAL;
+ }
+
+ CopyOnWriteArrayList dcNodes = myPerDcLiveNodes.get(dc);
+ if (dcNodes == null)
+ {
+ return NodeDistance.IGNORED;
+ }
+
+ return dcNodes.contains(node) ? NodeDistance.REMOTE : NodeDistance.IGNORED;
+ }
+
+ private Queue getFallbackQueryPlan(final String dataCenter)
+ {
+ CopyOnWriteArrayList localLiveNodes = null;
+ if (getLocalDatacenter().equals(dataCenter) || myAllowedDcs == null || myAllowedDcs.contains(dataCenter))
+ {
+ localLiveNodes = myPerDcLiveNodes.get(dataCenter);
+ }
+ final List nodes = localLiveNodes == null ? Collections.emptyList() : cloneList(localLiveNodes);
+ final int startIndex = myIndex.getAndIncrement();
+ int index = startIndex;
+ int remainingLocal = nodes.size();
+ Queue queue = new ConcurrentLinkedQueue<>();
+ while (remainingLocal > 0)
+ {
+ remainingLocal--;
+ int count = index++ % nodes.size();
+ if (count < 0)
+ {
+ count += nodes.size();
+ }
+ queue.add(nodes.get(count));
+ }
+ return queue;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static CopyOnWriteArrayList cloneList(final CopyOnWriteArrayList list)
+ {
+ return (CopyOnWriteArrayList) list.clone();
+ }
+
+ @Override
+ public final void onUp(final Node node)
+ {
+ super.onUp(node);
+ markAsUp(node);
+ }
+
+ private void markAsUp(final Node node)
+ {
+ String dc = getDc(node);
+
+ CopyOnWriteArrayList dcNodes = myPerDcLiveNodes.get(dc);
+ if (dcNodes == null)
+ {
+ CopyOnWriteArrayList newMap = new CopyOnWriteArrayList<>(Collections.singletonList(node));
+ dcNodes = myPerDcLiveNodes.putIfAbsent(dc, newMap);
+ // If we've successfully put our new node, we're good, otherwise we've been beaten so continue
+ if (dcNodes == null)
+ {
+ return;
+ }
+ }
+ dcNodes.addIfAbsent(node);
+ }
+
+ @Override
+ public final void onDown(final Node node)
+ {
+ super.onDown(node);
+ markAsDown(node);
+ }
+
+ private void markAsDown(final Node node)
+ {
+ CopyOnWriteArrayList dcNodes = myPerDcLiveNodes.get(getDc(node));
+ if (dcNodes != null)
+ {
+ dcNodes.remove(node);
+ }
+ }
+
+ private String getDc(final Node node)
+ {
+ String dc = node.getDatacenter();
+ return dc == null ? getLocalDatacenter() : dc;
+ }
+
+ @Override
+ public final void onAdd(final Node node)
+ {
+ super.onAdd(node);
+ markAsUp(node);
+ }
+
+ @Override
+ public final void onRemove(final Node node)
+ {
+ super.onRemove(node);
+ markAsDown(node);
+ }
+
+ /**
+ * Only for test purposes.
+ */
+ ConcurrentMap> getPerDcLiveNodes()
+ {
+ return myPerDcLiveNodes;
+ }
+}
+
diff --git a/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/DataCenterAwareStatement.java b/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/DataCenterAwareStatement.java
new file mode 100644
index 000000000..7f3511afc
--- /dev/null
+++ b/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/DataCenterAwareStatement.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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 com.ericsson.bss.cassandra.ecchronos.connection;
+
+import com.datastax.oss.driver.api.core.ConsistencyLevel;
+import com.datastax.oss.driver.api.core.CqlIdentifier;
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
+import com.datastax.oss.driver.api.core.cql.BoundStatement;
+import com.datastax.oss.driver.api.core.cql.PreparedStatement;
+import com.datastax.oss.driver.api.core.metadata.Node;
+import com.datastax.oss.driver.api.core.metadata.token.Token;
+import com.datastax.oss.driver.api.core.type.DataType;
+import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry;
+
+import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+
+public class DataCenterAwareStatement implements BoundStatement
+{
+ private final String myDataCenter;
+ private final BoundStatement myBoundStatement;
+
+ public DataCenterAwareStatement(final BoundStatement statement, final String dataCenter)
+ {
+ myBoundStatement = statement;
+ myDataCenter = dataCenter;
+ }
+
+ public final String getDataCenter()
+ {
+ return myDataCenter;
+ }
+
+ @Override
+ public final PreparedStatement getPreparedStatement()
+ {
+ return myBoundStatement.getPreparedStatement();
+ }
+
+ @Override
+ public final List getValues()
+ {
+ return myBoundStatement.getValues();
+ }
+
+ @Override
+ public final BoundStatement setExecutionProfileName(final String newConfigProfileName)
+ {
+ return myBoundStatement.setExecutionProfileName(newConfigProfileName);
+ }
+
+ @Override
+ public final BoundStatement setExecutionProfile(final DriverExecutionProfile newProfile)
+ {
+ return myBoundStatement.setExecutionProfile(newProfile);
+ }
+
+ @Override
+ public final BoundStatement setRoutingKeyspace(final CqlIdentifier newRoutingKeyspace)
+ {
+ return myBoundStatement.setRoutingKeyspace(newRoutingKeyspace);
+ }
+
+ @Override
+ public final BoundStatement setNode(final Node node)
+ {
+ return myBoundStatement.setNode(node);
+ }
+
+ @Override
+ public final BoundStatement setRoutingKey(final ByteBuffer newRoutingKey)
+ {
+ return myBoundStatement.setRoutingKey(newRoutingKey);
+ }
+
+ @Override
+ public final BoundStatement setRoutingToken(final Token newRoutingToken)
+ {
+ return myBoundStatement.setRoutingToken(newRoutingToken);
+ }
+
+ @Override
+ public final BoundStatement setCustomPayload(final Map newCustomPayload)
+ {
+ return myBoundStatement.setCustomPayload(newCustomPayload);
+ }
+
+ @Override
+ public final BoundStatement setIdempotent(final Boolean newIdempotence)
+ {
+ return myBoundStatement.setIdempotent(newIdempotence);
+ }
+
+ @Override
+ public final BoundStatement setTracing(final boolean newTracing)
+ {
+ return myBoundStatement.setTracing(newTracing);
+ }
+
+ @Override
+ public final long getQueryTimestamp()
+ {
+ return myBoundStatement.getQueryTimestamp();
+ }
+
+ @Override
+ public final BoundStatement setQueryTimestamp(final long newTimestamp)
+ {
+ return myBoundStatement.setQueryTimestamp(newTimestamp);
+ }
+
+ @Override
+ public final BoundStatement setTimeout(final Duration newTimeout)
+ {
+ return myBoundStatement.setTimeout(newTimeout);
+ }
+
+ @Override
+ public final ByteBuffer getPagingState()
+ {
+ return myBoundStatement.getPagingState();
+ }
+
+ @Override
+ public final BoundStatement setPagingState(final ByteBuffer newPagingState)
+ {
+ return myBoundStatement.setPagingState(newPagingState);
+ }
+
+ @Override
+ public final int getPageSize()
+ {
+ return myBoundStatement.getPageSize();
+ }
+
+ @Override
+ public final BoundStatement setPageSize(final int newPageSize)
+ {
+ return myBoundStatement.setPageSize(newPageSize);
+ }
+
+ @Override
+ public final ConsistencyLevel getConsistencyLevel()
+ {
+ return myBoundStatement.getConsistencyLevel();
+ }
+
+ @Override
+ public final BoundStatement setConsistencyLevel(final ConsistencyLevel newConsistencyLevel)
+ {
+ return myBoundStatement.setConsistencyLevel(newConsistencyLevel);
+ }
+
+ @Override
+ public final ConsistencyLevel getSerialConsistencyLevel()
+ {
+ return myBoundStatement.getSerialConsistencyLevel();
+ }
+
+ @Override
+ public final BoundStatement setSerialConsistencyLevel(final ConsistencyLevel newSerialConsistencyLevel)
+ {
+ return myBoundStatement.setSerialConsistencyLevel(newSerialConsistencyLevel);
+ }
+
+ @Override
+ public final boolean isTracing()
+ {
+ return myBoundStatement.isTracing();
+ }
+
+ @Override
+ public final int firstIndexOf(final String name)
+ {
+ return myBoundStatement.firstIndexOf(name);
+ }
+
+ @Override
+ public final int firstIndexOf(final CqlIdentifier id)
+ {
+ return myBoundStatement.firstIndexOf(id);
+ }
+
+ @Override
+ public final ByteBuffer getBytesUnsafe(final int i)
+ {
+ return myBoundStatement.getBytesUnsafe(i);
+ }
+
+ @Override
+ public final BoundStatement setBytesUnsafe(final int i, final ByteBuffer v)
+ {
+ return myBoundStatement.setBytesUnsafe(i, v);
+ }
+
+ @Override
+ public final int size()
+ {
+ return myBoundStatement.size();
+ }
+
+ @Override
+ public final DataType getType(final int i)
+ {
+ return myBoundStatement.getType(i);
+ }
+
+ @Override
+ public final CodecRegistry codecRegistry()
+ {
+ return myBoundStatement.codecRegistry();
+ }
+
+ @Override
+ public final ProtocolVersion protocolVersion()
+ {
+ return myBoundStatement.protocolVersion();
+ }
+
+ @Override
+ public final String getExecutionProfileName()
+ {
+ return myBoundStatement.getExecutionProfileName();
+ }
+
+ @Override
+ public final DriverExecutionProfile getExecutionProfile()
+ {
+ return myBoundStatement.getExecutionProfile();
+ }
+
+ @Override
+ public final CqlIdentifier getRoutingKeyspace()
+ {
+ return myBoundStatement.getRoutingKeyspace();
+ }
+
+ @Override
+ public final ByteBuffer getRoutingKey()
+ {
+ return myBoundStatement.getRoutingKey();
+ }
+
+ @Override
+ public final Token getRoutingToken()
+ {
+ return myBoundStatement.getRoutingToken();
+ }
+
+ @Override
+ public final Map getCustomPayload()
+ {
+ return myBoundStatement.getCustomPayload();
+ }
+
+ @Override
+ public final Boolean isIdempotent()
+ {
+ return myBoundStatement.isIdempotent();
+ }
+
+ @Override
+ public final Duration getTimeout()
+ {
+ return myBoundStatement.getTimeout();
+ }
+
+ @Override
+ public final Node getNode()
+ {
+ return myBoundStatement.getNode();
+ }
+}
+
diff --git a/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/DistributedJmxConnectionProvider.java b/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/DistributedJmxConnectionProvider.java
new file mode 100644
index 000000000..7fad4ba98
--- /dev/null
+++ b/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/DistributedJmxConnectionProvider.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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 com.ericsson.bss.cassandra.ecchronos.connection;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.management.remote.JMXConnector;
+
+public interface DistributedJmxConnectionProvider extends Closeable
+{
+ ConcurrentHashMap getJmxConnections();
+
+ JMXConnector getJmxConnector(UUID nodeID);
+
+ @Override
+ default void close() throws IOException
+ {
+ }
+
+ void close(UUID nodeID) throws IOException;
+}
diff --git a/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/DistributedNativeConnectionProvider.java b/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/DistributedNativeConnectionProvider.java
new file mode 100644
index 000000000..45dfb2ac2
--- /dev/null
+++ b/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/DistributedNativeConnectionProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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 com.ericsson.bss.cassandra.ecchronos.connection;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+
+import com.datastax.oss.driver.api.core.CqlSession;
+import com.datastax.oss.driver.api.core.metadata.Node;
+
+public interface DistributedNativeConnectionProvider extends Closeable
+{
+ CqlSession getCqlSession();
+
+ List getNodes();
+
+ @Override
+ default void close() throws IOException
+ {
+ }
+}
diff --git a/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/StatementDecorator.java b/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/StatementDecorator.java
new file mode 100644
index 000000000..f3c0a49c5
--- /dev/null
+++ b/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/StatementDecorator.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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 com.ericsson.bss.cassandra.ecchronos.connection;
+
+import com.datastax.oss.driver.api.core.cql.Statement;
+
+public interface StatementDecorator
+{
+ /**
+ * Decorates a statement before sending it over to the server.
+ * @param statement The original statement
+ * @return The decorated statement
+ */
+ Statement apply(Statement statement);
+}
diff --git a/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/package-info.java b/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/package-info.java
new file mode 100644
index 000000000..1f067bbd1
--- /dev/null
+++ b/connection/src/main/java/com/ericsson/bss/cassandra/ecchronos/connection/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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.
+ */
+/**
+ * Contains the API for outgoing connections (CQL and JMX) towards Cassandra.
+ */
+package com.ericsson.bss.cassandra.ecchronos.connection;
diff --git a/connection/src/test/java/com/ericsson/bss/cassandra/ecchronos/connection/TestDataCenterAwarePolicy.java b/connection/src/test/java/com/ericsson/bss/cassandra/ecchronos/connection/TestDataCenterAwarePolicy.java
new file mode 100644
index 000000000..890d2be69
--- /dev/null
+++ b/connection/src/test/java/com/ericsson/bss/cassandra/ecchronos/connection/TestDataCenterAwarePolicy.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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 com.ericsson.bss.cassandra.ecchronos.connection;
+
+import com.datastax.oss.driver.api.core.ConsistencyLevel;
+import com.datastax.oss.driver.api.core.CqlIdentifier;
+import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
+import com.datastax.oss.driver.api.core.config.DriverConfig;
+import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
+import com.datastax.oss.driver.api.core.cql.BoundStatement;
+import com.datastax.oss.driver.api.core.cql.SimpleStatement;
+import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy;
+import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance;
+import com.datastax.oss.driver.api.core.metadata.Metadata;
+import com.datastax.oss.driver.api.core.metadata.Node;
+import com.datastax.oss.driver.api.core.metadata.NodeState;
+import com.datastax.oss.driver.api.core.metadata.TokenMap;
+import com.datastax.oss.driver.api.core.session.Session;
+import com.datastax.oss.driver.internal.core.ConsistencyLevelRegistry;
+import com.datastax.oss.driver.internal.core.context.InternalDriverContext;
+import com.datastax.oss.driver.internal.core.metadata.MetadataManager;
+import java.util.*;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.Silent.class)
+public class TestDataCenterAwarePolicy
+{
+ private final String myLocalDc = "DC1";
+ private final String myRemoteDc = "DC2";
+ private final String[] myAllowedDcs = {"DC1", "DC2"};
+
+ @Mock
+ private Session mySessionMock;
+
+ @Mock
+ private Metadata myMetadataMock;
+
+ @Mock
+ private TokenMap myTokenMapMock;
+
+ @Mock
+ private InternalDriverContext myDriverContextMock;
+
+ @Mock
+ private DriverConfig myDriverConfigMock;
+
+ @Mock
+ private DriverExecutionProfile myDriverExecutionProfileMock;
+
+ @Mock
+ private ConsistencyLevelRegistry myConsistencyLevelRegistryMock;
+
+ @Mock
+ private MetadataManager myMetadataManagerMock;
+
+ @Mock
+ private LoadBalancingPolicy.DistanceReporter myDistanceReporterMock;
+
+ @Mock
+ private Node myNodeDC1Mock;
+
+ @Mock
+ private Node myNodeDC2Mock;
+
+ @Mock
+ private Node myNodeDC3Mock;
+
+ @Mock
+ private Node myNodeNoDCMock;
+
+ @Mock
+ private Node myNodeNotDC3Mock;
+
+ private Map myNodes = new HashMap<>();
+
+ @Before
+ public void setup()
+ {
+ when(mySessionMock.getMetadata()).thenReturn(myMetadataMock);
+ when(myMetadataMock.getTokenMap()).thenReturn(Optional.of(myTokenMapMock));
+
+ when(myNodeDC1Mock.getDatacenter()).thenReturn("DC1");
+ when(myNodeDC1Mock.getState()).thenReturn(NodeState.UP);
+ when(myNodeDC2Mock.getDatacenter()).thenReturn("DC2");
+ when(myNodeDC2Mock.getState()).thenReturn(NodeState.UP);
+ when(myNodeDC3Mock.getDatacenter()).thenReturn("DC3");
+ when(myNodeDC3Mock.getState()).thenReturn(NodeState.UP);
+ when(myNodeNoDCMock.getDatacenter()).thenReturn("no DC");
+ when(myNodeNoDCMock.getState()).thenReturn(NodeState.UP);
+ when(myNodeNotDC3Mock.getDatacenter()).thenReturn("DC3");
+ when(myNodeNotDC3Mock.getState()).thenReturn(NodeState.UP);
+
+ myNodes.put(UUID.randomUUID(), myNodeDC1Mock);
+ myNodes.put(UUID.randomUUID(), myNodeDC2Mock);
+ myNodes.put(UUID.randomUUID(), myNodeDC3Mock);
+ when(myDriverContextMock.getConfig()).thenReturn(myDriverConfigMock);
+ when(myDriverContextMock.getLocalDatacenter(any())).thenReturn(myLocalDc);
+ when(myDriverContextMock.getMetadataManager()).thenReturn(myMetadataManagerMock);
+ when(myMetadataManagerMock.getMetadata()).thenReturn(myMetadataMock);
+ when(myDriverConfigMock.getProfile(any(String.class))).thenReturn(myDriverExecutionProfileMock);
+ when(myDriverConfigMock.getDefaultProfile()).thenReturn(myDriverExecutionProfileMock);
+ when(myDriverExecutionProfileMock.getName()).thenReturn("unittest");
+ when(myDriverExecutionProfileMock.getInt(DefaultDriverOption.LOAD_BALANCING_DC_FAILOVER_MAX_NODES_PER_REMOTE_DC)).thenReturn(999);
+ when(myDriverExecutionProfileMock.getBoolean(DefaultDriverOption.LOAD_BALANCING_DC_FAILOVER_ALLOW_FOR_LOCAL_CONSISTENCY_LEVELS)).thenReturn(false);
+ when(myDriverExecutionProfileMock.getString(DefaultDriverOption.REQUEST_CONSISTENCY)).thenReturn("LOCAL_QUORUM");
+ when(myDriverContextMock.getConsistencyLevelRegistry()).thenReturn(myConsistencyLevelRegistryMock);
+ when(myConsistencyLevelRegistryMock.nameToLevel(any(String.class))).thenReturn(ConsistencyLevel.LOCAL_QUORUM);
+ when(myDriverConfigMock.getDefaultProfile().getStringList(CustomDriverOption.PartitionAwarePolicyAllowedDCs)).thenReturn(null);
+ }
+
+ @Test
+ public void testDistanceHost()
+ {
+// DataCenterAwarePolicy.setAllowedDcs(null);
+ DataCenterAwarePolicy policy = new DataCenterAwarePolicy(myDriverContextMock, "");
+ policy.init(myNodes, myDistanceReporterMock);
+
+ NodeDistance distance1 = policy.distance(myNodeDC1Mock, myLocalDc);
+ NodeDistance distance2 = policy.distance(myNodeDC2Mock, myLocalDc);
+ NodeDistance distance3 = policy.distance(myNodeDC3Mock, myLocalDc);
+ NodeDistance distance4 = policy.distance(myNodeNoDCMock, myLocalDc);
+ NodeDistance distance5 = policy.distance(myNodeNotDC3Mock, myLocalDc);
+
+ assertThat(distance1).isEqualTo(NodeDistance.LOCAL);
+ assertThat(distance2).isEqualTo(NodeDistance.REMOTE);
+ assertThat(distance3).isEqualTo(NodeDistance.REMOTE);
+ assertThat(distance4).isEqualTo(NodeDistance.IGNORED);
+ assertThat(distance5).isEqualTo(NodeDistance.IGNORED);
+ }
+
+ @Test
+ public void testDistanceHostWithAllowedDcs()
+ {
+ when(myDriverConfigMock.getDefaultProfile().getStringList(CustomDriverOption.PartitionAwarePolicyAllowedDCs)).thenReturn(List.of(myAllowedDcs));
+
+ DataCenterAwarePolicy policy = new DataCenterAwarePolicy(myDriverContextMock, "");
+ policy.init(myNodes, myDistanceReporterMock);
+
+ NodeDistance distance1 = policy.distance(myNodeDC3Mock, myLocalDc);
+ NodeDistance distance2 = policy.distance(myNodeDC1Mock, myLocalDc);
+ NodeDistance distance3 = policy.distance(myNodeDC2Mock, myLocalDc);
+
+ assertThat(distance1).isEqualTo(NodeDistance.IGNORED);
+ assertThat(distance2).isEqualTo(NodeDistance.LOCAL);
+ assertThat(distance3).isEqualTo(NodeDistance.REMOTE);
+ }
+
+ @Test
+ public void testNewQueryPlanWithNotPartitionAwareStatement()
+ {
+ SimpleStatement simpleStatement = SimpleStatement.newInstance("SELECT *");
+
+ DataCenterAwarePolicy policy = new DataCenterAwarePolicy(myDriverContextMock, "");
+
+ policy.init(myNodes, myDistanceReporterMock);
+
+ Set nodes = new HashSet<>();
+ nodes.add(myNodeDC1Mock);
+ when(myTokenMapMock.getReplicas(any(CqlIdentifier.class), any(ByteBuffer.class))).thenReturn(nodes);
+ Queue queue = policy.newQueryPlan(simpleStatement, mySessionMock);
+
+ assertThat(queue.isEmpty()).isFalse();
+ assertThat(queue.poll()).isEqualTo(myNodeDC1Mock);
+ assertThat(queue.isEmpty()).isTrue();
+ }
+
+ @Test
+ public void testNewQueryPlanWithPartitionAwareStatementLocalDc()
+ {
+ BoundStatement boundStatement = mock(BoundStatement.class);
+ when(boundStatement.getRoutingKeyspace()).thenReturn(CqlIdentifier.fromInternal("foo"));
+ when(boundStatement.getRoutingKey()).thenReturn(ByteBuffer.wrap("foo".getBytes()));
+ DataCenterAwareStatement partitionAwareStatement = new DataCenterAwareStatement(boundStatement, myLocalDc);
+
+ DataCenterAwarePolicy policy = new DataCenterAwarePolicy(myDriverContextMock, "");
+
+ policy.init(myNodes, myDistanceReporterMock);
+
+ Set nodes = new HashSet<>();
+ nodes.add(myNodeDC1Mock);
+ when(myTokenMapMock.getReplicas(any(CqlIdentifier.class), any(ByteBuffer.class))).thenReturn(nodes);
+
+ Queue queue = policy.newQueryPlan(partitionAwareStatement, mySessionMock);
+
+ assertThat(queue.isEmpty()).isFalse();
+ assertThat(queue.poll()).isEqualTo(myNodeDC1Mock);
+ assertThat(queue.isEmpty()).isTrue();
+ }
+
+ @Test
+ public void testNewQueryPlanWithPartitionAwareStatementRemoteDc()
+ {
+ BoundStatement boundStatement = mock(BoundStatement.class);
+ when(boundStatement.getRoutingKeyspace()).thenReturn(CqlIdentifier.fromInternal("foo"));
+ when(boundStatement.getRoutingKey()).thenReturn(ByteBuffer.wrap("foo".getBytes()));
+ DataCenterAwareStatement partitionAwareStatement = new DataCenterAwareStatement(boundStatement, myRemoteDc);
+
+ DataCenterAwarePolicy policy = new DataCenterAwarePolicy(myDriverContextMock, "");
+
+ policy.init(myNodes, myDistanceReporterMock);
+
+ Set nodes = new HashSet<>();
+ nodes.add(myNodeDC1Mock);
+ when(myTokenMapMock.getReplicas(any(CqlIdentifier.class), any(ByteBuffer.class))).thenReturn(nodes);
+
+ Queue queue = policy.newQueryPlan(partitionAwareStatement, mySessionMock);
+
+ assertThat(queue.isEmpty()).isFalse();
+ assertThat(queue.poll()).isEqualTo(myNodeDC2Mock);
+ assertThat(queue.isEmpty()).isTrue();
+ }
+
+ @Test
+ public void testOnUp()
+ {
+ DataCenterAwarePolicy policy = new DataCenterAwarePolicy(myDriverContextMock, "");
+ policy.init(myNodes, myDistanceReporterMock);
+
+ CopyOnWriteArrayList nodesInDC = policy.getPerDcLiveNodes().get("DC3");
+ assertThat(nodesInDC.contains(myNodeDC3Mock));
+ assertThat(nodesInDC.size()).isEqualTo(1);
+
+ policy.onUp(myNodeNotDC3Mock);
+
+ nodesInDC = policy.getPerDcLiveNodes().get("DC3");
+ assertThat(nodesInDC.contains(myNodeDC3Mock));
+ assertThat(nodesInDC.contains(myNodeNotDC3Mock));
+ assertThat(nodesInDC.size()).isEqualTo(2);
+ }
+
+ @Test
+ public void testOnUpTwice()
+ {
+ DataCenterAwarePolicy policy = new DataCenterAwarePolicy(myDriverContextMock, "");
+ policy.init(myNodes, myDistanceReporterMock);
+
+ CopyOnWriteArrayList nodesInDC = policy.getPerDcLiveNodes().get("DC3");
+ assertThat(nodesInDC.contains(myNodeDC3Mock));
+ assertThat(nodesInDC.size()).isEqualTo(1);
+
+ policy.onUp(myNodeNotDC3Mock);
+ policy.onUp(myNodeNotDC3Mock);
+
+ nodesInDC = policy.getPerDcLiveNodes().get("DC3");
+ assertThat(nodesInDC.contains(myNodeDC3Mock));
+ assertThat(nodesInDC.contains(myNodeNotDC3Mock));
+ assertThat(nodesInDC.size()).isEqualTo(2);
+ }
+
+ @Test
+ public void testOnDown()
+ {
+ DataCenterAwarePolicy policy = new DataCenterAwarePolicy(myDriverContextMock, "");
+ policy.init(myNodes, myDistanceReporterMock);
+ CopyOnWriteArrayList nodesInDC = policy.getPerDcLiveNodes().get("DC3");
+ assertThat(nodesInDC.contains(myNodeDC3Mock));
+ assertThat(nodesInDC.size()).isEqualTo(1);
+
+ policy.onDown(myNodeDC3Mock);
+
+ nodesInDC = policy.getPerDcLiveNodes().get("DC3");
+ assertThat(nodesInDC).isEmpty();
+ }
+
+ @Test
+ public void testOnDownTwice()
+ {
+ DataCenterAwarePolicy policy = new DataCenterAwarePolicy(myDriverContextMock, "");
+ policy.init(myNodes, myDistanceReporterMock);
+ CopyOnWriteArrayList nodesInDC = policy.getPerDcLiveNodes().get("DC3");
+ assertThat(nodesInDC.contains(myNodeDC3Mock));
+ assertThat(nodesInDC.size()).isEqualTo(1);
+
+ policy.onDown(myNodeDC3Mock);
+ policy.onDown(myNodeDC3Mock);
+
+ nodesInDC = policy.getPerDcLiveNodes().get("DC3");
+ assertThat(nodesInDC).isEmpty();
+ }
+
+ @Test
+ public void testOnAdd()
+ {
+ DataCenterAwarePolicy policy = new DataCenterAwarePolicy(myDriverContextMock, "");
+ policy.init(myNodes, myDistanceReporterMock);
+
+ CopyOnWriteArrayList nodesInDC = policy.getPerDcLiveNodes().get("DC3");
+ assertThat(nodesInDC.contains(myNodeDC3Mock));
+ assertThat(nodesInDC.size()).isEqualTo(1);
+
+ policy.onAdd(myNodeNotDC3Mock);
+
+ nodesInDC = policy.getPerDcLiveNodes().get("DC3");
+ assertThat(nodesInDC.contains(myNodeDC3Mock));
+ assertThat(nodesInDC.contains(myNodeNotDC3Mock));
+ assertThat(nodesInDC.size()).isEqualTo(2);
+ }
+
+ @Test
+ public void testOnRemove()
+ {
+ DataCenterAwarePolicy policy = new DataCenterAwarePolicy(myDriverContextMock, "");
+ policy.init(myNodes, myDistanceReporterMock);
+ CopyOnWriteArrayList nodesInDC = policy.getPerDcLiveNodes().get("DC3");
+ assertThat(nodesInDC.contains(myNodeDC3Mock));
+ assertThat(nodesInDC.size()).isEqualTo(1);
+
+ policy.onRemove(myNodeDC3Mock);
+
+ nodesInDC = policy.getPerDcLiveNodes().get("DC3");
+ assertThat(nodesInDC).isEmpty();
+ }
+}
diff --git a/connection/src/test/java/com/ericsson/bss/cassandra/ecchronos/connection/TestDataCenterAwareStatement.java b/connection/src/test/java/com/ericsson/bss/cassandra/ecchronos/connection/TestDataCenterAwareStatement.java
new file mode 100644
index 000000000..292234c4f
--- /dev/null
+++ b/connection/src/test/java/com/ericsson/bss/cassandra/ecchronos/connection/TestDataCenterAwareStatement.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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 com.ericsson.bss.cassandra.ecchronos.connection;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+
+public class TestDataCenterAwareStatement
+{
+ @Test
+ public void testGetDataCenter()
+ {
+ String dataCenter = "DC1";
+
+ DataCenterAwareStatement statement = new DataCenterAwareStatement(null, dataCenter);
+
+ assertThat(statement.getDataCenter()).isEqualTo(dataCenter);
+ }
+}
diff --git a/data/pom.xml b/data/pom.xml
new file mode 100644
index 000000000..b765ab205
--- /dev/null
+++ b/data/pom.xml
@@ -0,0 +1,171 @@
+
+
+
+ 4.0.0
+
+ com.ericsson.bss.cassandra.ecchronos
+ agent
+ 1.0.0-SNAPSHOT
+
+
+ data
+
+
+
+
+ com.ericsson.bss.cassandra.ecchronos
+ connection
+ ${project.version}
+
+
+
+ io.micrometer
+ micrometer-core
+
+
+
+
+ jakarta.validation
+ jakarta.validation-api
+
+
+
+
+ com.datastax.oss
+ java-driver-core
+
+
+
+ com.datastax.oss
+ java-driver-query-builder
+
+
+
+ com.google.guava
+ guava
+
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ test
+
+
+
+ commons-io
+ commons-io
+ test
+
+
+
+ org.awaitility
+ awaitility
+ test
+
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+ net.jcip
+ jcip-annotations
+ test
+
+
+
+ nl.jqno.equalsverifier
+ equalsverifier
+ test
+
+
+
+
+ org.springframework
+ spring-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-test
+ test
+
+
+ org.testcontainers
+ cassandra
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ dependencies
+ generate-sources
+
+ tree
+
+
+ compile
+ target/dependency-tree.txt
+
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+
+ true
+ META-INF
+
+ com.ericsson.bss.cassandra.ecchronos.data.*
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/enums/NodeStatus.java b/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/enums/NodeStatus.java
new file mode 100644
index 000000000..706a5998a
--- /dev/null
+++ b/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/enums/NodeStatus.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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 com.ericsson.bss.cassandra.ecchronos.data.enums;
+
+/**
+ * The status of nodes after creating jmx connection.
+ */
+public enum NodeStatus
+{
+ UNAVAILABLE,
+ AVAILABLE
+}
diff --git a/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/enums/package-info.java b/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/enums/package-info.java
new file mode 100644
index 000000000..e643c759a
--- /dev/null
+++ b/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/enums/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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.
+ */
+/**
+ * Contains enums definitions for data configs.
+ */
+package com.ericsson.bss.cassandra.ecchronos.data.enums;
diff --git a/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/exceptions/EcChronosException.java b/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/exceptions/EcChronosException.java
new file mode 100644
index 000000000..ee5e65983
--- /dev/null
+++ b/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/exceptions/EcChronosException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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 com.ericsson.bss.cassandra.ecchronos.data.exceptions;
+
+/**
+ * Generic exception thrown by schedulers to signal that something went wrong.
+ */
+public class EcChronosException extends Exception
+{
+ private static final long serialVersionUID = 1148561336907867613L;
+
+ public EcChronosException(final String message)
+ {
+ super(message);
+ }
+
+ public EcChronosException(final Throwable t)
+ {
+ super(t);
+ }
+
+ public EcChronosException(final String message, final Throwable t)
+ {
+ super(message, t);
+ }
+}
diff --git a/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/exceptions/package-info.java b/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/exceptions/package-info.java
new file mode 100644
index 000000000..6a241f019
--- /dev/null
+++ b/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/exceptions/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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.
+ */
+/**
+ * Contains exceptions related to I/O in data tables.
+ */
+package com.ericsson.bss.cassandra.ecchronos.data.exceptions;
diff --git a/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/sync/EccNodesSync.java b/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/sync/EccNodesSync.java
new file mode 100644
index 000000000..4b70b0878
--- /dev/null
+++ b/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/sync/EccNodesSync.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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 com.ericsson.bss.cassandra.ecchronos.data.sync;
+
+import com.datastax.oss.driver.api.core.ConsistencyLevel;
+import com.datastax.oss.driver.api.core.CqlSession;
+import com.datastax.oss.driver.api.core.cql.BoundStatement;
+import com.datastax.oss.driver.api.core.cql.PreparedStatement;
+import com.datastax.oss.driver.api.core.cql.ResultSet;
+import com.datastax.oss.driver.api.core.metadata.Node;
+import com.datastax.oss.driver.api.querybuilder.QueryBuilder;
+import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting;
+import com.ericsson.bss.cassandra.ecchronos.data.enums.NodeStatus;
+import com.ericsson.bss.cassandra.ecchronos.data.exceptions.EcChronosException;
+import com.google.common.base.Preconditions;
+
+import java.net.UnknownHostException;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker;
+
+/**
+ * CQL Definition for nodes_sync table. CREATE TABLE ecchronos_agent.nodes_sync ( ecchronos_id TEXT, datacenter_name
+ * TEXT, node_id UUID, node_endpoint TEXT, node_status TEXT, last_connection TIMESTAMP, next_connection TIMESTAMP,
+ * PRIMARY KEY ( ecchronos_id, datacenter_name, node_id ) ) WITH CLUSTERING ORDER BY( datacenter_name DESC, node_id DESC
+ * );
+ */
+public final class EccNodesSync
+{
+ private static final Logger LOG = LoggerFactory.getLogger(EccNodesSync.class);
+
+ private static final Integer DEFAULT_CONNECTION_DELAY_IN_MINUTES = 30;
+ private static final String COLUMN_ECCHRONOS_ID = "ecchronos_id";
+ private static final String COLUMN_DC_NAME = "datacenter_name";
+ private static final String COLUMN_NODE_ID = "node_id";
+ private static final String COLUMN_NODE_ENDPOINT = "node_endpoint";
+ private static final String COLUMN_NODE_STATUS = "node_status";
+ private static final String COLUMN_LAST_CONNECTION = "last_connection";
+ private static final String COLUMN_NEXT_CONNECTION = "next_connection";
+
+ private static final String KEYSPACE_NAME = "ecchronos";
+ private static final String TABLE_NAME = "nodes_sync";
+
+ private final CqlSession mySession;
+ private final List myNodesList;
+ private final String ecChronosID;
+
+ private final PreparedStatement myCreateStatement;
+ private final PreparedStatement myUpdateStatusStatement;
+
+ private EccNodesSync(final Builder builder) throws UnknownHostException
+ {
+ mySession = Preconditions.checkNotNull(builder.mySession, "Session cannot be null");
+ myNodesList = Preconditions
+ .checkNotNull(builder.initialNodesList, "Nodes list cannot be null");
+ myCreateStatement = mySession.prepare(QueryBuilder.insertInto(KEYSPACE_NAME, TABLE_NAME)
+ .value(COLUMN_ECCHRONOS_ID, bindMarker())
+ .value(COLUMN_DC_NAME, bindMarker())
+ .value(COLUMN_NODE_ENDPOINT, bindMarker())
+ .value(COLUMN_NODE_STATUS, bindMarker())
+ .value(COLUMN_LAST_CONNECTION, bindMarker())
+ .value(COLUMN_NEXT_CONNECTION, bindMarker())
+ .value(COLUMN_NODE_ID, bindMarker())
+ .build()
+ .setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM));
+ myUpdateStatusStatement = mySession.prepare(QueryBuilder.update(KEYSPACE_NAME, TABLE_NAME)
+ .setColumn(COLUMN_NODE_STATUS, bindMarker())
+ .setColumn(COLUMN_LAST_CONNECTION, bindMarker())
+ .setColumn(COLUMN_NEXT_CONNECTION, bindMarker())
+ .whereColumn(COLUMN_ECCHRONOS_ID).isEqualTo(bindMarker())
+ .whereColumn(COLUMN_DC_NAME).isEqualTo(bindMarker())
+ .whereColumn(COLUMN_NODE_ID).isEqualTo(bindMarker())
+ .build()
+ .setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM));
+ ecChronosID = builder.myEcchronosID;
+ }
+
+ public void acquireNodes() throws EcChronosException
+ {
+ if (myNodesList.isEmpty())
+ {
+ throw new EcChronosException("Cannot Acquire Nodes because there is no nodes to be acquired");
+ }
+ for (Node node : myNodesList)
+ {
+ LOG.info(
+ "Preparing to acquire node {} with endpoint {} and Datacenter {}",
+ node.getHostId(),
+ node.getEndPoint(),
+ node.getDatacenter());
+ ResultSet tmpResultSet = acquireNode(node);
+ if (tmpResultSet.wasApplied())
+ {
+ LOG.info("Node successfully acquired by instance {}", ecChronosID);
+ }
+ else
+ {
+ LOG.error("Unable to acquire node {}", node.getHostId());
+ }
+ }
+ }
+
+ private ResultSet acquireNode(final Node node)
+ {
+ return insertNodeInfo(
+ node.getDatacenter(),
+ node.getEndPoint().toString(),
+ node.getState().toString(),
+ Instant.now(),
+ Instant.now().plus(DEFAULT_CONNECTION_DELAY_IN_MINUTES, ChronoUnit.MINUTES),
+ node.getHostId());
+ }
+
+ @VisibleForTesting
+ public ResultSet verifyAcquireNode(final Node node)
+ {
+ return acquireNode(node);
+ }
+
+ private ResultSet insertNodeInfo(
+ final String datacenterName,
+ final String nodeEndpoint,
+ final String nodeStatus,
+ final Instant lastConnection,
+ final Instant nextConnection,
+ final UUID nodeID
+ )
+ {
+ BoundStatement insertNodeSyncInfo = myCreateStatement.bind(ecChronosID,
+ datacenterName, nodeEndpoint, nodeStatus, lastConnection, nextConnection, nodeID);
+ return execute(insertNodeSyncInfo);
+ }
+
+ public ResultSet updateNodeStatus(
+ final NodeStatus nodeStatus,
+ final String datacenterName,
+ final UUID nodeID
+ )
+ {
+ ResultSet tmpResultSet = updateNodeStateStatement(nodeStatus, datacenterName, nodeID);
+ if (tmpResultSet.wasApplied())
+ {
+ LOG.info("Node {} successfully updated", nodeID);
+ }
+ else
+ {
+ LOG.error("Unable to update node {}", nodeID);
+ }
+ return tmpResultSet;
+ }
+
+ private ResultSet updateNodeStateStatement(
+ final NodeStatus nodeStatus,
+ final String datacenterName,
+ final UUID nodeID
+ )
+ {
+ BoundStatement updateNodeStatus = myUpdateStatusStatement.bind(
+ nodeStatus.toString(),
+ Instant.now(),
+ Instant.now().plus(DEFAULT_CONNECTION_DELAY_IN_MINUTES, ChronoUnit.MINUTES),
+ ecChronosID,
+ datacenterName,
+ nodeID
+ );
+ return execute(updateNodeStatus);
+ }
+
+ @VisibleForTesting
+ public ResultSet verifyInsertNodeInfo(
+ final String datacenterName,
+ final String nodeEndpoint,
+ final String nodeStatus,
+ final Instant lastConnection,
+ final Instant nextConnection,
+ final UUID nodeID
+ )
+ {
+ return insertNodeInfo(
+ datacenterName,
+ nodeEndpoint,
+ nodeStatus,
+ lastConnection,
+ nextConnection,
+ nodeID
+ );
+ }
+
+ public ResultSet execute(final BoundStatement statement)
+ {
+ return mySession.execute(statement);
+ }
+
+ public static Builder newBuilder()
+ {
+ return new Builder();
+ }
+
+ public static class Builder
+ {
+ private CqlSession mySession;
+ private List initialNodesList;
+ private String myEcchronosID;
+
+ /**
+ * Builds EccNodesSync with session.
+ *
+ * @param session
+ * Session object
+ * @return Builder
+ */
+ public Builder withSession(final CqlSession session)
+ {
+ this.mySession = session;
+ return this;
+ }
+
+ /**
+ * Builds EccNodesSync with nodes list.
+ *
+ * @param nodes
+ * nodes list
+ * @return Builder
+ */
+ public Builder withInitialNodesList(final List nodes)
+ {
+ this.initialNodesList = nodes;
+ return this;
+ }
+
+ /**
+ * Builds EccNodesSync with ecchronosID.
+ *
+ * @param echronosID
+ * ecchronos ID generated by BeanConfigurator.
+ * @return Builder
+ */
+ public Builder withEcchronosID(final String echronosID)
+ {
+ this.myEcchronosID = echronosID;
+ return this;
+ }
+
+ /**
+ * Builds EccNodesSync.
+ *
+ * @return Builder
+ * @throws UnknownHostException
+ */
+ public EccNodesSync build() throws UnknownHostException
+ {
+ return new EccNodesSync(this);
+ }
+ }
+}
+
diff --git a/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/sync/package-info.java b/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/sync/package-info.java
new file mode 100644
index 000000000..ed86a2740
--- /dev/null
+++ b/data/src/main/java/com/ericsson/bss/cassandra/ecchronos/data/sync/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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.
+ */
+/**
+ * Contains the representation of nodes_sync table.
+ */
+package com.ericsson.bss.cassandra.ecchronos.data.sync;
diff --git a/data/src/test/java/com/ericsson/bss/cassandra/ecchronos/data/sync/TestEccNodesSync.java b/data/src/test/java/com/ericsson/bss/cassandra/ecchronos/data/sync/TestEccNodesSync.java
new file mode 100644
index 000000000..daf0ea662
--- /dev/null
+++ b/data/src/test/java/com/ericsson/bss/cassandra/ecchronos/data/sync/TestEccNodesSync.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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 com.ericsson.bss.cassandra.ecchronos.data.sync;
+
+import com.datastax.oss.driver.api.core.cql.ResultSet;
+import com.datastax.oss.driver.api.core.cql.SimpleStatement;
+import com.datastax.oss.driver.api.core.metadata.Node;
+
+import com.ericsson.bss.cassandra.ecchronos.data.enums.NodeStatus;
+import com.ericsson.bss.cassandra.ecchronos.data.exceptions.EcChronosException;
+import com.ericsson.bss.cassandra.ecchronos.data.utils.AbstractCassandraTest;
+import java.io.IOException;
+import net.jcip.annotations.NotThreadSafe;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.UnknownHostException;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+
+import static org.junit.Assert.*;
+
+@NotThreadSafe
+public class TestEccNodesSync extends AbstractCassandraTest
+{
+ private static final String ECCHRONOS_KEYSPACE = "ecchronos";
+
+ private EccNodesSync eccNodesSync;
+ private final List nodesList = getNativeConnectionProvider().getNodes();
+ private final UUID nodeID = UUID.randomUUID();
+ private final String datacenterName = "datacenter1";
+
+ @Before
+ public void setup() throws IOException
+ {
+ mySession.execute(String.format(
+ "CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'NetworkTopologyStrategy', 'DC1': 1}",
+ ECCHRONOS_KEYSPACE));
+ String query = String.format(
+ "CREATE TABLE IF NOT EXISTS %s.nodes_sync(" +
+ "ecchronos_id TEXT, " +
+ "datacenter_name TEXT, " +
+ "node_id UUID, " +
+ "node_endpoint TEXT, " +
+ "node_status TEXT, " +
+ "last_connection TIMESTAMP, " +
+ "next_connection TIMESTAMP, " +
+ "PRIMARY KEY(ecchronos_id, datacenter_name, node_id)) " +
+ "WITH CLUSTERING ORDER BY( datacenter_name DESC, node_id DESC);",
+ ECCHRONOS_KEYSPACE
+ );
+
+ mySession.execute(query);
+
+ eccNodesSync = EccNodesSync.newBuilder()
+ .withSession(mySession)
+ .withInitialNodesList(nodesList)
+ .withEcchronosID("ecchronos-test").build();
+ }
+
+ @After
+ public void testCleanup()
+ {
+ mySession.execute(SimpleStatement.newInstance(
+ String.format("TRUNCATE %s.%s", ECCHRONOS_KEYSPACE, "nodes_sync")));
+ }
+
+ @Test
+ public void testAcquireNode()
+ {
+ ResultSet result = eccNodesSync.verifyAcquireNode(nodesList.get(0));
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testInsertNodeInfo()
+ {
+
+ String nodeEndpoint = "127.0.0.1";
+ String nodeStatus = "UP";
+ Instant lastConnection = Instant.now();
+ Instant nextConnection = lastConnection.plus(30, ChronoUnit.MINUTES);
+
+ ResultSet result = eccNodesSync.verifyInsertNodeInfo(datacenterName, nodeEndpoint,
+ nodeStatus, lastConnection, nextConnection, nodeID);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testUpdateNodeStatus()
+ {
+ ResultSet resultSet = eccNodesSync.updateNodeStatus(NodeStatus.AVAILABLE, datacenterName, nodeID);
+ assertNotNull(resultSet);
+ assertTrue(resultSet.wasApplied());
+ }
+
+ @Test
+ public void testEccNodesWithNullList()
+ {
+ EccNodesSync.Builder tmpEccNodesSyncBuilder = EccNodesSync.newBuilder()
+ .withSession(mySession)
+ .withInitialNodesList(null);
+ NullPointerException exception = assertThrows(
+ NullPointerException.class, tmpEccNodesSyncBuilder::build);
+ assertEquals("Nodes list cannot be null", exception.getMessage());
+ }
+
+ @Test
+ public void testAcquiredNodesWithEmptyList() throws UnknownHostException
+ {
+ EccNodesSync tmpEccNodesSync = EccNodesSync.newBuilder()
+ .withSession(mySession)
+ .withInitialNodesList(new ArrayList<>()).build();
+ EcChronosException exception = assertThrows(
+ EcChronosException.class, tmpEccNodesSync::acquireNodes);
+ assertEquals(
+ "Cannot Acquire Nodes because there is no nodes to be acquired",
+ exception.getMessage());
+ }
+
+ @Test
+ public void testEccNodesWithNullSession()
+ {
+ EccNodesSync.Builder tmpEccNodesSyncBuilder = EccNodesSync.newBuilder()
+ .withSession(null)
+ .withInitialNodesList(nodesList);
+ NullPointerException exception = assertThrows(
+ NullPointerException.class, tmpEccNodesSyncBuilder::build);
+ assertEquals("Session cannot be null", exception.getMessage());
+ }
+}
diff --git a/data/src/test/java/com/ericsson/bss/cassandra/ecchronos/data/utils/AbstractCassandraTest.java b/data/src/test/java/com/ericsson/bss/cassandra/ecchronos/data/utils/AbstractCassandraTest.java
new file mode 100644
index 000000000..681a95426
--- /dev/null
+++ b/data/src/test/java/com/ericsson/bss/cassandra/ecchronos/data/utils/AbstractCassandraTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2024 Telefonaktiebolaget LM Ericsson
+ *
+ * Licensed 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 com.ericsson.bss.cassandra.ecchronos.data.utils;
+
+import com.datastax.oss.driver.api.core.CqlSession;
+import com.datastax.oss.driver.api.core.metadata.Node;
+import com.ericsson.bss.cassandra.ecchronos.connection.DistributedNativeConnectionProvider;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.testcontainers.containers.CassandraContainer;
+import org.testcontainers.utility.DockerImageName;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AbstractCassandraTest
+{
+ private static final List> nodes = new ArrayList<>();
+ protected static CqlSession mySession;
+
+ private static DistributedNativeConnectionProvider myNativeConnectionProvider;
+
+ @BeforeClass
+ public static void setUpCluster()
+ {
+ CassandraContainer> node = new CassandraContainer<>(DockerImageName.parse("cassandra:4.1.5"))
+ .withExposedPorts(9042, 7000)
+ .withEnv("CASSANDRA_DC", "DC1")
+ .withEnv("CASSANDRA_ENDPOINT_SNITCH", "GossipingPropertyFileSnitch")
+ .withEnv("CASSANDRA_CLUSTER_NAME", "TestCluster");
+ nodes.add(node);
+ node.start();
+ mySession = CqlSession.builder()
+ .addContactPoint(node.getContactPoint())
+ .withLocalDatacenter(node.getLocalDatacenter())
+ .build();
+ List nodesList = new ArrayList<>(mySession.getMetadata().getNodes().values());
+ myNativeConnectionProvider = new DistributedNativeConnectionProvider()
+ {
+ @Override
+ public CqlSession getCqlSession()
+ {
+ return mySession;
+ }
+
+ @Override
+ public List getNodes()
+ {
+ return nodesList;
+ }
+ };
+ }
+
+ @AfterClass
+ public static void tearDownCluster()
+ {
+ // Stop all nodes
+ for (CassandraContainer> node : nodes)
+ {
+ node.stop();
+ }
+ }
+
+ public static DistributedNativeConnectionProvider getNativeConnectionProvider()
+ {
+ return myNativeConnectionProvider;
+ }
+}
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 026d0af45..39ada764c 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -9,7 +9,7 @@ For keeping track of the history it is recommended that most communication is pe
### Prerequisites
* Maven
-* JDK11
+* JDK17
* Docker (for test setup)
* Python
@@ -38,7 +38,6 @@ If you encounter a PMD rule that seems odd or non-relevant feel free to discuss
#### Built with
* [Maven](https://maven.apache.org) - Dependency and build management
-* [docker-maven-plugin](https://github.com/fabric8io/docker-maven-plugin) - For integration tests
### REST API
diff --git a/docs/autogenerated/ECCHRONOS_SETTINGS.md b/docs/autogenerated/ECCHRONOS_SETTINGS.md
deleted file mode 100644
index 0747339c0..000000000
--- a/docs/autogenerated/ECCHRONOS_SETTINGS.md
+++ /dev/null
@@ -1,330 +0,0 @@
-```yaml
-### ecChronos configuration
-
-## Connection
-## Properties for connection to the local node
-##
-connection:
- cql:
- ##
- ## Host and port properties for CQL.
- ## Primarily used by the default connection provider
- ##
- host: localhost
- port: 9042
- ##
- ## Connection Timeout for a CQL attempt.
- ## Specify a time to wait for cassandra to come up.
- ## Connection is tried based on retry policy delay calculations. Each connection attempt will use the timeout to calculate CQL connection process delay.
- ##
- timeout:
- time: 60
- unit: seconds
- retryPolicy:
- ## Max number of attempts ecChronos will try to connect with Cassandra.
- maxAttempts: 5
- ## Delay use to wait between an attempt and another, this value will be multiplied by the current attempt count powered by two.
- ## If the current attempt is 4 and the default delay is 5 seconds, so ((4(attempt) x 2) x 5(default delay)) = 40 seconds.
- ## If the calculated delay is greater than maxDelay, maxDelay will be used instead of the calculated delay.
- delay: 5
- ## Maximum delay before the next connection attempt is made.
- ## Setting it as 0 will disable maxDelay and the delay interval will
- ## be calculated based on the attempt count and the default delay.
- maxDelay: 30
- unit: seconds
- ##
- ## The class used to provide CQL connections to Apache Cassandra.
- ## The default provider will be used unless another is specified.
- ##
- provider: com.ericsson.bss.cassandra.ecchronos.application.DefaultNativeConnectionProvider
- ##
- ## The class used to provide an SSL context to the NativeConnectionProvider.
- ## Extending this allows to manipulate the SSLEngine and SSLParameters.
- ##
- certificateHandler: com.ericsson.bss.cassandra.ecchronos.application.ReloadingCertificateHandler
- ##
- ## The class used to decorate CQL statements.
- ## The default no-op decorator will be used unless another is specified.
- ##
- decoratorClass: com.ericsson.bss.cassandra.ecchronos.application.NoopStatementDecorator
- ##
- ## Allow routing requests directly to a remote datacenter.
- ## This allows locks for other datacenters to be taken in that datacenter instead of via the local datacenter.
- ## If clients are prevented from connecting directly to Cassandra nodes in other sites this is not possible.
- ## If remote routing is disabled, instead SERIAL consistency will be used for those request.
- ##
- remoteRouting: true
- jmx:
- ##
- ## Host and port properties for JMX.
- ## Primarily used by the default connection provider.
- ##
- host: localhost
- port: 7199
- ##
- ## The class used to provide JMX connections to Apache Cassandra.
- ## The default provider will be used unless another is specified.
- ##
- provider: com.ericsson.bss.cassandra.ecchronos.application.DefaultJmxConnectionProvider
-
-## Repair configuration
-## This section defines default repair behavior for all tables.
-##
-repair:
- ##
- ## A class for providing repair configuration for tables.
- ## The default FileBasedRepairConfiguration uses a schedule.yml file to define per-table configurations.
- ##
- provider: com.ericsson.bss.cassandra.ecchronos.application.FileBasedRepairConfiguration
- ##
- ## How often repairs should be triggered for tables.
- ##
- interval:
- time: 7
- unit: days
- ##
- ## Initial delay for new tables. New tables are always assumed to have been repaired in the past by the interval.
- ## However, a delay can be set for the first repair. This will not affect subsequent repairs and defaults to one day.
- ##
- initial_delay:
- time: 1
- unit: days
- ##
- ## The unit of time granularity for priority calculation, can be HOURS, MINUTES, or SECONDS.
- ## This unit is used in the calculation of priority.
- ## Default is HOURS for backward compatibility.
- ## Ensure to pause repair operations prior to changing the granularity.
- ## Not doing so may lead to inconsistencies as some ecchronos instances
- ## could have different priorities compared to others for the same repair.
- ## Possible values are HOURS, MINUTES, or SECONDS.
- ##
- priority:
- granularity_unit: HOURS
- ##
- ## Specifies the type of lock to use for repairs.
- ## "vnode" will lock each node involved in a repair individually and increase the number of
- ## parallel repairs that can run in a single data center.
- ## "datacenter" will lock each data center involved in a repair and only allow a single repair per data center.
- ## "datacenter_and_vnode" will combine both options and allow a smooth transition between them without allowing
- ## multiple repairs to run concurrently on a single node.
- ##
- lock_type: vnode
- ##
- ## Alarms are triggered when tables have not been repaired for a long amount of time.
- ## The warning alarm is meant to indicate early that repairs are falling behind.
- ## The error alarm is meant to indicate that gc_grace has passed between repairs.
- ##
- ## With the defaults where repairs triggers once every 7 days for each table a warning alarm would be raised
- ## if the table has not been properly repaired within one full day.
- ##
- alarm:
- ##
- ## The class used for fault reporting
- ## The default LoggingFaultReporter will log when alarm is raised/ceased
- ##
- faultReporter: com.ericsson.bss.cassandra.ecchronos.fm.impl.LoggingFaultReporter
- ##
- ## If a table has not been repaired for the following duration an warning alarm will be raised.
- ## The schedule will be marked as late if the table has not been repaired within this interval.
- ##
- warn:
- time: 8
- unit: days
- ##
- ## If a table has not been repaired for the following duration an error alarm will be raised.
- ## The schedule will be marked as overdue if the table has not been repaired within this interval.
- ##
- error:
- time: 10
- unit: days
- ##
- ## Specifies the unwind ratio to smooth out the load that repairs generate.
- ## This value is a ratio between 0 -> 100% of the execution time of a repair session.
- ##
- ## 100% means that the executor will wait to run the next session for as long time as the previous session took.
- ## The 'unwind_ratio' setting configures the wait time between repair tasks as a proportion of the previous task's execution time.
- ##
- ## Examples:
- ## - unwind_ratio: 0
- ## Explanation: No wait time between tasks. The next task starts immediately after the previous one finishes.
- ## Total Repair Time: T1 (10s) + T2 (20s) = 30 seconds.
- ##
- ## - unwind_ratio: 1.0 (100%)
- ## Explanation: The wait time after each task equals its duration.
- ## Total Repair Time: T1 (10s + 10s wait) + T2 (20s + 20s wait) = 60 seconds.
- ##
- ## - unwind_ratio: 0.5 (50%)
- ## Explanation: The wait time is half of the task's duration.
- ## Total Repair Time: T1 (10s + 5s wait) + T2 (20s + 10s wait) = 45 seconds.
- ##
- ## A higher 'unwind_ratio' reduces system load by adding longer waits, but increases total repair time.
- ## A lower 'unwind_ratio' speeds up repairs but may increase system load.
- ##
- unwind_ratio: 0.0
- ##
- ## Specifies the lookback time for when the repair_history table is queried to get initial repair state at startup.
- ## The time should match the "expected TTL" of the system_distributed.repair_history table.
- ##
- history_lookback:
- time: 30
- unit: days
- ##
- ## Specifies a target for how much data each repair session should process.
- ## This is only supported if using 'vnode' as repair_type.
- ## This is an estimation assuming uniform data distribution among partition keys.
- ## The value should be either a number or a number with a unit of measurement:
- ## 12 (12 B)
- ## 12k (12 KiB)
- ## 12m (12 MiB)
- ## 12g (12 GiB)
- ##
- size_target:
- ##
- ## Specifies the repair history provider used to determine repair state.
- ## The "cassandra" provider uses the repair history generated by the database.
- ## The "upgrade" provider is an intermediate state reading history from "cassandra" and producing history for "ecc"
- ## The "ecc" provider maintains and uses an internal repair history in a dedicated table.
- ## The main context for the "ecc" provider is an environment where the ip address of nodes might change.
- ## Possible values are "ecc", "upgrade" and "cassandra".
- ##
- ## The keyspace parameter is only used by "ecc" and "upgrade" and points to the keyspace where the custom
- ## 'repair_history' table is located.
- ##
- history:
- provider: ecc
- keyspace: ecchronos
- ##
- ## Specifies if tables with TWCS (TimeWindowCompactionStrategy) should be ignored for repair
- ##
- ignore_twcs_tables: false
- ##
- ## Specifies the backoff time for a job.
- ## This is the time that the job will wait before trying to run again after failing.
- ##
- backoff:
- time: 30
- unit: MINUTES
- ##
- ## Specifies the default repair_type.
- ## Possible values are: vnode, parallel_vnode, incremental
- ## vnode = repair 1 vnode at a time (supports size_target to split the vnode further, in this case there will be 1 repair session per subrange)
- ## parallel_vnode = repair vnodes in parallel, this will combine vnodes into a single repair session per repair group
- ## incremental = repair vnodes incrementally (incremental repair)
- ##
- repair_type: vnode
-
-statistics:
- enabled: true
- ##
- ## Decides how statistics should be exposed.
- ## If all reporting is disabled, the statistics will be disabled as well.
- ##
- reporting:
- jmx:
- enabled: true
- ##
- ## The metrics can be excluded on name and on tag values using quoted regular expressions.
- ## Exclusion on name should be done without the prefix.
- ## If an exclusion is without tags, then metric matching the name will be excluded.
- ## If both name and tags are specified, then the metric must match both to be excluded.
- ## If multiple tags are specified, the metric must match all tags to be excluded.
- ## By default, no metrics are excluded.
- ## For list of available metrics and tags refer to the documentation.
- ##
- excludedMetrics: []
- file:
- enabled: true
- ##
- ## The metrics can be excluded on name and on tag values using quoted regular expressions.
- ## Exclusion on name should be done without the prefix.
- ## If an exclusion is without tags, then metric matching the name will be excluded.
- ## If both name and tags are specified, then the metric must match both to be excluded.
- ## If multiple tags are specified, the metric must match all tags to be excluded.
- ## By default, no metrics are excluded.
- ## For list of available metrics and tags refer to the documentation.
- ##
- excludedMetrics: []
- http:
- enabled: true
- ##
- ## The metrics can be excluded on name and on tag values using quoted regular expressions.
- ## Exclusion on name should be done without the prefix.
- ## If an exclusion is without tags, then metric matching the name will be excluded.
- ## If both name and tags are specified, then the metric must match both to be excluded.
- ## If multiple tags are specified, the metric must match all tags to be excluded.
- ## By default, no metrics are excluded.
- ## For list of available metrics and tags refer to the documentation.
- ##
- excludedMetrics: []
- directory: ./statistics
- ##
- ## Prefix all metrics with below string
- ## The prefix cannot start or end with a dot or any other path separator.
- ##
- prefix: ''
- ##
- ## Number of repair failures before status logger logs metrics in debug logs
- ## The number is used to trigger a status once number of failures is breached in a time window mentioned below
- ##
- repair_failures_count: 5
- ##
- ## Time window over which to track repair failures in node for trigger status logger messages in debug log
- ##
- repair_failures_time_window:
- time: 30
- unit: minutes
- ##
- ## Trigger interval for metric inspection.
- ## This time should always be lesser than repair_failures_time_window
- ##
- trigger_interval_for_metric_inspection:
- time: 5
- unit: seconds
-
-lock_factory:
- cas:
- ##
- ## The keyspace used for the CAS lock factory tables.
- ##
- keyspace: ecchronos
- ##
- ## The number of seconds until the lock failure cache expires.
- ## If an attempt to secure a lock is unsuccessful,
- ## all subsequent attempts will be failed until
- ## the cache expiration time is reached.
- ##
- cache_expiry_time_in_seconds: 30
- ##
- ## Allow to override consistency level for LWT (lightweight transactions). Possible values are:
- ## "DEFAULT" - Use consistency level based on remoteRouting.
- ## "SERIAL" - Use SERIAL consistency for LWT regardless of remoteRouting.
- ## "LOCAL" - Use LOCAL_SERIAL consistency for LWT regardless of remoteRouting.
- ##
- ## if you use remoteRouting: false and LOCAL then all locks will be taken locally
- ## in DC. I.e There's a risk that multiple nodes in different datacenters will be able to lock the
- ## same nodes causing multiple repairs on the same range/node at the same time.
- ##
- consistencySerial: "DEFAULT"
-
-run_policy:
- time_based:
- ##
- ## The keyspace used for the time based run policy tables.
- ##
- keyspace: ecchronos
-
-scheduler:
- ##
- ## Specifies the frequency the scheduler checks for work to be done
- ##
- frequency:
- time: 30
- unit: SECONDS
-
-rest_server:
- ##
- ## The host and port used for the HTTP server
- ##
- host: localhost
- port: 8080
-```
diff --git a/docs/autogenerated/ECCTOOL.md b/docs/autogenerated/ECCTOOL.md
deleted file mode 100644
index 6dd973488..000000000
--- a/docs/autogenerated/ECCTOOL.md
+++ /dev/null
@@ -1,238 +0,0 @@
-# ecctool
-
-ecctool is a command line utility which can be used to perform actions towards a local ecChronos instance. The actions are implemented in form of subcommands with arguments. All visualization is displayed in form of human-readable tables.
-
-```console
-usage: ecctool [-h]
- {repairs,schedules,run-repair,repair-info,start,stop,status}
- ...
-```
-
-
-### -h, --help
-show this help message and exit
-
-## ecctool repair-info
-
-Get information about repairs for tables. The repair information is based on repair history, meaning that both manual repairs and schedules will contribute to the repair information. This subcommand requires the user to provide either –since or –duration if –keyspace and –table is not provided. If repair info is fetched for a specific table using –keyspace and –table, the duration will default to the table’s GC_GRACE_SECONDS.
-
-```console
-usage: ecctool repair-info [-h] [-k KEYSPACE] [-t TABLE] [-s SINCE]
- [-d DURATION] [--local] [-u URL] [-l LIMIT]
-```
-
-
-### -h, --help
-show this help message and exit
-
-
-### -k <keyspace>, --keyspace <keyspace>
-Show repair information for all tables in the specified keyspace.
-
-
-### -t <table>, --table <table>
-Show repair information for the specified table. Keyspace argument -k or –keyspace becomes mandatory if using this argument.
-
-
-### -s <since>, --since <since>
-Show repair information since the specified date to now. Date must be specified in ISO8601 format. The time-window will be since to now. Mandatory if –duration or –keyspace and –table is not specified.
-
-
-### -d <duration>, --duration <duration>
-Show repair information for the duration. Duration can be specified as ISO8601 format or as simple format in form: 5s, 5m, 5h, 5d. The time-window will be now-duration to now. Mandatory if –since or –keyspace and –table is not specified.
-
-
-### --local
-Show repair information only for the local node.
-
-
-### -u <url>, --url <url>
-The ecChronos host to connect to, specified in the format [http:/](http:/)/<host>:<port>.
-
-
-### -l <limit>, --limit <limit>
-Limits the number of rows printed in the output. Specified as a number, -1 to disable limit.
-
-## ecctool repairs
-
-Show the status of all manual repairs. This subcommand has no mandatory parameters.
-
-```console
-usage: ecctool repairs [-h] [-k KEYSPACE] [-t TABLE] [-u URL] [-i ID]
- [-l LIMIT] [--hostid HOSTID]
-```
-
-
-### -h, --help
-show this help message and exit
-
-
-### -k <keyspace>, --keyspace <keyspace>
-Show repairs for the specified keyspace. This argument is mutually exclusive with -i and –id.
-
-
-### -t <table>, --table <table>
-Show repairs for the specified table. Keyspace argument -k or –keyspace becomes mandatory if using this argument. This argument is mutually exclusive with -i and –id.
-
-
-### -u <url>, --url <url>
-The ecChronos host to connect to, specified in the format [http:/](http:/)/<host>:<port>.
-
-
-### -i <id>, --id <id>
-Show repairs matching the specified ID. This argument is mutually exclusive with -k, –keyspace, -t and –table.
-
-
-### -l <limit>, --limit <limit>
-Limits the number of rows printed in the output. Specified as a number, -1 to disable limit.
-
-
-### --hostid <hostid>
-Show repairs for the specified host id. The host id corresponds to the Cassandra instance ecChronos is connected to.
-
-## ecctool run-repair
-
-Run a manual repair. The manual repair will be triggered in ecChronos. EcChronos will perform repair through Cassandra JMX interface. This subcommand has no mandatory parameters.
-
-```console
-usage: ecctool run-repair [-h] [-u URL] [--local] [-r REPAIR_TYPE]
- [-k KEYSPACE] [-t TABLE]
-```
-
-
-### -h, --help
-show this help message and exit
-
-
-### -u <url>, --url <url>
-The ecChronos host to connect to, specified in the format [http:/](http:/)/<host>:<port>.
-
-
-### --local
-Run repair for the local node only, i.e repair will only be performed for the ranges that the local node is a replica for.
-
-
-### -r <repair_type>, --repair_type <repair_type>
-The type of the repair, possible values are ‘vnode’, ‘parallel_vnode’, ‘incremental’
-
-
-### -k <keyspace>, --keyspace <keyspace>
-Run repair for the specified keyspace. Repair will be run for all tables within the keyspace with replication factor higher than 1.
-
-
-### -t <table>, --table <table>
-Run repair for the specified table. Keyspace argument -k or –keyspace becomes mandatory if using this argument.
-
-## ecctool schedules
-
-Show the status of schedules. This subcommand has no mandatory parameters.
-
-```console
-usage: ecctool schedules [-h] [-k KEYSPACE] [-t TABLE] [-u URL] [-i ID] [-f]
- [-l LIMIT]
-```
-
-
-### -h, --help
-show this help message and exit
-
-
-### -k <keyspace>, --keyspace <keyspace>
-Show schedules for the specified keyspace. This argument is mutually exclusive with -i and –id.
-
-
-### -t <table>, --table <table>
-Show schedules for the specified table. Keyspace argument -k or –keyspace becomes mandatory if using this argument. This argument is mutually exclusive with -i and –id.
-
-
-### -u <url>, --url <url>
-The ecChronos host to connect to, specified in the format [http:/](http:/)/<host>:<port>.
-
-
-### -i <id>, --id <id>
-Show schedules matching the specified ID. This argument is mutually exclusive with -k, –keyspace, -t and –table.
-
-
-### -f, --full
-Show full schedules, can only be used with -i or –id. Full schedules include schedule configuration and repair state per vnode.
-
-
-### -l <limit>, --limit <limit>
-Limits the number of rows printed in the output. Specified as a number, -1 to disable limit.
-
-## ecctool start
-
-Start the ecChronos service. This subcommand has no mandatory parameters.
-
-```console
-usage: ecctool start [-h] [-f] [-p PIDFILE]
-```
-
-
-### -h, --help
-show this help message and exit
-
-
-### -f, --foreground
-Start the ecChronos instance in foreground mode (exec in current terminal and log to stdout)
-
-
-### -p <pidfile>, --pidfile <pidfile>
-Start the ecChronos instance and store the pid in the specified pid file.
-
-## ecctool status
-
-View status of ecChronos instance. This subcommand has no mandatory parameters.
-
-```console
-usage: ecctool status [-h] [-u URL]
-```
-
-
-### -h, --help
-show this help message and exit
-
-
-### -u <url>, --url <url>
-The ecChronos host to connect to, specified in the format [http:/](http:/)/<host>:<port>.
-
-## ecctool stop
-
-Stop the ecChronos instance. Stopping of ecChronos is done by using kill with SIGTERM signal (same as kill in shell) for the pid. This subcommand has no mandatory parameters.
-
-```console
-usage: ecctool stop [-h] [-p PIDFILE]
-```
-
-
-### -h, --help
-show this help message and exit
-
-
-### -p <pidfile>, --pidfile <pidfile>
-Stops the ecChronos instance by pid fetched from the specified pid file.
-
-# Examples
-
-For example usage and explanation about output refer to [ECCTOOL_EXAMPLES.md](../ECCTOOL_EXAMPLES.md)
-
-## ecctool running-job
-
-Show which (if any) job that is currently running.
-
-```console
-usage: ecctool running-job [-h] [-u URL]
-
-Show which (if any) job is currently running
-
-optional arguments:
- -h, --help show this help message and exit
- -u URL, --url URL The ecChronos host to connect to, specified in the format http://:.
-```
-
-### -h, --help
-show this help message and exit
-
-
-### -u <url>, --url <url>
-The ecChronos host to connect to, specified in the format [http:/](http:/)/<host>:<port>.
diff --git a/docs/autogenerated/openapi.yaml b/docs/autogenerated/openapi.yaml
deleted file mode 100644
index 26b1405b6..000000000
--- a/docs/autogenerated/openapi.yaml
+++ /dev/null
@@ -1,547 +0,0 @@
-openapi: 3.0.1
-info:
- title: REST API
- license:
- name: Apache 2.0
- url: https://www.apache.org/licenses/LICENSE-2.0
- version: 1.0.0
-servers:
-- url: https://localhost:8080
- description: Generated server url
-tags:
-- name: Repair-Management
- description: Management of repairs
-- name: Metrics
- description: Retrieve metrics about ecChronos
-- name: Actuator
- description: Monitor and interact
- externalDocs:
- description: Spring Boot Actuator Web API Documentation
- url: https://docs.spring.io/spring-boot/docs/current/actuator-api/html/
-paths:
- /repair-management/v2/repairs:
- get:
- tags:
- - Repair-Management
- summary: Get manual repairs.
- description: Get manual repairs which are running/completed/failed.
- operationId: get-repairs
- parameters:
- - name: keyspace
- in: query
- description: "Only return repairs matching the keyspace, mandatory if 'table'\
- \ is provided."
- required: false
- schema:
- type: string
- - name: table
- in: query
- description: Only return repairs matching the table.
- required: false
- schema:
- type: string
- - name: hostId
- in: query
- description: Only return repairs matching the hostId.
- required: false
- schema:
- type: string
- responses:
- "200":
- description: OK
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: '#/components/schemas/OnDemandRepair'
- post:
- tags:
- - Repair-Management
- summary: Run a manual repair.
- description: "Run a manual repair, if 'isLocal' is not provided this will run\
- \ a cluster-wide repair."
- operationId: run-repair
- parameters:
- - name: keyspace
- in: query
- description: "The keyspace to run repair for, mandatory if 'table' is provided."
- required: false
- schema:
- type: string
- - name: table
- in: query
- description: The table to run repair for.
- required: false
- schema:
- type: string
- - name: repairType
- in: query
- description: "The type of the repair, defaults to vnode."
- required: false
- schema:
- type: string
- enum:
- - VNODE
- - PARALLEL_VNODE
- - INCREMENTAL
- - name: isLocal
- in: query
- description: "Decides if the repair should be only for the local node, i.e\
- \ not cluster-wide."
- required: false
- schema:
- type: boolean
- responses:
- "200":
- description: OK
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: '#/components/schemas/OnDemandRepair'
- /repair-management/v2/schedules:
- get:
- tags:
- - Repair-Management
- summary: Get schedules
- description: Get schedules
- operationId: get-schedules
- parameters:
- - name: keyspace
- in: query
- description: "Filter schedules based on this keyspace, mandatory if 'table'\
- \ is provided."
- required: false
- schema:
- type: string
- - name: table
- in: query
- description: Filter schedules based on this table.
- required: false
- schema:
- type: string
- responses:
- "200":
- description: OK
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: '#/components/schemas/Schedule'
- /repair-management/v2/schedules/{id}:
- get:
- tags:
- - Repair-Management
- summary: Get schedules matching the id.
- description: Get schedules matching the id.
- operationId: get-schedules-by-id
- parameters:
- - name: id
- in: path
- description: The id of the schedule.
- required: true
- schema:
- type: string
- - name: full
- in: query
- description: Decides if a 'full schedule' should be returned.
- required: false
- schema:
- type: boolean
- responses:
- "200":
- description: OK
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Schedule'
- /repair-management/v2/repairs/{id}:
- get:
- tags:
- - Repair-Management
- summary: Get manual repairs matching the id.
- description: Get manual repairs matching the id which are running/completed/failed.
- operationId: get-repairs-by-id
- parameters:
- - name: id
- in: path
- description: Only return repairs matching the id.
- required: true
- schema:
- type: string
- - name: hostId
- in: query
- description: Only return repairs matching the hostId.
- required: false
- schema:
- type: string
- responses:
- "200":
- description: OK
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: '#/components/schemas/OnDemandRepair'
- /repair-management/v2/repairInfo:
- get:
- tags:
- - Repair-Management
- summary: Get repair information
- description: "Get repair information, if keyspace and table are provided while\
- \ duration and since are not, the duration will default to GC_GRACE_SECONDS\
- \ of the table. This operation might take time depending on the provided params\
- \ since it's based on the repair history."
- operationId: get-repair-info
- parameters:
- - name: keyspace
- in: query
- description: "Only return repair-info matching the keyspace, mandatory if\
- \ 'table' is provided."
- required: false
- schema:
- type: string
- - name: table
- in: query
- description: Only return repair-info matching the table.
- required: false
- schema:
- type: string
- - name: since
- in: query
- description: "Since time, can be specified as ISO8601 date or as milliseconds\
- \ since epoch. Required if keyspace and table or duration is not specified."
- required: false
- schema:
- type: string
- - name: duration
- in: query
- description: "Duration, can be specified as either a simple duration like\
- \ '30s' or as ISO8601 duration 'pt30s'. Required if keyspace and table or\
- \ since is not specified."
- required: false
- schema:
- type: string
- - name: isLocal
- in: query
- description: Decides if the repair-info should be calculated for the local
- node only.
- required: false
- schema:
- type: boolean
- responses:
- "200":
- description: OK
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/RepairInfo'
- /metrics:
- get:
- tags:
- - Metrics
- summary: Get metrics
- description: Get metrics in the specified format
- operationId: metrics
- parameters:
- - name: Accept
- in: header
- required: false
- schema:
- type: string
- default: text/plain; version=0.0.4; charset=utf-8
- - name: "name[]"
- in: query
- description: Filter metrics based on these names.
- required: false
- schema:
- uniqueItems: true
- type: array
- items:
- type: string
- default: []
- responses:
- "200":
- description: OK
- content:
- text/plain;version=0.0.4;charset=utf-8:
- schema:
- type: string
- application/openmetrics-text;version=1.0.0;charset=utf-8:
- schema:
- type: string
- /actuator:
- get:
- tags:
- - Actuator
- summary: Actuator root web endpoint
- operationId: links
- responses:
- "200":
- description: OK
- content:
- application/vnd.spring-boot.actuator.v3+json:
- schema:
- type: object
- additionalProperties:
- type: object
- additionalProperties:
- $ref: '#/components/schemas/Link'
- application/vnd.spring-boot.actuator.v2+json:
- schema:
- type: object
- additionalProperties:
- type: object
- additionalProperties:
- $ref: '#/components/schemas/Link'
- application/json:
- schema:
- type: object
- additionalProperties:
- type: object
- additionalProperties:
- $ref: '#/components/schemas/Link'
- /actuator/health:
- get:
- tags:
- - Actuator
- summary: Actuator web endpoint 'health'
- operationId: health
- responses:
- "200":
- description: OK
- content:
- application/vnd.spring-boot.actuator.v3+json:
- schema:
- type: object
- application/vnd.spring-boot.actuator.v2+json:
- schema:
- type: object
- application/json:
- schema:
- type: object
- /actuator/health/**:
- get:
- tags:
- - Actuator
- summary: Actuator web endpoint 'health-path'
- operationId: health-path
- responses:
- "200":
- description: OK
- content:
- application/vnd.spring-boot.actuator.v3+json:
- schema:
- type: object
- application/vnd.spring-boot.actuator.v2+json:
- schema:
- type: object
- application/json:
- schema:
- type: object
-components:
- schemas:
- OnDemandRepair:
- required:
- - completedAt
- - hostId
- - id
- - keyspace
- - repairType
- - repairedRatio
- - status
- - table
- type: object
- properties:
- id:
- type: string
- format: uuid
- hostId:
- type: string
- format: uuid
- keyspace:
- type: string
- table:
- type: string
- status:
- type: string
- enum:
- - COMPLETED
- - IN_QUEUE
- - WARNING
- - ERROR
- - BLOCKED
- repairedRatio:
- maximum: 1
- minimum: 0
- type: number
- format: double
- completedAt:
- minimum: -1
- type: integer
- format: int64
- repairType:
- type: string
- enum:
- - VNODE
- - PARALLEL_VNODE
- - INCREMENTAL
- Schedule:
- required:
- - config
- - id
- - keyspace
- - lastRepairedAtInMs
- - nextRepairInMs
- - repairType
- - repairedRatio
- - status
- - table
- type: object
- properties:
- id:
- type: string
- format: uuid
- keyspace:
- type: string
- table:
- type: string
- status:
- type: string
- enum:
- - COMPLETED
- - ON_TIME
- - LATE
- - OVERDUE
- - BLOCKED
- repairedRatio:
- maximum: 1
- minimum: 0
- type: number
- format: double
- lastRepairedAtInMs:
- type: integer
- format: int64
- nextRepairInMs:
- type: integer
- format: int64
- config:
- $ref: '#/components/schemas/ScheduleConfig'
- repairType:
- type: string
- enum:
- - VNODE
- - PARALLEL_VNODE
- - INCREMENTAL
- virtualNodeStates:
- type: array
- items:
- $ref: '#/components/schemas/VirtualNodeState'
- ScheduleConfig:
- required:
- - errorTimeInMs
- - intervalInMs
- - parallelism
- - unwindRatio
- - warningTimeInMs
- type: object
- properties:
- intervalInMs:
- minimum: 0
- type: integer
- format: int64
- unwindRatio:
- minimum: 0
- type: number
- format: double
- warningTimeInMs:
- minimum: 0
- type: integer
- format: int64
- errorTimeInMs:
- minimum: 0
- type: integer
- format: int64
- parallelism:
- type: string
- enum:
- - PARALLEL
- VirtualNodeState:
- required:
- - endToken
- - lastRepairedAtInMs
- - repaired
- - replicas
- - startToken
- type: object
- properties:
- startToken:
- minimum: -9223372036854775808
- type: integer
- format: int64
- endToken:
- maximum: 9223372036854775807
- type: integer
- format: int64
- replicas:
- uniqueItems: true
- type: array
- items:
- type: string
- lastRepairedAtInMs:
- minimum: 0
- type: integer
- format: int64
- repaired:
- type: boolean
- RepairInfo:
- required:
- - repairStats
- - sinceInMs
- - toInMs
- type: object
- properties:
- sinceInMs:
- minimum: 0
- type: integer
- format: int64
- toInMs:
- minimum: 0
- type: integer
- format: int64
- repairStats:
- type: array
- items:
- $ref: '#/components/schemas/RepairStats'
- RepairStats:
- required:
- - keyspace
- - repairTimeTakenMs
- - repairedRatio
- - table
- type: object
- properties:
- keyspace:
- type: string
- table:
- type: string
- repairedRatio:
- maximum: 1
- minimum: 0
- type: number
- format: double
- repairTimeTakenMs:
- minimum: 0
- type: integer
- format: int64
- Link:
- type: object
- properties:
- href:
- type: string
- templated:
- type: boolean
diff --git a/pmd-rules.xml b/pmd-rules.xml
index 5858cf92b..6485e7866 100644
--- a/pmd-rules.xml
+++ b/pmd-rules.xml
@@ -1,7 +1,7 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
-
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 000000000..af7b56c06
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,661 @@
+
+
+
+ 4.0.0
+
+ com.ericsson.bss.cassandra.ecchronos
+ agent
+ 1.0.0-SNAPSHOT
+ pom
+
+ Ericsson Cassandra Chronos Agent
+ A distributed agent repair scheduler for Apache Cassandra.
+
+
+
+ Apache License, Version 2.0
+ https://www.apache.org/licenses/LICENSE-2.0.txt
+
+
+
+
+
+ Victor Cavichioli
+ victor.cavichioli@ericsson.com
+ Ericsson AB
+ http://www.ericsson.com
+
+
+
+
+ Ericsson AB
+ http://www.ericsson.com
+
+
+
+
+ ossrh
+ https://oss.sonatype.org/content/repositories/snapshots/
+
+
+ ossrh
+ https://oss.sonatype.org/service/local/staging/deploy/maven2/
+
+
+
+
+ https://github.com/ericsson/ecchronos
+ scm:git:git@github.com:ericsson/ecchronos.git
+ scm:git:git@github.com:ericsson/ecchronos.git
+ HEAD
+
+
+
+
+ connection
+ connection.impl
+ application
+ data
+
+
+
+
+ 17
+ 17
+ 17
+ UTF-8
+
+
+ 3.3.2
+ 10.1.26
+ 1.8.0
+ 6.1.11
+
+
+ 4.17.0
+ 32.0.1-jre
+ 1.5.0
+ 1.4.2
+ 25.1-jre
+ 3.1.8
+ 1.0.1
+ 2.1.12
+ 2.0.13
+ 1.5.6
+
+
+ 2.17.2
+ 2.17.2
+ 1.33
+
+
+ 5.12.0
+ 3.26.3
+ 5.7.0
+ 2.16.1
+ 3.16.1
+ 1.0
+ 1.18.3
+
+
+ 3.8.0
+
+ 2.5.2
+
+ 2.22.1
+
+ 2.22.1
+
+ 3.6.0
+
+ 3.2.0
+
+ 3.24.0
+ 3.0.0
+ 3.1.0
+
+ 3.5.0
+
+ 3.4.2
+ 3.4.1
+ 5.1.8
+ 3.0
+ 1.2
+
+ 4.2.1
+ 0.8.4
+ 3.0.0
+ 1.19
+
+ 2.5.3
+
+ 2.8.2
+
+ 3.0.1
+
+ 3.0.1
+
+ 1.6
+
+ false
+ ${skipTests}
+
+
+
+
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-core
+ ${tomcat.version}
+
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-el
+ ${tomcat.version}
+
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-websocket
+ ${tomcat.version}
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${org.springframework.boot.version}
+ pom
+ import
+
+
+
+ org.springdoc
+ springdoc-openapi-ui
+ ${org.springdoc.openapi-ui.version}
+
+
+
+ org.springframework
+ spring-web
+ ${org.springframework.web.version}
+
+
+
+
+ com.datastax.oss
+ java-driver-core
+ ${cassandra.driver.core.version}
+
+
+ com.github.jnr
+ jnr-ffi
+
+
+ com.github.jnr
+ jnr-posix
+
+
+ com.github.stephenc.jcip
+ jcip-annotations
+
+
+ com.github.spotbugs
+ spotbugs-annotations
+
+
+ io.dropwizard.metrics
+ metrics-core
+
+
+ org.hdrhistogram
+ HdrHistogram
+
+
+
+
+ com.datastax.oss
+ java-driver-metrics-micrometer
+ ${cassandra.driver.core.version}
+
+
+ com.datastax.oss
+ java-driver-query-builder
+ ${cassandra.driver.core.version}
+
+
+ com.github.stephenc.jcip
+ jcip-annotations
+
+
+ com.github.spotbugs
+ spotbugs-annotations
+
+
+
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ ${caffeine.version}
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+ com.google.guava
+ failureaccess
+ ${failureaccess.version}
+
+
+
+ com.datastax.oss
+ native-protocol
+ ${cassandra.driver.protocol.version}
+
+
+
+ com.datastax.oss
+ java-driver-shaded-guava
+ ${cassandra.driver.shaded.guava.version}
+
+
+
+ com.typesafe
+ config
+ ${com.typesafe.config.version}
+
+
+
+ org.hdrhistogram
+ HdrHistogram
+ ${org.hdrhistogram.version}
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+
+
+
+ ch.qos.logback
+ logback-core
+ ${logback.version}
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ ${com.fasterxml.jackson.core.version}
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ ${com.fasterxml.jackson.core.version}
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${com.fasterxml.jackson.core.version}
+
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+ ${com.fasterxml.jackson.dataformat.version}
+
+
+
+ org.yaml
+ snakeyaml
+ ${org.yaml.snakeyaml.version}
+
+
+
+
+ org.mockito
+ mockito-core
+ ${mockito.core.version}
+ test
+
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
+
+
+ org.junit
+ junit-bom
+ ${junit.version}
+ pom
+ import
+
+
+
+ commons-io
+ commons-io
+ ${org.apache.commons.io.version}
+ test
+
+
+
+ nl.jqno.equalsverifier
+ equalsverifier
+ ${equalsverifier.version}
+ test
+
+
+
+ net.jcip
+ jcip-annotations
+ ${jcip.version}
+ test
+
+
+
+ org.testcontainers
+ cassandra
+ ${testcontainers.version}
+ test
+
+
+
+
+
+
+
+
+ maven-pmd-plugin
+ ${org.apache.maven.plugins-maven-pmd-plugin.version}
+
+
+ pmd-rules.xml
+
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ ${org.apache.maven.plugins-maven-shade-plugin.version}
+
+
+
+ org.codehaus.mojo
+ license-maven-plugin
+ ${org.codehaus.mojo.license-maven-plugin.version}
+
+
+
+ maven-surefire-plugin
+ ${org.apache.maven.plugins.maven-surefire-plugin.version}
+
+ ${skipUTs}
+
+ -Djdk.attach.allowAttachSelf=true
+ --add-exports java.base/jdk.internal.misc=ALL-UNNAMED
+ --add-exports java.base/jdk.internal.ref=ALL-UNNAMED
+ --add-exports java.base/sun.nio.ch=ALL-UNNAMED
+ --add-exports java.management.rmi/com.sun.jmx.remote.internal.rmi=ALL-UNNAMED
+ --add-exports java.rmi/sun.rmi.registry=ALL-UNNAMED
+ --add-exports java.rmi/sun.rmi.server=ALL-UNNAMED
+ --add-exports java.sql/java.sql=ALL-UNNAMED
+ --add-opens java.base/java.lang.module=ALL-UNNAMED
+ --add-opens java.base/jdk.internal.loader=ALL-UNNAMED
+ --add-opens java.base/jdk.internal.ref=ALL-UNNAMED
+ --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
+ --add-opens java.base/jdk.internal.math=ALL-UNNAMED
+ --add-opens java.base/jdk.internal.module=ALL-UNNAMED
+ --add-opens java.base/jdk.internal.util.jar=ALL-UNNAMED
+ --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED
+ --add-opens=java.base/java.io=ALL-UNNAMED
+ --add-opens=java.base/sun.nio.ch=ALL-UNNAMED
+ --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
+ --add-opens=java.base/java.util=ALL-UNNAMED
+ --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED
+ --add-opens=java.base/java.lang=ALL-UNNAMED
+ --add-opens java.base/java.nio=ALL-UNNAMED
+
+
+
+
+
+ maven-failsafe-plugin
+ ${org.apache.maven.plugins.maven-failsafe-plugin.version}
+
+
+
+ maven-compiler-plugin
+ ${org.apache.maven.plugins.maven-compiler-plugin.version}
+
+
+ ${maven.compiler.target}
+
+
+
+
+ maven-install-plugin
+ ${org.apache.maven.plugins.maven-install-plugin.version}
+
+
+
+ maven-assembly-plugin
+ ${org.apache.maven.plugins.maven-assembly-plugin.version}
+
+ true
+
+
+
+
+ org.apache.karaf.tooling
+ karaf-maven-plugin
+ ${org.apache.karaf.tooling.karaf-maven-plugin.version}
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ ${org.apache.felix.maven-bundle-plugin.version}
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.apache.servicemix.tooling
+ depends-maven-plugin
+ ${org.apache.servicemix.tooling.depends-maven-plugin.version}
+
+
+
+ com.mycila
+ license-maven-plugin
+ ${com.mycila.license-maven-plugin.version}
+
+
+
+
+ SLASHSTAR_STYLE
+
+ true
+ true
+
+
+ YEAR
+ 2019
+ [0-9-]+
+
+
+
+ CODEOWNERS
+ code_style.xml
+ check_style.xml
+ .github/workflows/actions.yml
+ .github/workflows/scorecard.yml
+
+
+
+
+
+ maven-release-plugin
+ ${org.apache.maven.plugins.maven-release-plugin.version}
+
+ false
+ release
+ deploy
+
+
+
+
+ maven-deploy-plugin
+ ${org.apache.maven.plugins.maven-deploy-plugin.version}
+
+
+
+ maven-source-plugin
+ ${org.apache.maven.plugins.maven-source-plugin.version}
+
+
+
+ maven-javadoc-plugin
+ ${org.apache.maven.plugins.maven-javadoc-plugin.version}
+
+
+ com.ericsson.bss.cassandra.ecchronos.application
+
+
+
+
+
+ maven-gpg-plugin
+ ${org.apache.maven.plugins.maven-gpg-plugin.version}
+
+
+
+ maven-dependency-plugin
+ ${org.apache.maven.plugins-maven-dependency-plugin.version}
+
+
+
+ maven-resources-plugin
+ ${org.apache.maven.plugins-maven-resources-plugin.version}
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ ${org.codehaus.mojo.exec-maven-plugin.version}
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${org.apache.maven.plugins-maven-jar-plugin.version}
+
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ true
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${org.apache.maven.plugins-maven-checkstyle-plugin.version}
+
+ UTF-8
+ UTF-8
+ true
+ check_style.xml
+ true
+
+
+
+
+ check
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${org.jacoco.jacoco-maven-plugin.version}
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jxr-plugin
+ ${org.apache.maven.plugin.maven-jxr-plugin.version}
+
+
+
+
\ No newline at end of file