Skip to content

feat: Add SentinelInstanceClient for direct Sentinel node administration#4463

Draft
ggivo wants to merge 6 commits into
masterfrom
topic/ggivo/CAE-2561-jedis-sdr-sentinel-admin-cmd
Draft

feat: Add SentinelInstanceClient for direct Sentinel node administration#4463
ggivo wants to merge 6 commits into
masterfrom
topic/ggivo/CAE-2561-jedis-sdr-sentinel-admin-cmd

Conversation

@ggivo
Copy link
Copy Markdown
Collaborator

@ggivo ggivo commented Mar 13, 2026

Introduce a new UnifiedJedis-based client for connecting directly to Redis Sentinel instances and executing Sentinel administrative commands.

Changes:

Add SentinelInstanceClient interface

  • Extends SentinelNodeCommands for Sentinel operations
  • Extends AutoCloseable for resource management
  • Provides direct access to all Sentinel administrative commands

Add RedisSentinelInstanceClient implementation

  • Extends UnifiedJedis
  • Implements SentinelInstanceClient interface
  • Supports Sentinel administrative commands (SENTINEL FAILOVER, SENTINEL MONITOR, SENTINEL MASTERS, SENTINEL REPLICAS, etc.)

Add SentinelNodeCommands interface

  • Add ping() method for Sentinel health checks
  • extends from SentinelCommands for Sentinel administrative commands

Enhance RedisSentinelClient

  • Add getAvailableSentinel() method to obtain a connected Sentinel instance

Add integration tests

  • RedisSentinelInstanceClientIT
  • RedisSentinelClientIT

This client enables direct interaction with Sentinel instances for:

  • Monitoring master/replica configurations
  • Triggering manual failovers

Public API Summary

New Interfaces

  • SentinelInstanceClient - Client interface for direct Sentinel instance operations (extends SentinelNodeCommands, AutoCloseable)
  • SentinelNodeCommands - Sentinel command interface with methods:
    • ping() - Health check
    • sentinelMyId() - Get Sentinel ID
    • sentinelMasters() / sentinelMaster(String) - Query masters
    • sentinelReplicas(String) / sentinelSentinels(String) - Query replicas/sentinels
    • sentinelFailover(String) - Trigger failover
    • sentinelMonitor(...) / sentinelRemove(String) - Manage monitoring
    • sentinelSet(...) / sentinelCkquorum(String) / sentinelFlushConfig() / sentinelReset(String) - Configuration

New Implementation

  • RedisSentinelInstanceClient - Concrete implementation with builder() method

Enhanced API

  • RedisSentinelClient.getAvailableSentinel() - Returns a connected SentinelInstanceClient from the configured Sentinel set

Example usage:

try (SentinelInstanceClient client = RedisSentinelInstanceClient.builder()
    .hostAndPort("localhost", 26379)
    .build()) {
  List<Map<String, String>> masters = client.sentinelMasters();
  client.sentinelFailover("mymaster");
}

// Or get an available Sentinel from RedisSentinelClient
try (RedisSentinelClient client = RedisSentinelClient.builder()
    .masterName("mymaster")
    .sentinels(sentinels)
    .build();
     SentinelInstanceClient sentinel = client.getAvailableSentinel()) {
  String myId = sentinel.sentinelMyId();
}

Note

Medium Risk
Introduces new public Sentinel client interfaces and new connection logic (getAvailableSentinel) that opens/validates sentinel connections, which could affect Sentinel-mode users if misconfigured or if resource handling differs.

Overview
Adds a new direct-to-Sentinel client surface: sentinel.api.SentinelInstanceClient/SentinelNodeCommands and an internal RedisSentinelInstanceClient implementation to execute Sentinel admin commands (e.g., SENTINEL MASTERS, MASTER, REPLICAS, FAILOVER) over a standalone connection.

Enhances RedisSentinelClient to retain a SentinelConfiguration and expose getAvailableSentinel(), which iterates configured sentinels, PINGs for health, and returns the first working SentinelInstanceClient (else throws JedisConnectionException).

Extends response decoding with BuilderFactory.STRING_MAP_LIST for Sentinel replies, relaxes SentinelClientBuilder field visibility for reuse, updates/adjusts Sentinel integration tests, and expands formatter includes to cover new Sentinel-related sources/tests.

Written by Cursor Bugbot for commit 21042b5. This will update automatically on new commits. Configure here.

@jit-ci
Copy link
Copy Markdown

jit-ci Bot commented Mar 13, 2026

🛡️ Jit Security Scan Results

CRITICAL HIGH MEDIUM

✅ No security findings were detected in this PR


Security scan by Jit

Introduce a new UnifiedJedis-based client for connecting directly to Redis
Sentinel instances and executing Sentinel administrative commands.

Changes:
- Add SentinelInstanceClient interface
  * Extends SentinelNodeCommands for Sentinel operations
  * Extends AutoCloseable for resource management
  * Provides direct access to all Sentinel administrative commands

- Add RedisSentinelInstanceClient implementation
  * Extends UnifiedJedis for consistent client architecture
  * Implements SentinelInstanceClient interface
  * Provides builder pattern via StandaloneClientBuilder
  * Supports Sentinel administrative commands (SENTINEL FAILOVER,
    SENTINEL MONITOR, SENTINEL MASTERS, SENTINEL REPLICAS, etc.)

- Add SentinelNodeCommands interface
  * Add ping() method for Sentinel health checks
  * extends from SentinelCommands for Sentinel administrative commands

- Enhance RedisSentinelClient
  * Add getAvailableSentinel() method to obtain a connected Sentinel instance toward one of used sentinels
  * Uses ping() for health checking when selecting available Sentinel

- Add integration tests
  * RedisSentinelInstanceClientIT
  * RedisSentinelClientIT

This client enables direct interaction with Sentinel instances for:
- Monitoring master/replica configurations
- Triggering manual failovers
- Managing Sentinel monitoring configurations
- Querying Sentinel cluster state
- Health checking Sentinel instances

Example usage:
  try (SentinelInstanceClient client = RedisSentinelInstanceClient.builder()
      .hostAndPort("localhost", 26379)
      .build()) {
    List<Map<String, String>> masters = client.sentinelMasters();
    client.sentinelFailover("mymaster");
  }

  // Or get an available Sentinel from RedisSentinelClient
  try (RedisSentinelClient client = RedisSentinelClient.builder()
      .masterName("mymaster")
      .sentinels(sentinels)
      .build();
       SentinelInstanceClient sentinel = client.getAvailableSentinel()) {
    String myId = sentinel.sentinelMyId();
  }

Related to: CAE-2561
@ggivo ggivo force-pushed the topic/ggivo/CAE-2561-jedis-sdr-sentinel-admin-cmd branch from b2cce48 to 7791fa8 Compare March 13, 2026 15:19
Comment thread src/main/java/redis/clients/jedis/RedisSentinelClient.java
Comment thread src/main/java/redis/clients/jedis/RedisSentinelInstanceClient.java Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 13, 2026

Test Results

   307 files  + 2     307 suites  +2   11m 57s ⏱️ + 3m 4s
10 956 tests +43  10 741 ✅ +1 519  215 💤  - 1 476  0 ❌ ±0 
 5 671 runs  +49   5 604 ✅ +  578   67 💤  -   529  0 ❌ ±0 

Results for commit 21042b5. ± Comparison against base commit fef7829.

This pull request removes 1 and adds 44 tests. Note that renamed tests count towards both.
redis.clients.jedis.commands.unified.search.FTHybridCommandsTestBase$SupportedScorersTest ‑ testScorer(Scorer, double, double)
redis.clients.jedis.RedisSentinelClientIT ‑ testBasicOperations
redis.clients.jedis.RedisSentinelClientIT ‑ testGetAvailableSentinel
redis.clients.jedis.RedisSentinelClientIT ‑ testGetCurrentMaster
redis.clients.jedis.RedisSentinelInstanceClientIT ‑ ping
redis.clients.jedis.RedisSentinelInstanceClientIT ‑ sentinel
redis.clients.jedis.RedisSentinelInstanceClientIT ‑ sentinelFailover
redis.clients.jedis.RedisSentinelInstanceClientIT ‑ sentinelMaster
redis.clients.jedis.RedisSentinelInstanceClientIT ‑ sentinelMonitor
redis.clients.jedis.RedisSentinelInstanceClientIT ‑ sentinelMyId
redis.clients.jedis.RedisSentinelInstanceClientIT ‑ sentinelRemove
…

♻️ This comment has been updated with latest results.

Comment thread src/main/java/redis/clients/jedis/RedisSentinelInstanceClient.java Outdated
Comment thread src/main/java/redis/clients/jedis/commands/SentinelCommands.java Outdated
Comment thread src/test/java/redis/clients/jedis/JedisSentinelTest.java
@ggivo ggivo requested review from atakavci, tishun and uglide March 13, 2026 17:00
Copy link
Copy Markdown
Contributor

@tishun tishun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second thing I commented got me very confused

* @see SentinelNodeCommands
* @since 7.5.0
*/
public interface SentinelInstanceClient extends SentinelNodeCommands, AutoCloseable {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should consider this from the perspective of what common interface we would want to have used instead of the UnifiedJedis.

If we want to aim at something like RedisConnection that implements the Redis*Commands (or returns them) we might consider using here SentinelConnection? Or SentinelManagementConnection?

Not sure. tbh we can also think about SentinelManagementClient - if we want to follow the RedisClient / RedisClusterClient / RedisSentinelClient.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't like current naming.
RedisSentinelClient makes most sense to me but is already used.
Probably SentinelManagementClient and actual implementation could be RedisSentinelManagementClient

import static redis.clients.jedis.Protocol.Command.SENTINEL;
import static redis.clients.jedis.Protocol.SentinelKeyword.*;

public final class RedisSentinelInstanceClient extends UnifiedJedis
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we implement the UnifiedJedis?
This is very different than what I was thinking we are doing...

Comment thread pom.xml
<include>**/resps/LibraryInfoTest.java</include>
<include>**/*Matchers.java</include>
<include>**/*TestUtil.java</include>
<include>**/sentinel/*.java</include>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatter glob pattern misses sentinel/api subdirectory files

Low Severity

The formatter include pattern **/sentinel/*.java only matches Java files directly inside a sentinel/ directory, but the new SentinelInstanceClient.java and SentinelNodeCommands.java files are in sentinel/api/. The Ant-style glob * does not cross directory boundaries, so these files won't be covered by the formatter validation. The pattern needs to be **/sentinel/**/*.java to include subdirectories.

Fix in Cursor Fix in Web

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

ConnectionProvider connectionProvider, CommandObjects commandObjects,
RedisProtocol redisProtocol, Cache cache) {
super(commandExecutor, connectionProvider, commandObjects, redisProtocol, cache);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extending UnifiedJedis exposes inappropriate Redis commands

Medium Severity

RedisSentinelInstanceClient extends UnifiedJedis, inheriting all standard Redis data commands (SET, GET, HSET, etc.) that are not valid on a Sentinel node. While the SentinelInstanceClient interface only exposes sentinel commands, the implementation carries unnecessary weight and any internal code or reflection-based access could invoke unsupported operations. A composition-based approach (wrapping a connection) would be more appropriate than inheritance here.

Fix in Cursor Fix in Web

@ggivo ggivo marked this pull request as draft March 20, 2026 13:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants