Status

Current state: ACCEPTED

Discussion thread: https://lists.apache.org/thread/pow83q92m666nqtwyw4m3b18nnkgj2y8

Voting thread: https://lists.apache.org/thread/kmcxqwjzrr7t24qnx4jncsjwpr9nqgf9

Slack: https://the-asf.slack.com/archives/CK23JSY2K/p1688662169018449

JIRA:  

Released: Unreleased

Please keep the discussion on the mailing list rather than commenting on the wiki (wiki discussions get unwieldy fast).

Motivation

Cassandra makes use of the JMX to expose the management commands, such as taking a snapshot operation of specified keyspaces, with local JMX access enabled by default and remote access enabled by configuration when it's needed. JMX is used as the transport layer for command execution by most of the management tools, such as the built-in command line tool, the `nodetool` that ships with the Cassandra itself, or the Cassandra Sidecar a standalone JVM process that runs alongside the Cassandra server daemon and is used for the configuration management and/or metrics exposure.

This means that the extensive management API that Cassandra has is tied to the JMX MBeans themselves, and this approach has the following drawbacks, some of which are related to JMX itself:

There is no intention to fix everything in an initial pull request, for reasons of backwards compatibility. However, once the necessary steps have been taken to support command execution via CQL will be completed, the adopted deprecation policy will be applied.

Audience

Goals

Non-Goals

Considering how much of the world's infrastructure relies on our current JMX interfaces to orchestrate C* clusters, there is no appetite to update or remove existing MBeans as well as CLI tools currently in use. This proposal doesn't also imply that JMX will be deprecated once the design is fully implemented, but rather it moves the state of the project toward the point where such deprecation might be possible.

Proposed Changes

The proposed design describes all the changes that are needed to support command execution via CQL from a high-level component perspective, changes that are necessary to understand the solution design, and only incorporates public API changes, leaving internal classes and interfaces undescribed to allow room for manoeuvre during the implementation.

Basic Requirements

Requirements that are necessary to support the execution of management commands through CQL:

Cassandra Solutions

Currently, there are a few ways to perform management operations:

Other Vendor Solutions

How other vendors have solved the same problem:

Command Specifications

At present, Apache Cassandra doesn't advocate a unified approach to the specification of management commands that are promoted directly by the C* node itself. The heart of these management commands lies in the JMX MBeans. While each MBean provides operations on the same keyspaces and tables, they differ subtly in aspects such as command parameter names and their order. Additionally, MBeans do not provide fine-grained command metadata to a user. This limitation causes command-line interfaces and other tools that depend on the JMX API to create their layer of organizing MBean operations, adding an extra layer of grouping to the process. 

For example, the same operations on a keyspace are represented differently for the user's public API:

To solve this problem we can use the same design approach that has been adopted by the Dropwizard Metrics library for metrics [5], choosing the right granularity when creating command metadata. This means that all of the commands that are executed across the same keyspaces must have a common denominator of the command arguments and the order of the arguments.

Command Migration and Refactoring

The nodetool commands are reused to provide management operations via different APIs. To achieve this, we need to consider the following:


To remove the JMX specificity from commands, it is suggested that the following abstraction be added to commands as a replacement for the NodeProbe class.

/**
 * Service bridge for Cassandra management commands to access MBeans and other service-related functionality.
 * This interface is used to bridge the gap between the nodetool commands and the Cassandra service MBeans.
 */
public interface ServiceMBeanBridge
{
    <T> T getMBean(Class<T> clazz);

    default StorageServiceMBean ssProxy()
    {
        return getMBean(StorageServiceMBean.class);
    }

    default CompactionManagerMBean cmProxy()
    {
        return getMBean(CompactionManagerMBean.class);
    }
}


Command Registry

The starting point for management operations is the CommandRegistry, which is a collection of all commands, subcommands, and command metadatas for C* management.

The following process is supported:


public interface CommandRegistry<A, R> extends Command<A, R>
{
    public Command<?, ?> command(String name);
    public Iterator<Map.Entry<String, Command<?, ?>>> commands();
}

Command API

The nodetool CLI uses the Airline annotation-based framework [6] to parse input arguments and execute management commands from the command line; these annotations already contain all the necessary command metadata including input arguments, their descriptions, and general command details. A significant limitation, however, is that the metadata is embedded in the CLI tool itself, making it inaccessible on the C* server node. As a result, the metadata can't be shared with other API consumers involved in management operations. Direct migration of all CLI commands to the CommandRegistry is not feasible as well. This is not only due to the obsolescence of the Airline library, but also because it lacks the necessary abstractions to support a transparent and aligned reflection of available commands in the CLI, JMX, and REST API (represented as k8ssandra management API project) that we have, and thus such a reflection requires a more narrow approach to ensure consistency and compatibility across different management interfaces.

Therefore, the Command API might look like this:

public interface Command<A, R>
{
    public String description();

    public Class<? extends A> argClass();

    public R execute(A arg);

    /** Custom output required to preserve backwards compatibility with the nodetool output. */
    public default void printResult(A arg, R res, Consumer<String> printer) {}
}

Command Authorization

All the management commands under the new API are the same as the nodetool commands, stripped of the JMX specifics. This means that the authorization for a management operation on the node must be shifted from "granting permissions on an MBean" to "granting permissions on a command" accordingly. Regarding granularity, the proposed changes require a few code changes to authorize an operation based on the command name and input arguments rather than the MBean name, since a command may invoke multiple MBeans under the hood. 

The topics below outline the most important points.

CommandResource

A new CommandResource implementation of the IResource interface has been introduced to allow permissions to be granted on specific commands or subcommands. The CommandResource establishes a multi-level command hierarchy, similar to MBean names, formatted as ROOT/COMMAND/SUBCOMMAND/PARAMS. If arguments are omitted from PARAMS, or if some arguments are specified, this works like the '*' pattern. In addition to having EXECUTE or DESCRIBE privileges for a command to run, a user must also have the necessary privileges for any keyspaces and/or tables involved in the command operation.

Example:

PermissionCommandResource
EXECUTEnodetool/bootstrap/resume/ARGS
EXECUTEnodetool/compact/ARGS:keyspace=myks,tables=mytbl1,mytbl2
DESCRIBEnodetool/abortbootstrap

Permissions

On a related note, it seems that we could reuse the existing predefined permissions for managing commands. Just using EXECUTE and DESCRIBE should be enough to handle these operations effectively.

JMX vs CQL Authorization

When authentication and authorization are enabled, the AuthenticationProxy and AuthorizationProxy are used accordingly, with the latter enforcing role-based authorization on method invocations. Since the management commands are implemented as wrappers around MBeans, no client-side JMX authorization is required for the commands themselves (only for the MBeans). In addition, the call remains backwards compatible with the new API.

Command Registry Adapters

Once the CommandRegistry is available, some out-of-the-box adapters must be developed to achieve the goals of the proposal and make the commands available through JMX, CQL and indirectly REST API:

  1. CQL Command Adapter - The CQL invoker validates the given arguments based on the command metadata from CommandRegistry and invokes the corresponding command;
  2. Dynamic Command MBean Adapter - New dynamic JMX MBeans for management operations are generated and exposed to public API based on available command metadata for use by the nodetool. The JMX MBeans that are now statically registered are still supported. However, they are deprecated in favour of new ones;
  3. Open API Adapter - an adapter that provides dynamically generated RESTful API endpoints based on the CommandRegistry metadata as well as `openapi.json` specification; 

Commands Virtual Table

All the commands and command definitions are available by querying a corresponding new virtual table based on metadata provided by the CommandRegistry.

Management Diagram

CQL Command Syntax

The commands below become valid. While many commands in Apache Cassandra are typically run on keyspaces and tables, and adopting a CQL syntax like `EXECUTE COMMAND rebuild ON keyspace.table` seems logical, it's more practical for the initial implementation to concentrate on accepting command arguments as straightforward key-value pairs or as a JSON string. This approach simplifies the early stages of development. Subsequently, the more intuitive CQL syntax can be introduced as an alias for these commands, enhancing usability and aligning with familiar patterns.

Execute Command

Basic Syntax

EXECUTE COMMAND forcecompact 
    WITH keyspace=distributed_test_keyspace
    AND table=tbl
    AND keys=["k4", "k2", "k7"];


EXECUTE COMMAND rebuild 
	WITH keyspace=distributed_test_keyspace
    AND sourceDataCenterName=datacenter1
    AND tokens=null
    AND specificSources=null
    AND excludeLocalDatacenterNodes=true;


EXECUTE COMMAND setconcurrentcompactors 
    WITH concurrent_compactors=5;

Describe Command

DESCRIBE COMMANDS | COMMAND command_name;

Shows the output depending on the options selected:

Nodetool Compatibility

Once the CommandRegistry is in place, our goal is to streamline the process so that simply adding a command to the registry makes it available to the CLI. This leads to a decision point: we can either adopt a new CQL syntax for executing commands or continue utilizing JMX MBeans for managing node states. Opting for the new CQL syntax would necessitate reconfiguring the nodetool to communicate through a new administrative port. However, this change risks breaking backward compatibility for numerous existing deployments. We must consider various deployment complexities, such as potential firewall restrictions blocking the new port, particularly in scenarios involving remote node management. 

Given these considerations, maintaining the current functionality of the nodetool via internal JMX client emerges as a crucial aspect that we need to preserve. Thus to accomplish the goal, we need to change the following:

Minimum Viable Product (MVP)

Although the scope has been described quite broadly, the minimum viable product includes the following changes:

Risks and Assumptions

Admin Port

Cassandra (C*) relies primarily on port 9042 to listen for incoming client connections using the binary protocol. While this port can be reassigned or modified in some deployments, it may be hardcoded into certain implementations, such as node liveness check scripts, C* drivers, and other client tools. The port can also be disabled by running the 'disablebinary' command via JMX, which prevents incoming connections to a node.

These factors make the default C* port unsuitable for management operations on a node. The key assumption is that operators and users would need to adjust their deployments to perform management operations if they choose to disable JMX at the same time.

Using the same default C* port for management operations would involve plenty of trade-offs and could complicate the adjustment of drives and cli tools for users. In contrast, having a dedicated management port simplifies the initial implementation by reducing the number of corner cases that need to be addressed.

New or Changed Public Interfaces

The new public interfaces are available:

Compatibility, Deprecation, and Migration Plan

It's not expected that the proposed changes will be finalized in a single JIRA issue and pull request unless we're focusing on a minimum-viable product. It is planned to implement a deprecation policy for the statically registered MBeans over several major releases. For example, `nodetool` commands will be phased into new JMX MBeans, with each command migrated individually. Throughout this process, we'll ensure that full backward compatibility is maintained for users and the distributed tests as well.

Dynamic JMX MBeans

New dynamic MBeans are available in the public API that match the migrated `nodetool` commands and the way the same commands can be executed via CQL. These endpoints are fully compatible as the statically registered MBeans remain intact.

CQL Syntax

The proposed changes to CQL should be fully backwards compatible since it doesn’t introduce any regressions to current CQL capabilities. 

Internode Messaging

Command execution via CQL using the newly introduced syntax must be rejected until all nodes in the cluster are upgraded to the new version that supports the new syntax.

Test Plan

The nodetool commands are widely used across the distributed tests to perform operations on the node, which in turn means that running the full CI is sufficient to verify that these executions produce the same expected results. 

For the CQL commands a new custom test adapter is implemented that translates an MBean method call into the appropriate CQL syntax for the command that is being executed, so everything is tested via CQL on the same set of `nodetool` tests.

Rejected Alternatives

There are not many alternatives in this case, since the CQL is the Cassandra-specific protocol, meaning that all other possible implementations would lead to implementations that are overwhelmed by the unrelated code and would also increase the amount of change and maintenance enormously. 

Since management via CQL will be available after the changes have been implemented, the question remains whether nodetool should still use the JMX client to trigger the management operation, or whether it should be migrated to the CQL instead. The former seems to be a smoother transition in terms of usage in C*s already in use.

Tracking Progress