diff --git a/.github/workflows/docker-release.yaml b/.github/workflows/docker-release.yaml index 7a545e92..6b89f3ad 100644 --- a/.github/workflows/docker-release.yaml +++ b/.github/workflows/docker-release.yaml @@ -319,9 +319,9 @@ jobs: fail-fast: false max-parallel: 4 matrix: - cassandra-version: [4.1.0, 4.1.1, 4.1.2, 4.1.3, 4.1.4, 4.1.5] + cassandra-version: [4.1.0, 4.1.1, 4.1.2, 4.1.3, 4.1.4, 4.1.5, 4.1.6] include: - - cassandra-version: 4.1.5 + - cassandra-version: 4.1.6 latest: true runs-on: ubuntu-latest steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index aa3ae5e7..a6108ce4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Changelog for Management API, new PRs should update the `main / unreleased` sect ## unreleased * [FEATURE] [#517](https://github.com/k8ssandra/management-api-for-apache-cassandra/issues/517) Add DSE 6.9.0 to the build matrix +* [FEATURE] [#527](https://github.com/k8ssandra/management-api-for-apache-cassandra/issues/527) Add Cassandra 4.1.6 to the build matrix * [ENHANCEMENT] [#516](https://github.com/k8ssandra/management-api-for-apache-cassandra/issues/516) Address warnings in Dockerfiles * [ENHANCEMENT] [#521](https://github.com/k8ssandra/management-api-for-apache-cassandra/issues/521) Add management-api to Cassandra conf in the Dockerfile, not entrypoint for DSE 6.9, Cassandra 4.1 and Cassandra 5.0. This allows to run the container with readOnlyRootFilesystem. * [BUGFIX] [#524](https://github.com/k8ssandra/management-api-for-apache-cassandra/issues/524) Fix HintsService Hint_delays- metrics parsing and ReadCoordination metrics parsing diff --git a/README.md b/README.md index 1cddd739..3f0af6cb 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ The following versions of Cassandra and DSE are published to Docker and supporte | 3.11.12 | 4.0.4 | 4.1.3 | 6.8.29 | | | 3.11.13 | 4.0.5 | 4.1.4 | 6.8.30 | | | 3.11.14 | 4.0.6 | 4.1.5 | 6.8.31 | | -| 3.11.15 | 4.0.7 | | 6.8.32 | | +| 3.11.15 | 4.0.7 | 4.1.6 | 6.8.32 | | | 3.11.16 | 4.0.8 | | 6.8.33 | | | 3.11.17 | 4.0.9 | | 6.8.34 | | | | 4.0.10 | | 6.8.35 | | diff --git a/cassandra/Dockerfile-4.1 b/cassandra/Dockerfile-4.1 index 3441365f..ff713bff 100644 --- a/cassandra/Dockerfile-4.1 +++ b/cassandra/Dockerfile-4.1 @@ -1,4 +1,4 @@ -ARG CASSANDRA_VERSION=4.1.5 +ARG CASSANDRA_VERSION=4.1.6 FROM --platform=$BUILDPLATFORM maven:3.8.7-eclipse-temurin-11 AS builder diff --git a/cassandra/Dockerfile-4.1.ubi8 b/cassandra/Dockerfile-4.1.ubi8 index 7a150b89..db8ff71d 100644 --- a/cassandra/Dockerfile-4.1.ubi8 +++ b/cassandra/Dockerfile-4.1.ubi8 @@ -1,6 +1,6 @@ ARG UBI_MAJOR=8 ARG UBI_BASETAG=latest -ARG CASSANDRA_VERSION=4.1.5 +ARG CASSANDRA_VERSION=4.1.6 FROM registry.access.redhat.com/ubi${UBI_MAJOR}/ubi-minimal:${UBI_BASETAG} AS builder ARG URL_PREFIX=https://dlcdn.apache.org/cassandra diff --git a/management-api-agent-4.1.x/pom.xml b/management-api-agent-4.1.x/pom.xml index cdec1e08..599dc453 100644 --- a/management-api-agent-4.1.x/pom.xml +++ b/management-api-agent-4.1.x/pom.xml @@ -16,7 +16,7 @@ ${revision} datastax-mgmtapi-agent-4.1.x - 4.1.5 + 4.1.6 diff --git a/management-api-agent-4.1.x/src/main/java/com/datastax/mgmtapi/shim/RpcStatement41x.java b/management-api-agent-4.1.x/src/main/java/com/datastax/mgmtapi/shim/RpcStatement41x.java index 11418a49..5c9e6b7b 100644 --- a/management-api-agent-4.1.x/src/main/java/com/datastax/mgmtapi/shim/RpcStatement41x.java +++ b/management-api-agent-4.1.x/src/main/java/com/datastax/mgmtapi/shim/RpcStatement41x.java @@ -10,6 +10,7 @@ import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher.RequestTime; import org.apache.cassandra.transport.messages.ResultMessage; public class RpcStatement41x implements RpcStatementShim { @@ -27,11 +28,22 @@ public void authorize(ClientState clientState) {} @Override public void validate(ClientState clientState) {} - @Override + /** + * as of Cassandra 4.1.6, CASSANDRA-19534, org.apache.cassandra.cql3.CQLStatement no longer has + * the method signature below, so we need to remove the @Override annotation. + */ public ResultMessage execute(QueryState queryState, QueryOptions queryOptions, long l) { return new ResultMessage.Void(); } + /** + * as of Cassandra 4.1.6, CASSANDRA-19534, org.apache.cassandra.cql3.CQLStatement now has the + * method signature below, but it does not exist in Cassandra < 4.1.6. + */ + public ResultMessage execute(QueryState queryState, QueryOptions queryOptions, RequestTime rt) { + return new ResultMessage.Void(); + } + @Override public ResultMessage executeLocally(QueryState queryState, QueryOptions queryOptions) { return new ResultMessage.Void(); diff --git a/management-api-agent-4.1.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer41x.java b/management-api-agent-4.1.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer41x.java index 11a8a69f..bd431997 100644 --- a/management-api-agent-4.1.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer41x.java +++ b/management-api-agent-4.1.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer41x.java @@ -101,7 +101,33 @@ protected void channelRead0(ChannelHandlerContext ctx, Message.Request request) // logger.info("Executing {} {} {}", request, connection.getVersion(), // request.getStreamId()); - Message.Response r = request.execute(qstate, queryStartNanoTime); + // In Cassandra 4.1.6, CASSANDRA-19534 changed the Message.Request.execute method signature + // to take a new Dispatcher.RequestTime object instead of a primitive long for the Query + // start time. We'll need to introduce reflection here to create the correct Objects and + // make the correct calls based on which version of 4.1.x we are. + Message.Response r = null; + try { + // First see if we have the Dispatcher.RequestTime class. If so, assume we are 4.1.6+ + Class dispatcherRequestTime = + Class.forName("org.apache.cassandra.transport.Dispatcher$RequestTime"); + // we are 4.1.6+, get Dispatcher.RequestTime.forImmediateExecution() + Method forImmediateExecution = + dispatcherRequestTime.getDeclaredMethod("forImmediateExecution"); + Method requestExecute = + Message.Request.class.getDeclaredMethod( + "execute", QueryState.class, dispatcherRequestTime); + r = + (Message.Response) + requestExecute.invoke(request, qstate, forImmediateExecution.invoke(null)); + + } catch (ClassNotFoundException cfne) { + logger.debug( + "Dispatcher$RequestTime in 4.1.6+ not found, trying Request.execute from older versions"); + // we must be 4.1.5- + Method requestExecute = + Message.Request.class.getDeclaredMethod("execute", QueryState.class, long.class); + r = (Message.Response) requestExecute.invoke(request, qstate, queryStartNanoTime); + } // UnixSocket has no auth response = r instanceof AuthenticateMessage ? new ReadyMessage() : r; @@ -291,37 +317,24 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List ou // add CASSANDRA-15241 (https://issues.apache.org/jira/browse/CASSANDRA-15241). To // avoid splitting the 4.1 agent based on which version of Cassandra it runs with, // we'll use reflection here to determine the correct method to invoke. - Method processRequestMethod = null; - boolean requiresStartTime = false; + + // In Cassandra 4.1.6, Dispatcher.processRequest method signatures changed again for + // CASSANDRA-19534. The long value for start time was replaced by RequestTime + Message.Response response = null; try { - // try to get the Cassandra 4.1.3+ method - processRequestMethod = + // try to see if the 4.1.6+ object RequestTime exists + Class dispatcherRequestTime = + Class.forName("org.apache.cassandra.transport.Dispatcher$RequestTime"); + // it's 4.1.6+, get Dispatcher.RequestTime.forImmediateExecution() + Method forImmediateExecution = + dispatcherRequestTime.getDeclaredMethod("forImmediateExecution"); + Method processRequestMethod = Dispatcher.class.getDeclaredMethod( "processRequest", Channel.class, Message.Request.class, Overload.class, - long.class); - // 4.1.3 method found so we'll need to invoke it with a start time - requiresStartTime = true; - } catch (NoSuchMethodException ex) { - // 4.1.3+ method doesn't existy, try 4.1.2- method - logger.debug( - "Cassandra Dispatcher.processRequest() for 4.1.3 not found, trying 4.1.2 method"); - try { - processRequestMethod = - Dispatcher.class.getDeclaredMethod( - "processRequest", Channel.class, Message.Request.class, Overload.class); - } catch (NoSuchMethodException ex2) { - // something is broken, need to figure out what method/signature should be used - logger.error( - "Expected Cassandra Dispatcher.processRequest() method signature not found. Management API agent will not be able to start Cassandra.", - ex2); - throw ex2; - } - } - Message.Response response; - if (requiresStartTime) { + dispatcherRequestTime); response = (Message.Response) processRequestMethod.invoke( @@ -329,11 +342,48 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List ou ctx.channel(), startup, Overload.NONE, - MonotonicClock.Global.approxTime.now()); - } else { - response = - (Message.Response) - processRequestMethod.invoke(null, ctx.channel(), startup, Overload.NONE); + forImmediateExecution.invoke(null)); + + } catch (ClassNotFoundException cnfe) { + logger.debug( + "Dispatcher$RequestTime in 4.1.6+ not found, trying Dispatcher.processRequest from older versions"); + try { + // try to get the Cassandra 4.1.3+ method + Method processRequestMethod = + Dispatcher.class.getDeclaredMethod( + "processRequest", + Channel.class, + Message.Request.class, + Overload.class, + long.class); + // 4.1.3 method found so we'll need to invoke it with a start time + response = + (Message.Response) + processRequestMethod.invoke( + null, + ctx.channel(), + startup, + Overload.NONE, + MonotonicClock.Global.approxTime.now()); + } catch (NoSuchMethodException ex) { + // 4.1.3+ method doesn't existy, try 4.1.2- method + logger.debug( + "Cassandra Dispatcher.processRequest() for 4.1.3 not found, trying 4.1.2 method"); + try { + Method processRequestMethod = + Dispatcher.class.getDeclaredMethod( + "processRequest", Channel.class, Message.Request.class, Overload.class); + response = + (Message.Response) + processRequestMethod.invoke(null, ctx.channel(), startup, Overload.NONE); + } catch (NoSuchMethodException ex2) { + // something is broken, need to figure out what method/signature should be used + logger.error( + "Expected Cassandra Dispatcher.processRequest() method signature not found. Management API agent will not be able to start Cassandra.", + ex2); + throw ex2; + } + } } if (response.type.equals(Message.Type.AUTHENTICATE))