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]

Reply via email to