Skip to content

Commit 8ab4167

Browse files
CASSSIDECAR-360: Sidecar API Endpoint for Nodetool Compaction Stop (#272)
Patch by Shalni Sundram; reviewed by Arjun Ashok, Bernardo Botella, Jyothsna Konisa, Saranya Krishnakumar, Sudipta Laha for CASSSIDECAR-360
1 parent 1ee13fb commit 8ab4167

34 files changed

Lines changed: 2510 additions & 9 deletions

File tree

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
0.3.0
22
-----
3+
* Sidecar API Endpoint for Nodetool Compaction Stop (CASSSIDECAR-360)
34
* Implementation of CDCPublisher (CASSSIDECAR-243)
45
* Sidecar endpoint for moving a node to a new token (CASSSIDECAR-344)
56
* Returning JSON responses for live migration status endpoints in case of errors (CASSSIDECAR-395)

adapters/adapters-base/src/main/java/org/apache/cassandra/sidecar/adapters/base/CassandraAdapter.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,11 @@ public CassandraAdapter(DnsResolver dnsResolver,
8181
this.tableOperations = Objects.requireNonNull(createTableOperations(jmxClient), "tableOperations is required");
8282
this.compactionManagerOperations = Objects.requireNonNull(createCompactionManagerOperations(jmxClient), "compactionManagerOperations is required");
8383
this.metricsOperations = Objects.requireNonNull(createMetricsOperations(jmxClient, tableSchemaFetcher), "metricsOperations is required");
84-
this.compactionStatsOperations = Objects.requireNonNull(createCompactionStatsOperations(storageOperations, metricsOperations,
85-
compactionManagerOperations), "compactionStatsOperations is required");
84+
this.compactionStatsOperations
85+
= Objects.requireNonNull(createCompactionStatsOperations(storageOperations,
86+
metricsOperations,
87+
compactionManagerOperations),
88+
"compactionStatsOperations is required");
8689
}
8790

8891
/**

adapters/adapters-base/src/main/java/org/apache/cassandra/sidecar/adapters/base/CassandraCompactionManagerOperations.java

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
* See the License for the specific language governing permissions and
1616
* limitations under the License.
1717
*/
18-
1918
package org.apache.cassandra.sidecar.adapters.base;
2019

20+
import java.util.Arrays;
2121
import java.util.List;
2222
import java.util.Map;
23+
import java.util.Objects;
24+
import java.util.stream.Collectors;
2325

2426
import org.apache.cassandra.sidecar.adapters.base.jmx.CompactionManagerJmxOperations;
2527
import org.apache.cassandra.sidecar.common.server.CompactionManagerOperations;
@@ -32,6 +34,11 @@
3234
*/
3335
public class CassandraCompactionManagerOperations implements CompactionManagerOperations
3436
{
37+
private static final List<String> SUPPORTED_COMPACTION_TYPES =
38+
Arrays.stream(CompactionType.values())
39+
.map(CompactionType::name)
40+
.collect(Collectors.toList());
41+
3542
protected final JmxClient jmxClient;
3643

3744
/**
@@ -53,4 +60,54 @@ public List<Map<String, String>> getCompactions()
5360
return jmxClient.proxy(CompactionManagerJmxOperations.class, COMPACTION_MANAGER_OBJ_NAME)
5461
.getCompactions();
5562
}
63+
64+
/**
65+
* {@inheritDoc}
66+
*/
67+
@Override
68+
public void stopCompactionById(String compactionId)
69+
{
70+
// compactionId takes precedence over type if both are provided
71+
if (compactionId != null && !compactionId.trim().isEmpty())
72+
{
73+
CompactionManagerJmxOperations proxy = jmxClient.proxy(CompactionManagerJmxOperations.class,
74+
COMPACTION_MANAGER_OBJ_NAME);
75+
proxy.stopCompactionById(compactionId);
76+
}
77+
else
78+
{
79+
throw new IllegalArgumentException("compaction process with compaction ID "
80+
+ compactionId + " is null or empty");
81+
}
82+
}
83+
84+
@Override
85+
public void stopCompaction(String compactionType)
86+
{
87+
if (compactionType != null && !compactionType.trim().isEmpty())
88+
{
89+
if (supportedCompactionTypes().contains(compactionType))
90+
{
91+
CompactionManagerJmxOperations proxy = jmxClient.proxy(CompactionManagerJmxOperations.class,
92+
COMPACTION_MANAGER_OBJ_NAME);
93+
String errMsg
94+
= "compaction process with compaction type " + compactionType + " must not be null when compactionId is not provided";
95+
proxy.stopCompaction(Objects.requireNonNull(compactionType, errMsg));
96+
}
97+
else
98+
{
99+
throw new IllegalArgumentException("compaction type " + compactionType + " is not supported");
100+
}
101+
}
102+
else
103+
{
104+
throw new IllegalArgumentException("compaction type " + compactionType + " is null or empty");
105+
}
106+
}
107+
108+
@Override
109+
public List<String> supportedCompactionTypes()
110+
{
111+
return SUPPORTED_COMPACTION_TYPES;
112+
}
56113
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.cassandra.sidecar.adapters.base;
19+
20+
import java.util.Arrays;
21+
import java.util.List;
22+
import java.util.Locale;
23+
import java.util.stream.Collectors;
24+
25+
import com.fasterxml.jackson.annotation.JsonCreator;
26+
27+
/**
28+
* Supported compaction types based on Cassandra's OperationType enum
29+
*/
30+
public enum CompactionType
31+
{
32+
CLEANUP,
33+
SCRUB,
34+
UPGRADE_SSTABLES,
35+
VERIFY,
36+
RELOCATE,
37+
GARBAGE_COLLECT,
38+
ANTICOMPACTION,
39+
VALIDATION,
40+
INDEX_BUILD,
41+
VIEW_BUILD,
42+
COMPACTION,
43+
TOMBSTONE_COMPACTION,
44+
KEY_CACHE_SAVE,
45+
ROW_CACHE_SAVE,
46+
COUNTER_CACHE_SAVE,
47+
INDEX_SUMMARY;
48+
49+
private static final List<String> SUPPORTED_COMPACTION_TYPES =
50+
Arrays.stream(org.apache.cassandra.sidecar.adapters.base.CompactionType.values())
51+
.map(org.apache.cassandra.sidecar.adapters.base.CompactionType::name)
52+
.collect(Collectors.toList());
53+
54+
@Override
55+
public String toString()
56+
{
57+
return name();
58+
}
59+
60+
/**
61+
* Case-insensitive factory method for Jackson deserialization
62+
* @return {@link CompactionType} from string
63+
*/
64+
@JsonCreator
65+
public static CompactionType fromString(String name)
66+
{
67+
if (name == null || name.trim().isEmpty())
68+
{
69+
return null;
70+
}
71+
try
72+
{
73+
return valueOf(name.trim().toUpperCase(Locale.ROOT));
74+
}
75+
catch (IllegalArgumentException unknownEnum)
76+
{
77+
throw new IllegalArgumentException(
78+
String.format("Unsupported compactionType: '%s'. Valid types are: %s",
79+
name, SUPPORTED_COMPACTION_TYPES));
80+
}
81+
}
82+
}

adapters/adapters-base/src/main/java/org/apache/cassandra/sidecar/adapters/base/jmx/CompactionManagerJmxOperations.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,16 @@ public interface CompactionManagerJmxOperations
3434
* @return list of compaction info maps
3535
*/
3636
List<Map<String, String>> getCompactions();
37+
38+
/**
39+
* Stop compaction by type
40+
* @throws IllegalArgumentException when compaction type is null
41+
*/
42+
void stopCompaction(String type);
43+
44+
/**
45+
* Stop compaction by ID
46+
* @throws IllegalArgumentException when compaction ID is null
47+
*/
48+
void stopCompactionById(String compactionId);
3749
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.cassandra.sidecar.adapters.base;
20+
21+
import org.junit.jupiter.api.BeforeEach;
22+
import org.junit.jupiter.api.Test;
23+
24+
import org.apache.cassandra.sidecar.adapters.base.jmx.CompactionManagerJmxOperations;
25+
import org.apache.cassandra.sidecar.common.server.JmxClient;
26+
27+
import static org.junit.jupiter.api.Assertions.assertThrows;
28+
import static org.apache.cassandra.sidecar.adapters.base.jmx.CompactionManagerJmxOperations.COMPACTION_MANAGER_OBJ_NAME;
29+
import static org.mockito.Mockito.mock;
30+
import static org.mockito.Mockito.times;
31+
import static org.mockito.Mockito.verify;
32+
import static org.mockito.Mockito.when;
33+
34+
35+
/**
36+
* Tests for {@link CassandraCompactionManagerOperations} class
37+
*/
38+
class CassandraCompactionManagerOperationsTest
39+
{
40+
private CassandraCompactionManagerOperations compactionManagerOperations;
41+
private JmxClient mockJmxClient;
42+
private CompactionManagerJmxOperations mockJmxOperations;
43+
44+
@BeforeEach
45+
void setUp()
46+
{
47+
mockJmxClient = mock(JmxClient.class);
48+
mockJmxOperations = mock(CompactionManagerJmxOperations.class);
49+
compactionManagerOperations = new CassandraCompactionManagerOperations(mockJmxClient);
50+
51+
// Setup JMX proxy mock
52+
when(mockJmxClient.proxy(CompactionManagerJmxOperations.class, COMPACTION_MANAGER_OBJ_NAME))
53+
.thenReturn(mockJmxOperations);
54+
}
55+
56+
@Test
57+
void testStopCompactionByIdOnly()
58+
{
59+
// Test stopCompactionById called when providing compactionId
60+
String compactionId = "abc-123";
61+
compactionManagerOperations.stopCompactionById(compactionId);
62+
63+
verify(mockJmxOperations, times(1)).stopCompactionById(compactionId);
64+
verify(mockJmxOperations, times(0)).stopCompaction(org.mockito.ArgumentMatchers.anyString());
65+
}
66+
67+
@Test
68+
void testStopCompactionByTypeOnly()
69+
{
70+
// Test stopCompaction called when no compactionId provided
71+
String compactionType = "COMPACTION";
72+
compactionManagerOperations.stopCompaction(compactionType);
73+
74+
verify(mockJmxOperations, times(1)).stopCompaction(compactionType);
75+
verify(mockJmxOperations, times(0)).stopCompactionById(org.mockito.ArgumentMatchers.anyString());
76+
}
77+
78+
@Test
79+
void testStopCompactionByIdWithWhitespace()
80+
{
81+
// Test trim does not result in empty string
82+
String compactionId = " abc-123 ";
83+
compactionManagerOperations.stopCompactionById(compactionId);
84+
85+
verify(mockJmxOperations, times(1)).stopCompactionById(compactionId);
86+
verify(mockJmxOperations, times(0)).stopCompaction(org.mockito.ArgumentMatchers.anyString());
87+
}
88+
89+
@Test
90+
void testStopCompactionAllSupportedTypes()
91+
{
92+
// Test no failures upon any supported type being provided as param
93+
String[] supportedTypes = {
94+
"COMPACTION", "VALIDATION", "KEY_CACHE_SAVE", "ROW_CACHE_SAVE",
95+
"COUNTER_CACHE_SAVE", "CLEANUP", "SCRUB", "UPGRADE_SSTABLES",
96+
"INDEX_BUILD", "TOMBSTONE_COMPACTION", "ANTICOMPACTION",
97+
"VERIFY", "VIEW_BUILD", "INDEX_SUMMARY", "RELOCATE",
98+
"GARBAGE_COLLECT"
99+
};
100+
101+
for (String type : supportedTypes)
102+
{
103+
compactionManagerOperations.stopCompaction(type);
104+
verify(mockJmxOperations, times(1)).stopCompaction(type);
105+
}
106+
}
107+
108+
@Test
109+
void testStopCompactionCatchesUnsupportedType()
110+
{
111+
String compactionType = "MAJOR_COMPACTION";
112+
assertThrows(IllegalArgumentException.class,
113+
() -> compactionManagerOperations.stopCompaction(compactionType));
114+
}
115+
116+
@Test
117+
void testStopCompactionJmxProxyCalledOnce()
118+
{
119+
// Test JMX proxy obtained exactly once per call
120+
compactionManagerOperations.stopCompactionById("test-id");
121+
122+
verify(mockJmxClient, times(1))
123+
.proxy(CompactionManagerJmxOperations.class, COMPACTION_MANAGER_OBJ_NAME);
124+
}
125+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
21+
import java.nio.file.Paths
22+
23+
plugins {
24+
id 'java-library'
25+
id 'idea'
26+
id 'maven-publish'
27+
id "com.github.spotbugs"
28+
}
29+
30+
apply from: "$rootDir/gradle/common/publishing.gradle"
31+
32+
sourceCompatibility = JavaVersion.VERSION_11
33+
34+
test {
35+
useJUnitPlatform()
36+
maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
37+
reports {
38+
junitXml.setRequired(true)
39+
def destDir = Paths.get(rootProject.rootDir.absolutePath, "build", "test-results", "adapters-cassandra50").toFile()
40+
println("Destination directory for adapters-cassandra50 tests: ${destDir}")
41+
junitXml.getOutputLocation().set(destDir)
42+
html.setRequired(true)
43+
html.getOutputLocation().set(destDir)
44+
}
45+
}
46+
47+
dependencies {
48+
api(project(":server-common"))
49+
api(project(":adapters:adapters-base"))
50+
api(project(":adapters:adapters-cassandra41"))
51+
52+
compileOnly('org.jetbrains:annotations:23.0.0')
53+
compileOnly('com.datastax.cassandra:cassandra-driver-core:3.11.3')
54+
implementation("org.slf4j:slf4j-api:${project.slf4jVersion}")
55+
}
56+
57+
spotbugsTest.enabled = false

0 commit comments

Comments
 (0)