This is an automated email from the ASF dual-hosted git repository.
frankgh pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-sidecar.git
The following commit(s) were added to refs/heads/trunk by this push:
new fa9439b8 CASSSIDECAR-366: Added endpoint for returning the system disk
information (#297)
fa9439b8 is described below
commit fa9439b8d4ed3f35f3aafafea4ac238097165dc6
Author: N V Harikrishna <[email protected]>
AuthorDate: Mon Dec 8 07:08:56 2025 +0530
CASSSIDECAR-366: Added endpoint for returning the system disk information
(#297)
Patch by N V Harikrishna; reviewed by Francisco Guerrero, Yifan Cai for
CASSSIDECAR-366
---
CHANGES.txt | 1 +
.../cassandra/sidecar/common/ApiEndpointsV1.java | 3 +
.../sidecar/common/response/data/DiskInfo.java | 89 ++++++++++
.../sidecar/common/response/data/DiskInfoTest.java | 89 ++++++++++
gradle.properties | 2 +
server/build.gradle | 3 +
.../acl/authorization/BasicPermissions.java | 3 +
.../acl/authorization/FeaturePermission.java | 5 +-
.../sidecar/handlers/sysinfo/DiskInfoHandler.java | 181 +++++++++++++++++++++
.../cassandra/sidecar/modules/SidecarModules.java | 3 +-
.../cassandra/sidecar/modules/SysInfoModule.java | 67 ++++++++
.../modules/multibindings/VertxRouteMapKeys.java | 5 +
.../handlers/sysinfo/DiskInfoHandlerTest.java | 94 +++++++++++
13 files changed, 543 insertions(+), 2 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index d173bd98..83f6ae67 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
0.3.0
-----
+ * Added system disk information endpoint to Cassandra Sidecar
(CASSSIDECAR-366)
* Adding support for quoted tables and keyspaces in snapshot cleanup
(CASSSIDECAR-388)
* File descriptor leak after file streamed in Sidecar Client (CASSSIDECAR-386)
* Endpoint to invalidate auth caches as required (CASSSIDECAR-364)
diff --git
a/client-common/src/main/java/org/apache/cassandra/sidecar/common/ApiEndpointsV1.java
b/client-common/src/main/java/org/apache/cassandra/sidecar/common/ApiEndpointsV1.java
index 70742924..f9911b63 100644
---
a/client-common/src/main/java/org/apache/cassandra/sidecar/common/ApiEndpointsV1.java
+++
b/client-common/src/main/java/org/apache/cassandra/sidecar/common/ApiEndpointsV1.java
@@ -177,6 +177,9 @@ public final class ApiEndpointsV1
// Lifecycle APIs
public static final String LIFECYCLE_ROUTE = API_V1 + CASSANDRA +
"/lifecycle";
+ private static final String SYSTEM_API_PREFIX = API_V1 + "/system";
+ public static final String SYSTEM_DISK_INFO_ROUTE = SYSTEM_API_PREFIX +
"/disk-info";
+
private ApiEndpointsV1()
{
throw new IllegalStateException(getClass() + " is a constants
container and shall not be instantiated");
diff --git
a/client-common/src/main/java/org/apache/cassandra/sidecar/common/response/data/DiskInfo.java
b/client-common/src/main/java/org/apache/cassandra/sidecar/common/response/data/DiskInfo.java
new file mode 100644
index 00000000..6416b546
--- /dev/null
+++
b/client-common/src/main/java/org/apache/cassandra/sidecar/common/response/data/DiskInfo.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.common.response.data;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Represents information about a single disk. It contains space metrics
(total, free, usable)
+ * and identifying information (name, mount point, type).
+ */
+public class DiskInfo
+{
+
+ private final long totalSpace;
+ private final long freeSpace;
+ private final long usableSpace;
+ private final String name;
+ private final String mount;
+ private final String type;
+
+ @JsonCreator
+ public DiskInfo(@JsonProperty("totalSpace") long totalSpace,
+ @JsonProperty("freeSpace") long freeSpace,
+ @JsonProperty("usableSpace") long usableSpace,
+ @JsonProperty("name") String name,
+ @JsonProperty("mount") String mount,
+ @JsonProperty("type") String type)
+ {
+ this.totalSpace = totalSpace;
+ this.freeSpace = freeSpace;
+ this.usableSpace = usableSpace;
+ this.name = name;
+ this.mount = mount;
+ this.type = type;
+ }
+
+ @JsonProperty("totalSpace")
+ public long totalSpace()
+ {
+ return totalSpace;
+ }
+
+ @JsonProperty("freeSpace")
+ public long freeSpace()
+ {
+ return freeSpace;
+ }
+
+ @JsonProperty("usableSpace")
+ public long usableSpace()
+ {
+ return usableSpace;
+ }
+
+ @JsonProperty("name")
+ public String name()
+ {
+ return name;
+ }
+
+ @JsonProperty("mount")
+ public String mount()
+ {
+ return mount;
+ }
+
+ @JsonProperty("type")
+ public String type()
+ {
+ return type;
+ }
+}
diff --git
a/client-common/src/test/java/org/apache/cassandra/sidecar/common/response/data/DiskInfoTest.java
b/client-common/src/test/java/org/apache/cassandra/sidecar/common/response/data/DiskInfoTest.java
new file mode 100644
index 00000000..33cb07f0
--- /dev/null
+++
b/client-common/src/test/java/org/apache/cassandra/sidecar/common/response/data/DiskInfoTest.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.common.response.data;
+
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Unit tests for {@link DiskInfo} JSON serialization and deserialization.
+ */
+class DiskInfoTest
+{
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Test
+ void testSerializationDeserializationRoundTrip() throws Exception
+ {
+ DiskInfo original = new DiskInfo(1000000000L, 500000000L, 450000000L,
+ "data", "/dev/sda1", "ext4");
+
+ String json = objectMapper.writeValueAsString(original);
+ DiskInfo deserialized = objectMapper.readValue(json, DiskInfo.class);
+
+ assertThat(deserialized.totalSpace()).isEqualTo(original.totalSpace());
+ assertThat(deserialized.freeSpace()).isEqualTo(original.freeSpace());
+
assertThat(deserialized.usableSpace()).isEqualTo(original.usableSpace());
+ assertThat(deserialized.name()).isEqualTo(original.name());
+ assertThat(deserialized.mount()).isEqualTo(original.mount());
+ assertThat(deserialized.type()).isEqualTo(original.type());
+ }
+
+ @Test
+ void testJsonStructure() throws Exception
+ {
+ DiskInfo response = new DiskInfo(1000000000L, 500000000L, 450000000L,
+ "data", "/dev/sda1", "ext4");
+
+ String json = objectMapper.writeValueAsString(response);
+
+ assertThat(json).contains("\"totalSpace\":1000000000");
+ assertThat(json).contains("\"freeSpace\":500000000");
+ assertThat(json).contains("\"usableSpace\":450000000");
+ assertThat(json).contains("\"name\":\"data\"");
+ assertThat(json).contains("\"mount\":\"/dev/sda1\"");
+ assertThat(json).contains("\"type\":\"ext4\"");
+ }
+
+ @Test
+ void testMultipleDisksSerialization() throws Exception
+ {
+ DiskInfo disk1 = new DiskInfo(1000000000L, 500000000L, 450000000L,
+ "data1", "/dev/sda1", "ext4");
+ DiskInfo disk2 = new DiskInfo(2000000000L, 1000000000L, 950000000L,
+ "data2", "/dev/sdb1", "xfs");
+
+ String json1 = objectMapper.writeValueAsString(disk1);
+ String json2 = objectMapper.writeValueAsString(disk2);
+
+ DiskInfo deserialized1 = objectMapper.readValue(json1, DiskInfo.class);
+ DiskInfo deserialized2 = objectMapper.readValue(json2, DiskInfo.class);
+
+ assertThat(deserialized1.name()).isEqualTo("data1");
+ assertThat(deserialized1.mount()).isEqualTo("/dev/sda1");
+ assertThat(deserialized1.type()).isEqualTo("ext4");
+
+ assertThat(deserialized2.name()).isEqualTo("data2");
+ assertThat(deserialized2.mount()).isEqualTo("/dev/sdb1");
+ assertThat(deserialized2.type()).isEqualTo("xfs");
+ }
+}
diff --git a/gradle.properties b/gradle.properties
index ef8ed7a8..d5cd6a32 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -44,3 +44,5 @@ swaggerVersion=2.2.21
# Cdc dependencies
kryoVersion=4.0.2
analyticsVersion=0.1.0
+# OSHI dependencies
+oshiVersion=6.9.0
diff --git a/server/build.gradle b/server/build.gradle
index 483b517a..53419b0b 100644
--- a/server/build.gradle
+++ b/server/build.gradle
@@ -147,6 +147,9 @@ dependencies {
implementation(group: "org.apache.cassandra", name:
"cassandra-analytics-cdc_spark3_2.12", version: "${[project.analyticsVersion]}")
implementation "com.esotericsoftware:kryo-shaded:${kryoVersion}"
+ // OSHI core library for fetching system information
+ implementation("com.github.oshi:oshi-core:${oshiVersion}")
+
testImplementation
"org.junit.jupiter:junit-jupiter-api:${project.junitVersion}"
testImplementation
"org.junit.jupiter:junit-jupiter-params:${project.junitVersion}"
testImplementation "org.assertj:assertj-core:3.24.2"
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/BasicPermissions.java
b/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/BasicPermissions.java
index f09ed863..2195122e 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/BasicPermissions.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/BasicPermissions.java
@@ -92,4 +92,7 @@ public class BasicPermissions
// Lifecycle permissions
public static final Permission READ_LIFECYCLE = new
DomainAwarePermission("LIFECYCLE:READ", CLUSTER_SCOPE);
public static final Permission MODIFY_LIFECYCLE = new
DomainAwarePermission("LIFECYCLE:MODIFY", CLUSTER_SCOPE);
+
+ // System information related permissions
+ public static final Permission DISK_INFO = new
StandardPermission("DISK_INFO:READ", CLUSTER_SCOPE);
}
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/FeaturePermission.java
b/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/FeaturePermission.java
index eff119b8..0d22435b 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/FeaturePermission.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/FeaturePermission.java
@@ -28,6 +28,7 @@ import static
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.DA
import static
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.DELETE_RESTORE_JOB;
import static
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.DELETE_SNAPSHOT;
import static
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.DELETE_STAGED_SSTABLE;
+import static
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.DISK_INFO;
import static
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.EDIT_RESTORE_JOB;
import static
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.IMPORT_STAGED_SSTABLE;
import static
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.LIST_FILES;
@@ -80,7 +81,9 @@ public enum FeaturePermission
* Permission for live migration data copy operations. This feature
permission grants access to
* stream files and list files, which are necessary for copying data
during live migration processes.
*/
- LIVE_MIGRATION("LIVE_MIGRATION", DATA_COPY, LIST_FILES);
+ LIVE_MIGRATION("LIVE_MIGRATION", DATA_COPY, LIST_FILES),
+
+ SYSTEM("SYSTEM", DISK_INFO);
public static final List<CompositePermission> ALL_FEATURE_PERMISSIONS
=
Arrays.stream(values()).map(FeaturePermission::permission).collect(Collectors.toList());
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/handlers/sysinfo/DiskInfoHandler.java
b/server/src/main/java/org/apache/cassandra/sidecar/handlers/sysinfo/DiskInfoHandler.java
new file mode 100644
index 00000000..dda33a6c
--- /dev/null
+++
b/server/src/main/java/org/apache/cassandra/sidecar/handlers/sysinfo/DiskInfoHandler.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.handlers.sysinfo;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.vertx.core.http.HttpServerRequest;
+import io.vertx.core.net.SocketAddress;
+import io.vertx.ext.auth.authorization.Authorization;
+import io.vertx.ext.web.RoutingContext;
+import org.apache.cassandra.sidecar.acl.authorization.BasicPermissions;
+import org.apache.cassandra.sidecar.common.response.data.DiskInfo;
+import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
+import org.apache.cassandra.sidecar.handlers.AbstractHandler;
+import org.apache.cassandra.sidecar.handlers.AccessProtected;
+import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
+import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
+import org.apache.cassandra.sidecar.utils.TimeProvider;
+import org.jetbrains.annotations.NotNull;
+import oshi.SystemInfo;
+import oshi.software.os.OSFileStore;
+
+import static
org.apache.cassandra.sidecar.utils.HttpExceptions.wrapHttpException;
+
+/**
+ * Handler that retrieves disk information for all file stores on the system.
+ * Uses OSHI library to gather metrics about storage capacity and usage for
each mounted file system.
+ * Disk information is cached for 5 minutes to avoid expensive OSHI calls on
every request.
+ */
+@Singleton
+public class DiskInfoHandler extends AbstractHandler<Void> implements
AccessProtected
+{
+ private static final Logger LOGGER =
LoggerFactory.getLogger(DiskInfoHandler.class);
+ private static final Duration CACHE_TTL = Duration.ofMinutes(5);
+
+ private final AtomicReference<CacheEntry> diskInfoCache;
+ private final TimeProvider timeProvider;
+
+ /**
+ * Constructs a handler with the provided {@code metadataFetcher}
+ *
+ * @param metadataFetcher the interface to retrieve instance metadata
+ * @param executorPools the executor pools for blocking executions
+ * @param validator a validator instance to validate
Cassandra-specific input
+ */
+ @Inject
+ protected DiskInfoHandler(InstanceMetadataFetcher metadataFetcher,
+ ExecutorPools executorPools,
+ CassandraInputValidator validator,
+ TimeProvider timeProvider)
+ {
+ super(metadataFetcher, executorPools, validator);
+ this.diskInfoCache = new AtomicReference<>();
+ this.timeProvider = timeProvider;
+ }
+
+
+ @Override
+ public Set<Authorization> requiredAuthorizations()
+ {
+ return Set.of(BasicPermissions.DISK_INFO.toAuthorization());
+ }
+
+ @Override
+ protected Void extractParamsOrThrow(RoutingContext context)
+ {
+ // No params to extract
+ return null;
+ }
+
+ @Override
+ protected void handleInternal(RoutingContext context,
+ HttpServerRequest httpRequest,
+ @NotNull String host,
+ SocketAddress remoteAddress,
+ Void request)
+ {
+ executorPools.internal()
+ .executeBlocking(this::getCachedDiskInfo)
+ .onSuccess(context::json)
+ .onFailure(e -> {
+ LOGGER.warn("Failed to fetch disk information", e);
+
context.fail(wrapHttpException(HttpResponseStatus.SERVICE_UNAVAILABLE,
+ e.getMessage(), e));
+ });
+ }
+
+ /**
+ * Loads disk information from the system using OSHI
+ */
+ private List<DiskInfo> loadDiskInfo()
+ {
+ SystemInfo systemInfo = new SystemInfo();
+ List<OSFileStore> fileStoreList =
systemInfo.getOperatingSystem().getFileSystem().getFileStores();
+ List<DiskInfo> diskInfoList = new ArrayList<>(fileStoreList.size());
+
+ for (OSFileStore fileStore : fileStoreList)
+ {
+ diskInfoList.add(new DiskInfo(fileStore.getTotalSpace(),
+ fileStore.getFreeSpace(),
+ fileStore.getUsableSpace(),
+ fileStore.getName(),
+ fileStore.getMount(),
+ fileStore.getType()));
+ }
+
+ return diskInfoList;
+ }
+
+ /**
+ * Gets cached disk info or loads fresh data if cache is expired or empty
+ */
+ private List<DiskInfo> getCachedDiskInfo()
+ {
+ CacheEntry current = diskInfoCache.get();
+
+ // Check if cache is empty or expired
+ if (current == null || current.isExpired())
+ {
+ List<DiskInfo> freshData = loadDiskInfo();
+ CacheEntry newEntry = new CacheEntry(freshData, timeProvider,
CACHE_TTL);
+
+ // Try to update with CAS
+ if (diskInfoCache.compareAndSet(current, newEntry))
+ {
+ return freshData;
+ }
+ }
+
+ return diskInfoCache.get().diskInfo;
+ }
+
+
+ /**
+ * Cache entry that holds the disk information and the expiry time
+ */
+ private static class CacheEntry
+ {
+ private final List<DiskInfo> diskInfo;
+ private final long expiryTime;
+ private final TimeProvider timeProvider;
+
+ CacheEntry(List<DiskInfo> diskInfo, TimeProvider timeProvider,
Duration ttl)
+ {
+ this.diskInfo = diskInfo;
+ this.timeProvider = timeProvider;
+ this.expiryTime = timeProvider.currentTimeMillis() +
ttl.toMillis();
+ }
+
+ boolean isExpired()
+ {
+ return timeProvider.currentTimeMillis() > expiryTime;
+ }
+ }
+}
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/modules/SidecarModules.java
b/server/src/main/java/org/apache/cassandra/sidecar/modules/SidecarModules.java
index dc798177..50073b51 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/modules/SidecarModules.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/modules/SidecarModules.java
@@ -62,7 +62,8 @@ public class SidecarModules
new SidecarSchemaModule(),
new SSTablesAccessModule(),
new TelemetryModule(),
- new UtilitiesModule());
+ new UtilitiesModule(),
+ new SysInfoModule());
}
/**
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/modules/SysInfoModule.java
b/server/src/main/java/org/apache/cassandra/sidecar/modules/SysInfoModule.java
new file mode 100644
index 00000000..ed75625d
--- /dev/null
+++
b/server/src/main/java/org/apache/cassandra/sidecar/modules/SysInfoModule.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.modules;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.multibindings.ProvidesIntoMap;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import org.apache.cassandra.sidecar.common.ApiEndpointsV1;
+import org.apache.cassandra.sidecar.common.response.data.DiskInfo;
+import org.apache.cassandra.sidecar.handlers.sysinfo.DiskInfoHandler;
+import org.apache.cassandra.sidecar.modules.multibindings.KeyClassMapKey;
+import org.apache.cassandra.sidecar.modules.multibindings.VertxRouteMapKeys;
+import org.apache.cassandra.sidecar.routes.RouteBuilder;
+import org.apache.cassandra.sidecar.routes.VertxRoute;
+import org.eclipse.microprofile.openapi.annotations.Operation;
+import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
+import org.eclipse.microprofile.openapi.annotations.media.Content;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
+
+/**
+ * Guice module that provides system information-related routes and handlers.
+ * Configures endpoints for retrieving system metrics such as disk information.
+ */
+public class SysInfoModule extends AbstractModule
+{
+
+ @GET
+ @Path(ApiEndpointsV1.SYSTEM_DISK_INFO_ROUTE)
+ @Operation(summary = "Retrieves disks information",
+ description = "Retrieves information about disks of the current
system")
+ @APIResponse(description = "Disk information reported successfully",
+ responseCode = "200",
+ content = @Content(mediaType = "application/json",
+ schema = @Schema(type = SchemaType.ARRAY, implementation =
DiskInfo.class,
+ example =
"[{\"totalSpace\":1000000000000,\"freeSpace\":500000000000,\"usableSpace\":450000000000,\"name\":\"data1\",\"mount\":\"/dev/sda1\",\"type\":\"ext4\"},{\"totalSpace\":2000000000000,\"freeSpace\":1500000000000,\"usableSpace\":1400000000000,\"name\":\"data2\",\"mount\":\"/dev/sdb1\",\"type\":\"xfs\"}]")))
+ @APIResponse(description = "Unauthorized - requires SYSTEM permission",
+ responseCode = "401")
+ @APIResponse(description = "Service unavailable - unable to fetch disk
information",
+ responseCode = "503")
+ @ProvidesIntoMap
+ @KeyClassMapKey(VertxRouteMapKeys.SystemDiskInfoRoute.class)
+ VertxRoute getDiskInfoHandler(RouteBuilder.Factory factory,
+ DiskInfoHandler diskInfoHandler)
+ {
+ return factory.builderForRoute()
+ .handler(diskInfoHandler)
+ .build();
+ }
+}
diff --git
a/server/src/main/java/org/apache/cassandra/sidecar/modules/multibindings/VertxRouteMapKeys.java
b/server/src/main/java/org/apache/cassandra/sidecar/modules/multibindings/VertxRouteMapKeys.java
index 99bd450c..c798fbde 100644
---
a/server/src/main/java/org/apache/cassandra/sidecar/modules/multibindings/VertxRouteMapKeys.java
+++
b/server/src/main/java/org/apache/cassandra/sidecar/modules/multibindings/VertxRouteMapKeys.java
@@ -314,6 +314,11 @@ public interface VertxRouteMapKeys
HttpMethod HTTP_METHOD = HttpMethod.GET;
String ROUTE_URI =
ApiEndpointsV1.COMPONENTS_WITH_SECONDARY_INDEX_ROUTE_SUPPORT;
}
+ interface SystemDiskInfoRoute extends RouteClassKey
+ {
+ HttpMethod HTTP_METHOD = HttpMethod.GET;
+ String ROUTE_URI = ApiEndpointsV1.SYSTEM_DISK_INFO_ROUTE;
+ }
interface TableStatsRouteKey extends RouteClassKey
{
HttpMethod HTTP_METHOD = HttpMethod.GET;
diff --git
a/server/src/test/java/org/apache/cassandra/sidecar/handlers/sysinfo/DiskInfoHandlerTest.java
b/server/src/test/java/org/apache/cassandra/sidecar/handlers/sysinfo/DiskInfoHandlerTest.java
new file mode 100644
index 00000000..f64648a4
--- /dev/null
+++
b/server/src/test/java/org/apache/cassandra/sidecar/handlers/sysinfo/DiskInfoHandlerTest.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.handlers.sysinfo;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.util.Modules;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.vertx.core.Vertx;
+import io.vertx.ext.web.client.WebClient;
+import io.vertx.ext.web.codec.BodyCodec;
+import io.vertx.junit5.VertxExtension;
+import io.vertx.junit5.VertxTestContext;
+import org.apache.cassandra.sidecar.TestModule;
+import org.apache.cassandra.sidecar.common.ApiEndpointsV1;
+import org.apache.cassandra.sidecar.modules.SidecarModules;
+import org.apache.cassandra.sidecar.server.Server;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ExtendWith(VertxExtension.class)
+class DiskInfoHandlerTest
+{
+ private static final Logger LOGGER =
LoggerFactory.getLogger(DiskInfoHandlerTest.class);
+
+ private Server server;
+ private Vertx vertx;
+
+ @BeforeEach
+ public void setup() throws InterruptedException
+ {
+ Injector injector =
Guice.createInjector(Modules.override(SidecarModules.all())
+ .with(new
TestModule()));
+
+ vertx = injector.getInstance(Vertx.class);
+ server = injector.getInstance(Server.class);
+ VertxTestContext context = new VertxTestContext();
+ server.start()
+ .onSuccess(s -> context.completeNow())
+ .onFailure(context::failNow);
+ context.awaitCompletion(15, TimeUnit.SECONDS);
+ }
+
+ @AfterEach
+ void after() throws InterruptedException
+ {
+ CountDownLatch closeLatch = new CountDownLatch(1);
+ server.close().onSuccess(res -> closeLatch.countDown());
+ if (closeLatch.await(60, TimeUnit.SECONDS))
+ LOGGER.info("Close event received before timeout.");
+ else
+ LOGGER.error("Close event timed out.");
+ }
+
+ @Test
+ void testHappyPath(VertxTestContext context)
+ {
+ WebClient client = WebClient.create(vertx);
+ client.get(server.actualPort(), "localhost",
ApiEndpointsV1.SYSTEM_DISK_INFO_ROUTE)
+ .as(BodyCodec.buffer())
+ .send(context.succeeding(resp -> context.verify(() -> {
+
assertThat(resp.statusCode()).isEqualTo(HttpResponseStatus.OK.code());
+ assertThat(resp.bodyAsJsonArray()).isNotNull();
+ client.close();
+ context.completeNow();
+ })));
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]