From 0608d0c32e8f7d1bb51e5d9b62e05eb767474a4f Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Thu, 10 Dec 2020 13:05:17 -0600 Subject: [PATCH] Update for CASSANDRA-15299 protocol v5 changes * Update to Cassandra-beta4 * Run Cassandra/DSE as cassandra/dse user * Remove shim projects * Refactor agent project into server-specific projects --- .github/workflows/jar-release.yaml | 2 +- Dockerfile-3_11 | 35 -- Dockerfile-4_0 | 63 +++- Dockerfile-astra-4_0 | 53 ++- Dockerfile-build | 12 +- Dockerfile-build-dse | 16 +- Dockerfile-dse-68 | 53 ++- Dockerfile-oss | 95 ++---- README.md | 8 +- .../pom.xml | 72 +--- .../CassandraAPIServiceProvider3x.java | 18 + .../mgmtapi/rpc/GenericSerializer3x.java | 12 +- .../mgmtapi/rpc/ObjectSerializer3x.java | 12 +- .../com/datastax/mgmtapi/rpc/RpcMethod3x.java | 195 +++++++++++ .../rpc/RpcMethodServiceProvider3x.java | 17 + .../datastax/mgmtapi/shim/CassandraAPI3x.java | 8 - .../cassandra/locator/K8SeedProvider3x.java | 0 .../transport/UnixSocketServer3x.java | 6 +- ...tastax.mgmtapi.CassandraAPIServiceProvider | 1 + ...astax.mgmtapi.rpc.RpcMethodServiceProvider | 1 + .../pom.xml | 96 ++++-- .../CassandraAPIServiceProvider4x.java | 18 + .../mgmtapi/rpc/GenericSerializer4x.java | 148 ++++++++ .../mgmtapi/rpc/ObjectSerializer4x.java | 137 ++++++++ .../com/datastax/mgmtapi/rpc/RpcMethod4x.java | 195 +++++++++++ .../rpc/RpcMethodServiceProvider4x.java | 17 + .../datastax/mgmtapi/shim/CassandraAPI4x.java | 7 - .../datastax/mgmtapi/shim/RpcStatement.java | 0 .../cassandra/locator/K8SeedProvider4x.java | 0 .../transport/UnixSocketServer4x.java | 320 ++++++++++++++++++ ...tastax.mgmtapi.CassandraAPIServiceProvider | 1 + ...astax.mgmtapi.rpc.RpcMethodServiceProvider | 1 + management-api-agent-common/README.md | 35 ++ .../pom.xml | 89 +++-- .../main/java/com/datastax/mgmtapi/Agent.java | 0 .../mgmtapi/CassandraAPIServiceProvider.java | 13 + .../com/datastax/mgmtapi/NodeOpsProvider.java | 0 .../java/com/datastax/mgmtapi/ShimLoader.java | 25 ++ .../CassandraDaemonInterceptor.java | 0 .../CassandraRoleManagerInterceptor.java | 0 .../interceptors/QueryHandlerInterceptor.java | 0 .../QueryHandlerInterceptor4x.java | 0 ...stemDistributedReplicationInterceptor.java | 0 .../com/datastax/mgmtapi/rpc/LazyRef.java | 0 .../mgmtapi/rpc/ObjectSerializer.java | 35 ++ .../java/com/datastax/mgmtapi/rpc/Rpc.java | 0 .../datastax/mgmtapi/rpc/RpcClientState.java | 0 .../mgmtapi/rpc/RpcExecutionException.java | 0 .../com/datastax/mgmtapi/rpc/RpcMethod.java | 22 ++ .../mgmtapi/rpc/RpcMethodServiceProvider.java | 13 + .../com/datastax/mgmtapi/rpc/RpcObject.java | 7 +- .../com/datastax/mgmtapi/rpc/RpcParam.java | 0 .../com/datastax/mgmtapi/rpc/RpcRegistry.java | 0 .../com/datastax/mgmtapi/rpc/RpcResource.java | 0 .../mgmtapi/util/ServiceProviderLoader.java | 46 +++ .../cassandra/gms/GossiperInterceptor.java | 0 .../cassandra/locator/K8SeedProvider.java | 0 .../README.md | 0 .../pom.xml | 19 +- .../CassandraAPIServiceProviderDse68.java | 18 + .../mgmtapi/rpc/GenericSerializerDse68.java | 148 ++++++++ .../mgmtapi/rpc/ObjectSerializerDse68.java | 137 ++++++++ .../datastax/mgmtapi/rpc/RpcMethodDse68.java | 16 +- .../rpc/RpcMethodServiceProviderDse68.java | 17 + .../com/datastax/mgmtapi/shim/DseAPI68.java | 8 - .../locator/K8SeedProviderDse68.java | 0 .../transport/UnixSocketServerDse68.java | 0 ...tastax.mgmtapi.CassandraAPIServiceProvider | 1 + ...astax.mgmtapi.rpc.RpcMethodServiceProvider | 1 + .../java/com/datastax/mgmtapi/ShimLoader.java | 64 ---- .../datastax/mgmtapi/shims/CassandraAPI.java | 7 - management-api-server/pom.xml | 4 +- .../datastax/mgmtapi/UnixSocketCQLAccess.java | 3 +- .../resources/KeyspaceOpsResources.java | 2 +- .../mgmtapi/resources/LifecycleResources.java | 4 +- .../mgmtapi/resources/TableOpsResources.java | 2 +- .../mgmtapi/helpers/DockerHelper.java | 144 ++++++-- .../transport/UnixSocketServer4x.java | 202 ----------- pom.xml | 14 +- scripts/docker-entrypoint.sh | 197 ++++++----- scripts/dse-6.8-docker-entrypoint.sh | 154 +++++++++ 81 files changed, 2318 insertions(+), 748 deletions(-) delete mode 100644 Dockerfile-3_11 rename {management-api-agent => management-api-agent-3.x}/pom.xml (69%) create mode 100644 management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProvider3x.java rename management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializer.java => management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializer3x.java (94%) rename management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer.java => management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer3x.java (91%) create mode 100644 management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod3x.java create mode 100644 management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProvider3x.java rename {management-api-shim-3.x => management-api-agent-3.x}/src/main/java/com/datastax/mgmtapi/shim/CassandraAPI3x.java (97%) rename {management-api-shim-3.x => management-api-agent-3.x}/src/main/java/org/apache/cassandra/locator/K8SeedProvider3x.java (100%) rename {management-api-shim-3.x => management-api-agent-3.x}/src/main/java/org/apache/cassandra/transport/UnixSocketServer3x.java (97%) create mode 100644 management-api-agent-3.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.CassandraAPIServiceProvider create mode 100644 management-api-agent-3.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.rpc.RpcMethodServiceProvider rename {management-api-shim-4.x => management-api-agent-4.x}/pom.xml (56%) create mode 100644 management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProvider4x.java create mode 100644 management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializer4x.java create mode 100644 management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer4x.java create mode 100644 management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod4x.java create mode 100644 management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProvider4x.java rename {management-api-shim-4.x => management-api-agent-4.x}/src/main/java/com/datastax/mgmtapi/shim/CassandraAPI4x.java (98%) rename {management-api-shim-4.x => management-api-agent-4.x}/src/main/java/com/datastax/mgmtapi/shim/RpcStatement.java (100%) rename {management-api-shim-4.x => management-api-agent-4.x}/src/main/java/org/apache/cassandra/locator/K8SeedProvider4x.java (100%) create mode 100644 management-api-agent-4.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer4x.java create mode 100644 management-api-agent-4.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.CassandraAPIServiceProvider create mode 100644 management-api-agent-4.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.rpc.RpcMethodServiceProvider create mode 100644 management-api-agent-common/README.md rename {management-api-shim-3.x => management-api-agent-common}/pom.xml (59%) rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/Agent.java (100%) create mode 100644 management-api-agent-common/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProvider.java rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/NodeOpsProvider.java (100%) create mode 100644 management-api-agent-common/src/main/java/com/datastax/mgmtapi/ShimLoader.java rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/interceptors/CassandraDaemonInterceptor.java (100%) rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/interceptors/CassandraRoleManagerInterceptor.java (100%) rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/interceptors/QueryHandlerInterceptor.java (100%) rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/interceptors/QueryHandlerInterceptor4x.java (100%) rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/interceptors/SystemDistributedReplicationInterceptor.java (100%) rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/rpc/LazyRef.java (100%) create mode 100644 management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer.java rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/rpc/Rpc.java (100%) rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/rpc/RpcClientState.java (100%) rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/rpc/RpcExecutionException.java (100%) create mode 100644 management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod.java create mode 100644 management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProvider.java rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/rpc/RpcObject.java (85%) rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/rpc/RpcParam.java (100%) rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/rpc/RpcRegistry.java (100%) rename {management-api-agent => management-api-agent-common}/src/main/java/com/datastax/mgmtapi/rpc/RpcResource.java (100%) create mode 100644 management-api-agent-common/src/main/java/com/datastax/mgmtapi/util/ServiceProviderLoader.java rename {management-api-agent => management-api-agent-common}/src/main/java/org/apache/cassandra/gms/GossiperInterceptor.java (100%) rename {management-api-agent => management-api-agent-common}/src/main/java/org/apache/cassandra/locator/K8SeedProvider.java (100%) rename {management-api-shim-dse-6.8 => management-api-agent-dse-6.8}/README.md (100%) rename {management-api-shim-dse-6.8 => management-api-agent-dse-6.8}/pom.xml (84%) create mode 100644 management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProviderDse68.java create mode 100644 management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializerDse68.java create mode 100644 management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializerDse68.java rename management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod.java => management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodDse68.java (92%) create mode 100644 management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProviderDse68.java rename {management-api-shim-dse-6.8 => management-api-agent-dse-6.8}/src/main/java/com/datastax/mgmtapi/shim/DseAPI68.java (98%) rename {management-api-shim-dse-6.8 => management-api-agent-dse-6.8}/src/main/java/org/apache/cassandra/locator/K8SeedProviderDse68.java (100%) rename {management-api-shim-dse-6.8 => management-api-agent-dse-6.8}/src/main/java/org/apache/cassandra/transport/UnixSocketServerDse68.java (100%) create mode 100644 management-api-agent-dse-6.8/src/main/resources/META-INF/services/com.datastax.mgmtapi.CassandraAPIServiceProvider create mode 100644 management-api-agent-dse-6.8/src/main/resources/META-INF/services/com.datastax.mgmtapi.rpc.RpcMethodServiceProvider delete mode 100644 management-api-agent/src/main/java/com/datastax/mgmtapi/ShimLoader.java delete mode 100644 management-api-shim-4.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer4x.java create mode 100755 scripts/dse-6.8-docker-entrypoint.sh diff --git a/.github/workflows/jar-release.yaml b/.github/workflows/jar-release.yaml index 509d86d2..43684e33 100644 --- a/.github/workflows/jar-release.yaml +++ b/.github/workflows/jar-release.yaml @@ -62,7 +62,7 @@ jobs: mvn -q -ff package -DskipTests -P dse - name: zip-up run: | - zip jars.zip management-api-agent/target/datastax-mgmtapi-agent-*.jar management-api-server/target/datastax-mgmtapi-server-*.jar management-api-shim-4.x/target/datastax-mgmtapi-shim-4.x-*.jar management-api-shim-dse-6.8/target/datastax-mgmtapi-shim-dse-6.8-*.jar management-api-shim-3.x/target/datastax-mgmtapi-shim-3.x-*.jar management-api-common/target/datastax-mgmtapi-common-*.jar + zip jars.zip management-api-agent-common/target/datastax-mgmtapi-agent-*.jar management-api-agent-3.x/target/datastax-mgmtapi-agent-*.jar management-api-agent-4.x/target/datastax-mgmtapi-agent-*.jar management-api-agent-dse-6.8/target/datastax-mgmtapi-agent-*.jar management-api-server/target/datastax-mgmtapi-server-*.jar management-api-common/target/datastax-mgmtapi-common-*.jar - name: Retrieve stashed release URL uses: actions/download-artifact@v1 with: diff --git a/Dockerfile-3_11 b/Dockerfile-3_11 deleted file mode 100644 index 0848b2fe..00000000 --- a/Dockerfile-3_11 +++ /dev/null @@ -1,35 +0,0 @@ -FROM management-api-for-apache-cassandra-builder as builder - -FROM cassandra:3.11.7 - -COPY --from=builder /build/management-api-common/target/datastax-mgmtapi-common-0.1.0-SNAPSHOT.jar /tmp -COPY --from=builder /build/management-api-agent/target/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar /tmp -COPY --from=builder /build/management-api-server/target/datastax-mgmtapi-server-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ -COPY --from=builder /build/management-api-shim-3.x/target/datastax-mgmtapi-shim-3.x-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ -COPY --from=builder /build/management-api-shim-4.x/target/datastax-mgmtapi-shim-4.x-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ - -ENV TINI_VERSION v0.18.0 -ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini -RUN chmod +x /tini - -RUN set -eux; \ - rm -fr /etc/apt/sources.list.d/*; \ - rm -rf /var/lib/apt/lists/*; \ - apt-get update; \ - apt-get install -y --no-install-recommends wget iproute2; \ - rm -rf /var/lib/apt/lists/* - -ENV MCAC_VERSION 0.1.7 -ADD https://github.com/datastax/metric-collector-for-apache-cassandra/releases/download/v${MCAC_VERSION}/datastax-mcac-agent-${MCAC_VERSION}.tar.gz /opt/mcac-agent.tar.gz -RUN mkdir /opt/mcac-agent && tar zxvf /opt/mcac-agent.tar.gz -C /opt/mcac-agent --strip-components 1 && rm /opt/mcac-agent.tar.gz - -# backwards compat with upstream ENTRYPOINT -COPY scripts/docker-entrypoint.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/docker-entrypoint.sh && \ - ln -sf /usr/local/bin/docker-entrypoint.sh /docker-entrypoint.sh - -EXPOSE 9103 -EXPOSE 8080 - -ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["mgmtapi"] diff --git a/Dockerfile-4_0 b/Dockerfile-4_0 index 44b0415d..479ada1d 100644 --- a/Dockerfile-4_0 +++ b/Dockerfile-4_0 @@ -1,26 +1,63 @@ -FROM management-api-for-apache-cassandra-builder as builder +ARG CASSANDRA_VERSION=4.0-beta4 -FROM datastax/cassandra:4.0 +FROM maven:3.6.3-jdk-8-slim as builder -COPY --from=builder /build/management-api-common/target/datastax-mgmtapi-common-0.1.0-SNAPSHOT.jar /tmp -COPY --from=builder /build/management-api-agent/target/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar /tmp -COPY --from=builder /build/management-api-server/target/datastax-mgmtapi-server-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ -COPY --from=builder /build/management-api-shim-3.x/target/datastax-mgmtapi-shim-3.x-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ -COPY --from=builder /build/management-api-shim-4.x/target/datastax-mgmtapi-shim-4.x-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ +ARG METRICS_COLLECTOR_VERSION=0.1.9 + +WORKDIR /build + +COPY pom.xml ./ +COPY management-api-agent-common/pom.xml ./management-api-agent-common/pom.xml +COPY management-api-agent-3.x/pom.xml ./management-api-agent-3.x/pom.xml +COPY management-api-agent-4.x/pom.xml ./management-api-agent-4.x/pom.xml +COPY management-api-common/pom.xml ./management-api-common/pom.xml +COPY management-api-server/pom.xml ./management-api-server/pom.xml +# this duplicates work done in the next steps, but this should provide +# a solid cache layer that only gets reset on pom.xml changes +RUN mvn -q -ff -T 1C install && rm -rf target + +COPY management-api-agent-common ./management-api-agent-common +COPY management-api-agent-3.x ./management-api-agent-3.x +COPY management-api-agent-4.x ./management-api-agent-4.x +COPY management-api-common ./management-api-common +COPY management-api-server ./management-api-server +RUN mvn -q -ff package -DskipTests + +# Download and extract Metrics Collector +ENV MCAC_PATH /opt/metrics-collector +RUN mkdir ${MCAC_PATH} && \ + if test ! -e datastax-mcac-agent-${METRICS_COLLECTOR_VERSION}.tar.gz; then curl -L -O "https://github.com/datastax/metric-collector-for-apache-cassandra/releases/download/v${METRICS_COLLECTOR_VERSION}/datastax-mcac-agent-${METRICS_COLLECTOR_VERSION}.tar.gz"; fi && \ + tar --directory ${MCAC_PATH} --strip-components 1 --gzip --extract --file datastax-mcac-agent-${METRICS_COLLECTOR_VERSION}.tar.gz + +FROM cassandra:${CASSANDRA_VERSION} + +ENV CASSANDRA_PATH /opt/cassandra +ENV MAAC_PATH /opt/management-api +ENV MCAC_PATH /opt/metrics-collector + +ENV CASSANDRA_HOME ${CASSANDRA_PATH} +ENV CASSANDRA_CONF ${CASSANDRA_PATH}/conf +ENV CASSANDRA_LOGS ${CASSANDRA_PATH}/logs + +COPY --from=builder /build/management-api-agent-4.x/target/datastax-mgmtapi-agent-4.x-0.1.0-SNAPSHOT.jar ${MAAC_PATH}/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar +COPY --from=builder /build/management-api-server/target/datastax-mgmtapi-server-0.1.0-SNAPSHOT.jar ${MAAC_PATH}/ +COPY --from=builder ${MCAC_PATH} ${MCAC_PATH} + +# Setup user and fixup permissions +RUN chown -R cassandra:root ${CASSANDRA_PATH} ${MAAC_PATH} ${MCAC_PATH} && \ + chmod -R g+w ${CASSANDRA_PATH} ${MAAC_PATH} ${MCAC_PATH} ENV TINI_VERSION v0.18.0 ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini RUN chmod +x /tini RUN set -eux; \ + rm -fr /etc/apt/sources.list.d/*; \ + rm -rf /var/lib/apt/lists/*; \ apt-get update; \ apt-get install -y --no-install-recommends wget iproute2; \ rm -rf /var/lib/apt/lists/* -ENV MCAC_VERSION 0.1.7 -ADD https://github.com/datastax/metric-collector-for-apache-cassandra/releases/download/v${MCAC_VERSION}/datastax-mcac-agent-${MCAC_VERSION}.tar.gz /opt/mcac-agent.tar.gz -RUN mkdir /opt/mcac-agent && tar zxvf /opt/mcac-agent.tar.gz -C /opt/mcac-agent --strip-components 1 && rm /opt/mcac-agent.tar.gz - # backwards compat with upstream ENTRYPOINT COPY scripts/docker-entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/docker-entrypoint.sh && \ @@ -29,5 +66,7 @@ RUN chmod +x /usr/local/bin/docker-entrypoint.sh && \ EXPOSE 9103 EXPOSE 8080 -ENTRYPOINT ["/docker-entrypoint.sh"] +USER cassandra + +ENTRYPOINT ["/tini", "-g", "--", "/docker-entrypoint.sh"] CMD ["mgmtapi"] diff --git a/Dockerfile-astra-4_0 b/Dockerfile-astra-4_0 index 659173c6..e0d62bbd 100644 --- a/Dockerfile-astra-4_0 +++ b/Dockerfile-astra-4_0 @@ -1,12 +1,49 @@ -FROM management-api-for-apache-cassandra-builder as builder +FROM maven:3.6.3-jdk-8-slim as builder + +ARG METRICS_COLLECTOR_VERSION=0.1.9 + +WORKDIR /build + +COPY pom.xml ./ +COPY management-api-agent-common/pom.xml ./management-api-agent-common/pom.xml +COPY management-api-agent-3.x/pom.xml ./management-api-agent-3.x/pom.xml +COPY management-api-agent-4.x/pom.xml ./management-api-agent-4.x/pom.xml +COPY management-api-common/pom.xml ./management-api-common/pom.xml +COPY management-api-server/pom.xml ./management-api-server/pom.xml +# this duplicates work done in the next steps, but this should provide +# a solid cache layer that only gets reset on pom.xml changes +RUN mvn -q -ff -T 1C install && rm -rf target + +COPY management-api-agent-common ./management-api-agent-common +COPY management-api-agent-3.x ./management-api-agent-3.x +COPY management-api-agent-4.x ./management-api-agent-4.x +COPY management-api-common ./management-api-common +COPY management-api-server ./management-api-server +RUN mvn -q -ff package -DskipTests + +# Download and extract Metrics Collector +ENV MCAC_PATH /opt/metrics-collector +RUN mkdir ${MCAC_PATH} && \ + if test ! -e datastax-mcac-agent-${METRICS_COLLECTOR_VERSION}.tar.gz; then curl -L -O "https://github.com/datastax/metric-collector-for-apache-cassandra/releases/download/v${METRICS_COLLECTOR_VERSION}/datastax-mcac-agent-${METRICS_COLLECTOR_VERSION}.tar.gz"; fi && \ + tar --directory ${MCAC_PATH} --strip-components 1 --gzip --extract --file datastax-mcac-agent-${METRICS_COLLECTOR_VERSION}.tar.gz FROM datastax/astra:4.0 -COPY --from=builder /build/management-api-common/target/datastax-mgmtapi-common-0.1.0-SNAPSHOT.jar /tmp/ -COPY --from=builder /build/management-api-agent/target/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar /tmp/ -COPY --from=builder /build/management-api-server/target/datastax-mgmtapi-server-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ -COPY --from=builder /build/management-api-shim-3.x/target/datastax-mgmtapi-shim-3.x-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ -COPY --from=builder /build/management-api-shim-4.x/target/datastax-mgmtapi-shim-4.x-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ +ENV CASSANDRA_PATH /opt/cassandra +ENV MAAC_PATH /opt/management-api +ENV MCAC_PATH /opt/metrics-collector + +ENV CASSANDRA_HOME ${CASSANDRA_PATH} +ENV CASSANDRA_CONF ${CASSANDRA_PATH}/conf +ENV CASSANDRA_LOGS ${CASSANDRA_PATH}/logs + +COPY --from=builder /build/management-api-agent-3.x/target/datastax-mgmtapi-agent-3.x-0.1.0-SNAPSHOT.jar ${MAAC_PATH}/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar +COPY --from=builder /build/management-api-server/target/datastax-mgmtapi-server-0.1.0-SNAPSHOT.jar ${MAAC_PATH}/ +COPY --from=builder ${MCAC_PATH} ${MCAC_PATH} + +# Setup user and fixup permissions +RUN chown -R cassandra:root ${CASSANDRA_PATH} ${MAAC_PATH} ${MCAC_PATH} && \ + chmod -R g+w ${CASSANDRA_PATH} ${MAAC_PATH} ${MCAC_PATH} ENV TINI_VERSION v0.18.0 ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini @@ -17,10 +54,6 @@ RUN set -eux; \ apt-get install -y --no-install-recommends wget; \ rm -rf /var/lib/apt/lists/* -ENV MCAC_VERSION 0.1.2 -ADD https://github.com/datastax/metric-collector-for-apache-cassandra/releases/download/v${MCAC_VERSION}/datastax-mcac-agent-${MCAC_VERSION}.tar.gz /opt/mcac-agent.tar.gz -RUN mkdir /opt/mcac-agent && tar zxvf /opt/mcac-agent.tar.gz -C /opt/mcac-agent --strip-components 1 && rm /opt/mcac-agent.tar.gz - # backwards compat with upstream ENTRYPOINT COPY scripts/docker-entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/docker-entrypoint.sh && \ diff --git a/Dockerfile-build b/Dockerfile-build index 1760eb40..f3290a19 100644 --- a/Dockerfile-build +++ b/Dockerfile-build @@ -3,18 +3,18 @@ FROM maven:3.6.3-jdk-8-slim WORKDIR /build COPY pom.xml ./ -COPY management-api-agent/pom.xml ./management-api-agent/pom.xml +COPY management-api-agent-common/pom.xml ./management-api-agent-common/pom.xml +COPY management-api-agent-3.x/pom.xml ./management-api-agent-3.x/pom.xml +COPY management-api-agent-4.x/pom.xml ./management-api-agent-4.x/pom.xml COPY management-api-common/pom.xml ./management-api-common/pom.xml COPY management-api-server/pom.xml ./management-api-server/pom.xml -COPY management-api-shim-3.x/pom.xml ./management-api-shim-3.x/pom.xml -COPY management-api-shim-4.x/pom.xml ./management-api-shim-4.x/pom.xml # this duplicates work done in the next steps, but this should provide # a solid cache layer that only gets reset on pom.xml changes RUN mvn -q -ff -T 1C install && rm -rf target -COPY management-api-agent ./management-api-agent +COPY management-api-agent-common ./management-api-agent-common +COPY management-api-agent-3.x ./management-api-agent-3.x +COPY management-api-agent-4.x ./management-api-agent-4.x COPY management-api-common ./management-api-common COPY management-api-server ./management-api-server -COPY management-api-shim-3.x ./management-api-shim-3.x -COPY management-api-shim-4.x ./management-api-shim-4.x RUN mvn -q -ff package -DskipTests diff --git a/Dockerfile-build-dse b/Dockerfile-build-dse index eb2e49ee..7163e61d 100644 --- a/Dockerfile-build-dse +++ b/Dockerfile-build-dse @@ -3,19 +3,19 @@ FROM maven:3.6.3-jdk-8-slim WORKDIR /build COPY pom.xml ./ -COPY management-api-agent/pom.xml ./management-api-agent/pom.xml +COPY management-api-agent-common/pom.xml ./management-api-agent-common/pom.xml +COPY management-api-agent-3.x/pom.xml ./management-api-agent-3.x/pom.xml +COPY management-api-agent-4.x/pom.xml ./management-api-agent-4.x/pom.xml +COPY management-api-agent-dse-6.8/pom.xml ./management-api-agent-dse-6.8/pom.xml COPY management-api-common/pom.xml ./management-api-common/pom.xml COPY management-api-server/pom.xml ./management-api-server/pom.xml -COPY management-api-shim-3.x/pom.xml ./management-api-shim-3.x/pom.xml -COPY management-api-shim-4.x/pom.xml ./management-api-shim-4.x/pom.xml -COPY management-api-shim-dse-6.8/pom.xml ./management-api-shim-dse-6.8/pom.xml COPY settings.xml settings.xml /root/.m2/ -COPY management-api-agent ./management-api-agent +COPY management-api-agent-common ./management-api-agent-common +COPY management-api-agent-3.x ./management-api-agent-3.x +COPY management-api-agent-4.x ./management-api-agent-4.x +COPY management-api-agent-dse-6.8 ./management-api-agent-dse-6.8 COPY management-api-common ./management-api-common COPY management-api-server ./management-api-server -COPY management-api-shim-3.x ./management-api-shim-3.x -COPY management-api-shim-4.x ./management-api-shim-4.x -COPY management-api-shim-dse-6.8 ./management-api-shim-dse-6.8 RUN mvn -ff -q clean package -DskipTests -P dse diff --git a/Dockerfile-dse-68 b/Dockerfile-dse-68 index 78ec87cd..60499728 100644 --- a/Dockerfile-dse-68 +++ b/Dockerfile-dse-68 @@ -1,34 +1,61 @@ -FROM management-api-for-dse-builder as builder +ARG CASSANDRA_VERSION=6.8.9 -FROM datastax/dse-server:6.8.0 +FROM maven:3.6.3-jdk-8-slim as builder + +ARG METRICS_COLLECTOR_VERSION=0.1.9 + +WORKDIR /build + +COPY pom.xml ./ +COPY management-api-agent-common/pom.xml ./management-api-agent-common/pom.xml +COPY management-api-agent-3.x/pom.xml ./management-api-agent-3.x/pom.xml +COPY management-api-agent-4.x/pom.xml ./management-api-agent-4.x/pom.xml +COPY management-api-agent-dse-6.8/pom.xml ./management-api-agent-dse-6.8/pom.xml +COPY management-api-common/pom.xml ./management-api-common/pom.xml +COPY management-api-server/pom.xml ./management-api-server/pom.xml +COPY settings.xml settings.xml /root/.m2/ +# this duplicates work done in the next steps, but this should provide +# a solid cache layer that only gets reset on pom.xml changes +RUN mvn -q -ff -T 1C install -Pdse && rm -rf target + +COPY management-api-agent-common ./management-api-agent-common +COPY management-api-agent-3.x ./management-api-agent-3.x +COPY management-api-agent-4.x ./management-api-agent-4.x +COPY management-api-agent-dse-6.8 ./management-api-agent-dse-6.8 +COPY management-api-common ./management-api-common +COPY management-api-server ./management-api-server +RUN mvn -q -ff package -DskipTests -Pdse + +FROM datastax/dse-server:${CASSANDRA_VERSION} # accept the License ENV DS_LICENSE=accept -COPY --from=builder /build/management-api-common/target/datastax-mgmtapi-common-0.1.0-SNAPSHOT.jar /opt/dse/ -COPY --from=builder /build/management-api-agent/target/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar /opt/dse/ -COPY --from=builder /build/management-api-server/target/datastax-mgmtapi-server-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ -COPY --from=builder /build/management-api-shim-3.x/target/datastax-mgmtapi-shim-3.x-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ -COPY --from=builder /build/management-api-shim-4.x/target/datastax-mgmtapi-shim-4.x-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ -COPY --from=builder /build/management-api-shim-dse-6.8/target/datastax-mgmtapi-shim-dse-6.8-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ +ENV CASSANDRA_PATH /opt/dse +ENV MAAC_PATH /opt/management-api -USER root -ENV TINI_VERSION v0.18.0 -ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini -RUN chmod +x /tini +ENV CASSANDRA_HOME ${CASSANDRA_PATH} +ENV CASSANDRA_CONF ${CASSANDRA_PATH}/resources/cassandra/conf +ENV CASSANDRA_LOGS ${CASSANDRA_PATH}/logs + +COPY --from=builder /build/management-api-agent-dse-6.8/target/datastax-mgmtapi-agent-dse-6.8-0.1.0-SNAPSHOT.jar ${MAAC_PATH}/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar +COPY --from=builder /build/management-api-server/target/datastax-mgmtapi-server-0.1.0-SNAPSHOT.jar ${MAAC_PATH}/ +USER root RUN set -eux; \ apt-get update; \ apt-get install -y --no-install-recommends wget iproute2; \ rm -rf /var/lib/apt/lists/* # backwards compat with upstream ENTRYPOINT -COPY dse-68/docker-entrypoint.sh /usr/local/bin/ +COPY scripts/dse-6.8-docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh RUN chmod +x /usr/local/bin/docker-entrypoint.sh && \ ln -sf /usr/local/bin/docker-entrypoint.sh /docker-entrypoint.sh EXPOSE 9103 EXPOSE 8080 +USER dse + ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["mgmtapi"] diff --git a/Dockerfile-oss b/Dockerfile-oss index 3bc1b6a4..9bf2b3ed 100644 --- a/Dockerfile-oss +++ b/Dockerfile-oss @@ -2,25 +2,33 @@ ARG CASSANDRA_VERSION=3.11.9 FROM --platform=$BUILDPLATFORM maven:3.6.3-jdk-8-slim as builder +ARG METRICS_COLLECTOR_VERSION=0.1.9 + WORKDIR /build COPY pom.xml ./ -COPY management-api-agent/pom.xml ./management-api-agent/pom.xml +COPY management-api-agent-common/pom.xml ./management-api-agent-common/pom.xml +COPY management-api-agent-3.x/pom.xml ./management-api-agent-3.x/pom.xml +COPY management-api-agent-4.x/pom.xml ./management-api-agent-4.x/pom.xml COPY management-api-common/pom.xml ./management-api-common/pom.xml COPY management-api-server/pom.xml ./management-api-server/pom.xml -COPY management-api-shim-3.x/pom.xml ./management-api-shim-3.x/pom.xml -COPY management-api-shim-4.x/pom.xml ./management-api-shim-4.x/pom.xml # this duplicates work done in the next steps, but this should provide # a solid cache layer that only gets reset on pom.xml changes RUN mvn -q -ff -T 1C install && rm -rf target -COPY management-api-agent ./management-api-agent +COPY management-api-agent-common ./management-api-agent-common +COPY management-api-agent-3.x ./management-api-agent-3.x +COPY management-api-agent-4.x ./management-api-agent-4.x COPY management-api-common ./management-api-common COPY management-api-server ./management-api-server -COPY management-api-shim-3.x ./management-api-shim-3.x -COPY management-api-shim-4.x ./management-api-shim-4.x RUN mvn -q -ff package -DskipTests +# Download and extract Metrics Collector +ENV MCAC_PATH /opt/metrics-collector +RUN mkdir ${MCAC_PATH} && \ + if test ! -e datastax-mcac-agent-${METRICS_COLLECTOR_VERSION}.tar.gz; then curl -L -O "https://github.com/datastax/metric-collector-for-apache-cassandra/releases/download/v${METRICS_COLLECTOR_VERSION}/datastax-mcac-agent-${METRICS_COLLECTOR_VERSION}.tar.gz"; fi && \ + tar --directory ${MCAC_PATH} --strip-components 1 --gzip --extract --file datastax-mcac-agent-${METRICS_COLLECTOR_VERSION}.tar.gz + FROM --platform=$BUILDPLATFORM maven:3.6.3-jdk-8-slim as netty4150 RUN mvn dependency:get -DgroupId=io.netty -DartifactId=netty-all -Dversion=4.1.50.Final -Dtransitive=false @@ -36,11 +44,20 @@ FROM oss311-${TARGETARCH} as oss311 ARG TARGETARCH -COPY --from=builder /build/management-api-common/target/datastax-mgmtapi-common-0.1.0-SNAPSHOT.jar /tmp/ -COPY --from=builder /build/management-api-agent/target/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar /tmp/ -COPY --from=builder /build/management-api-server/target/datastax-mgmtapi-server-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ -COPY --from=builder /build/management-api-shim-3.x/target/datastax-mgmtapi-shim-3.x-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ -COPY --from=builder /build/management-api-shim-4.x/target/datastax-mgmtapi-shim-4.x-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ +ENV CASSANDRA_PATH /opt/cassandra +ENV MAAC_PATH /opt/management-api +ENV MCAC_PATH /opt/metrics-collector + +ENV CASSANDRA_HOME ${CASSANDRA_PATH} +ENV CASSANDRA_CONF ${CASSANDRA_PATH}/conf + +COPY --from=builder /build/management-api-agent-3.x/target/datastax-mgmtapi-agent-3.x-0.1.0-SNAPSHOT.jar ${MAAC_PATH}/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar +COPY --from=builder /build/management-api-server/target/datastax-mgmtapi-server-0.1.0-SNAPSHOT.jar ${MAAC_PATH}/ +COPY --from=builder ${MCAC_PATH} ${MCAC_PATH} + +# Setup user and fixup permissions +RUN chown -R cassandra:root ${CASSANDRA_PATH} ${MAAC_PATH} ${MCAC_PATH} && \ + chmod -R g+w ${CASSANDRA_PATH} ${MAAC_PATH} ${MCAC_PATH} ENV TINI_VERSION v0.18.0 ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${TARGETARCH} /tini @@ -53,10 +70,6 @@ RUN set -eux; \ apt-get install -y --no-install-recommends wget iproute2; \ rm -rf /var/lib/apt/lists/* -ENV MCAC_VERSION 0.1.7 -ADD https://github.com/datastax/metric-collector-for-apache-cassandra/releases/download/v${MCAC_VERSION}/datastax-mcac-agent-${MCAC_VERSION}.tar.gz /opt/mcac-agent.tar.gz -RUN mkdir /opt/mcac-agent && tar zxvf /opt/mcac-agent.tar.gz -C /opt/mcac-agent --strip-components 1 && rm /opt/mcac-agent.tar.gz - # backwards compat with upstream ENTRYPOINT COPY scripts/docker-entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/docker-entrypoint.sh && \ @@ -65,53 +78,7 @@ RUN chmod +x /usr/local/bin/docker-entrypoint.sh && \ EXPOSE 9103 EXPOSE 8080 -ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["mgmtapi"] - - -# NOTE: Presently, OSS doesn't have official Cassandra 4.0 builds on dockerhub -# and our build at datastax/cassandra:4.0 is not a multiarch image like the -# official ones. Once one of those issues is fixed, the following targets can -# be used to build Cassandra 4.0 with Management API. - -# FROM --platform=linux/amd64 cassandra:4.0 as oss40-amd64 +USER cassandra -# FROM --platform=linux/arm64 cassandra:4.0 as oss40-arm64 -# # Netty arm64 epoll support was not added until 4.1.50 (https://github.com/netty/netty/pull/9804) -# # Only replace this dependency for arm64 to avoid regressions -# RUN rm /opt/cassandra/lib/netty-all-*.jar -# COPY --from=netty4150 /root/.m2/repository/io/netty/netty-all/4.1.50.Final/netty-all-4.1.50.Final.jar /opt/cassandra/lib/netty-all-4.1.50.Final.jar - -# FROM oss40-${TARGETARCH} as oss40 - -# ARG TARGETARCH - -# COPY --from=builder /build/management-api-common/target/datastax-mgmtapi-common-0.1.0-SNAPSHOT.jar /etc/cassandra/ -# COPY --from=builder /build/management-api-agent/target/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar /etc/cassandra/ -# COPY --from=builder /build/management-api-server/target/datastax-mgmtapi-server-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ -# COPY --from=builder /build/management-api-shim-3.x/target/datastax-mgmtapi-shim-3.x-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ -# COPY --from=builder /build/management-api-shim-4.x/target/datastax-mgmtapi-shim-4.x-0.1.0-SNAPSHOT.jar /opt/mgmtapi/ - -# ENV TINI_VERSION v0.18.0 -# ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-${TARGETARCH} /tini -# RUN chmod +x /tini - -# RUN set -eux; \ -# apt-get update; \ -# apt-get install -y --no-install-recommends wget iproute2; \ -# rm -rf /var/lib/apt/lists/* - -# ENV MCAC_VERSION 0.1.7 -# ADD https://github.com/datastax/metric-collector-for-apache-cassandra/releases/download/v${MCAC_VERSION}/datastax-mcac-agent-${MCAC_VERSION}.tar.gz /opt/mcac-agent.tar.gz -# RUN mkdir /opt/mcac-agent && tar zxvf /opt/mcac-agent.tar.gz -C /opt/mcac-agent --strip-components 1 && rm /opt/mcac-agent.tar.gz - -# # backwards compat with upstream ENTRYPOINT -# COPY scripts/docker-entrypoint.sh /usr/local/bin/ -# RUN chmod +x /usr/local/bin/docker-entrypoint.sh && \ -# ln -sf /usr/local/bin/docker-entrypoint.sh /docker-entrypoint.sh - -# EXPOSE 9103 -# EXPOSE 8080 - -# ENTRYPOINT ["/docker-entrypoint.sh"] -# CMD ["mgmtapi"] +ENTRYPOINT ["/tini", "-g", "--", "/docker-entrypoint.sh"] +CMD ["mgmtapi"] diff --git a/README.md b/README.md index 33240772..42586986 100644 --- a/README.md +++ b/README.md @@ -57,11 +57,13 @@ First you need to build the Management API base image: docker build -t management-api-for-apache-cassandra-builder -f ./Dockerfile-build . - + Then you need to build the image based on the actual Cassandra® version, either the 3.11 or 4.0: - #Create a docker image with management api and C* 3.11 - docker build -t mgmtapi-3_11 -f Dockerfile-3_11 . +**NOTE:** For building 3.11 images, you will need to have the [Docker buildx plugin](https://docs.docker.com/buildx/working-with-buildx/) installed. + + #Create a docker image with management api and C* 3.11 (version 3.11.7 and newer are supported, replace `3.11.10` with the version you want below) + docker buildx build --load --build-arg CASSANDRA_VERSION=3.11.10 --tag mgmtapi-3_11 --file Dockerfile-oss --target oss311 --platform linux/amd64 . #Create a docker image with management api and C* 4.0 docker build -t mgmtapi-4_0 -f Dockerfile-4_0 . diff --git a/management-api-agent/pom.xml b/management-api-agent-3.x/pom.xml similarity index 69% rename from management-api-agent/pom.xml rename to management-api-agent-3.x/pom.xml index b477f807..24e290b7 100644 --- a/management-api-agent/pom.xml +++ b/management-api-agent-3.x/pom.xml @@ -9,13 +9,13 @@ com.datastax - datastax-mgmtapi-agent + datastax-mgmtapi-agent-3.x 3.11.5 1.10.10 3.1.5 - 4.4.0 + 4.10.0 build_version.sh @@ -33,73 +33,7 @@ com.datastax - datastax-mgmtapi-shim-3.x - ${project.version} - - - com.datastax - datastax-mgmtapi-shim-4.x - ${project.version} - - - net.bytebuddy - byte-buddy - ${bytebuddy.version} - - - net.bytebuddy - byte-buddy-agent - ${bytebuddy.version} - - - org.apache.cassandra - cassandra-all - ${cassandra.version} - - - commons-codec - * - - - provided - - - com.datastax.oss - java-driver-query-builder - ${driver.version} - - - junit - junit - 4.11 - test - - - - - dse - - false - - - - com.datastax - datastax-mgmtapi-shim-dse-6.8 - ${project.version} - - - com.datastax - datastax-mgmtapi-common - ${project.version} - - - com.datastax - datastax-mgmtapi-shim-3.x - ${project.version} - - - com.datastax - datastax-mgmtapi-shim-4.x + datastax-mgmtapi-agent-common ${project.version} diff --git a/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProvider3x.java b/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProvider3x.java new file mode 100644 index 00000000..c86e21d5 --- /dev/null +++ b/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProvider3x.java @@ -0,0 +1,18 @@ +/** + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi; + +import com.datastax.mgmtapi.shim.CassandraAPI3x; +import com.datastax.mgmtapi.shims.CassandraAPI; + +public class CassandraAPIServiceProvider3x implements CassandraAPIServiceProvider { + + @Override + public CassandraAPI getCassandraAPI() { + return new CassandraAPI3x(); + } + +} diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializer.java b/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializer3x.java similarity index 94% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializer.java rename to management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializer3x.java index 5b45e88c..63618336 100644 --- a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializer.java +++ b/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializer3x.java @@ -31,7 +31,7 @@ * Uses reflection to look up an appropriate TypeSerializer/AbstractType to serialize objects * without writing annoying ByteBufferUtil.bytes(X/Y/Z) boilerplate. */ -public class GenericSerializer +class GenericSerializer3x { // I considered using the drivers code (CodecRegistry, TypeCodec, etc) but decided that it made more sense to // use the server side stuff from Cassandra. @@ -63,7 +63,7 @@ public class GenericSerializer put("java.util.UUID", UUIDType.instance); }}; - public static void registerType(String className, AbstractType type) + static void registerType(String className, AbstractType type) { if (typeMap.putIfAbsent(className, type) != null) { @@ -71,12 +71,12 @@ public static void registerType(String className, AbstractType type) } } - public static TypeSerializer getSerializer(Type type) + static TypeSerializer getSerializer(Type type) { return getTypeOrException(type).getSerializer(); } - public static AbstractType getTypeOrException(Type type) + static AbstractType getTypeOrException(Type type) { AbstractType ctype = getType(type); @@ -88,7 +88,7 @@ public static AbstractType getTypeOrException(Type type) return ctype; } - public static boolean simpleType(Type type) + static boolean simpleType(Type type) { return getType(type) != null; } @@ -99,7 +99,7 @@ public static boolean simpleType(Type type) * Method.getGenericParameterTypes() or something similar. Also, we currently punt on the frozen keyword. * @return The C* abstract type corresponding to the Java type, or null if not found/impossible. */ - public static AbstractType getType(Type type) + static AbstractType getType(Type type) { assert type != null; String strType = type.getTypeName(); diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer.java b/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer3x.java similarity index 91% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer.java rename to management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer3x.java index b1685aef..10bf6206 100644 --- a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer.java +++ b/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer3x.java @@ -24,7 +24,7 @@ import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.TupleType; -public class ObjectSerializer +public class ObjectSerializer3x implements ObjectSerializer { public final ImmutableSortedMap serializers; @@ -71,17 +71,17 @@ ByteBuffer serializeField(T obj) * Also, this will only serialize **PUBLIC** fields (perhaps this should be changed; it's not totally clear). * Tag accordingly. */ - public ObjectSerializer(Class clazz, Type genericType) + public ObjectSerializer3x(Class clazz, Type genericType) { - serializers = GenericSerializer.simpleType(genericType) ? - ImmutableSortedMap.of("result", new FieldSerializer(GenericSerializer.getType(genericType), x -> x)) : + serializers = GenericSerializer3x.simpleType(genericType) ? + ImmutableSortedMap.of("result", new FieldSerializer(GenericSerializer3x.getType(genericType), x -> x)) : ImmutableSortedMap.copyOf(Arrays.stream(clazz.getFields()) .collect(Collectors.toMap(field -> field.getName(), - field -> new FieldSerializer(GenericSerializer.getType(field.getType()), field)))); + field -> new FieldSerializer(GenericSerializer3x.getType(field.getType()), field)))); // currently not recursive; multiple ways to do it } - public ObjectSerializer(Class clazz) + public ObjectSerializer3x(Class clazz) { this(clazz, clazz); } diff --git a/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod3x.java b/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod3x.java new file mode 100644 index 00000000..7366599a --- /dev/null +++ b/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod3x.java @@ -0,0 +1,195 @@ +/** + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.rpc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.OptionalInt; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import com.google.common.base.Preconditions; +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.ResultSet; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.exceptions.RequestExecutionException; +import org.apache.cassandra.serializers.TypeSerializer; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.transport.messages.ResultMessage; + + +public class RpcMethod3x implements RpcMethod +{ + private static final Logger logger = LoggerFactory.getLogger(RpcMethod3x.class); + private final Method method; + private final RpcObject rpcObject; + private final String name; + private final List argSerializers; + private final List argTypes; + private final List argNames; + private final ObjectSerializer3x retSerializer; + private final OptionalInt clientStateArgIdx; + private final List> params; + + RpcMethod3x(Method method, RpcObject rpcObject) + { + this.method = method; + this.rpcObject = rpcObject; + this.name = method.getAnnotation(Rpc.class).name(); + + Annotation[][] allAnnotations = method.getParameterAnnotations(); + params = IntStream.range(0, method.getParameterCount()).boxed() + .flatMap(argIdx -> Arrays.stream(allAnnotations[argIdx]) + .filter(a -> a instanceof RpcParam) + .findFirst() + .map(RpcParam.class::cast) + .map(rpcParam -> Stream.of(Pair.of(argIdx, rpcParam))) + .orElseGet(Stream::empty)) + .collect(Collectors.toList()); + + Class[] paramTypes = method.getParameterTypes(); + clientStateArgIdx = IntStream.range(0, method.getParameterCount()) + .filter(argIdx -> paramTypes[argIdx] == RpcClientState.class) + .findFirst(); + + int expectedParamsCount = params.size() + (clientStateArgIdx.isPresent() ? 1 : 0); + if (method.getParameterCount() != expectedParamsCount) + { + throw new AssertionError(String.format( + "All arguments for %s.%s must be annotated with either RpcParam or RpcClientState", + rpcObject.getName(), + name)); + } + + Type[] genericParamTypes = method.getGenericParameterTypes(); + this.argSerializers = params.stream() + .map(p -> GenericSerializer3x.getSerializer(genericParamTypes[p.getKey()])) + .collect(Collectors.toList()); + + this.argTypes = params.stream() + .map(p -> GenericSerializer3x.getTypeOrException(genericParamTypes[p.getKey()])) + .collect(Collectors.toList()); + + this.argNames = params.stream() + .map(p -> p.getValue().name()) + .collect(Collectors.toList()); + + if (method.getAnnotation(Rpc.class).multiRow()) + { + Preconditions.checkArgument(Collection.class.isAssignableFrom(method.getReturnType()), + "If mutli-row result set is requested, the method return type must be an implementation of java.util.Collection"); + Type elemType = ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0]; + Preconditions.checkArgument(elemType instanceof Class, + "If multi-row result set is request, the element type must be a Class"); + this.retSerializer = new ObjectSerializer3x<>((Class) elemType); + } + else + { + this.retSerializer = new ObjectSerializer3x<>(method.getReturnType(), method.getGenericReturnType()); + } + } + + public String getName() + { + return name; + } + + public int getArgumentCount() + { + return argTypes.size(); + } + + public ColumnSpecification getArgumentSpecification(int position) + { + return new ColumnSpecification("system", rpcObject.getName()+"."+name, new ColumnIdentifier(argNames.get(position), false), argTypes.get(position)); + } + + public ResultMessage execute(ClientState clientState, List parameters) + throws RequestExecutionException + { + try + { + RpcClientState rpcClientState = RpcClientState.fromClientState(clientState); + LazyRef rpcArgs = LazyRef.of(() -> getMethodArgs(rpcClientState, parameters)); + + // endpoint is not explicitly provided or points to this node -> execute locally + return toResultMessage(method.invoke(rpcObject.raw, rpcArgs.get())); + } + catch (Exception e) + { + throw createRpcExecutionException(e); + } + } + + private RpcExecutionException createRpcExecutionException(Throwable e) + { + String msg = String.format("Failed to execute method %s.%s", rpcObject.getName(), name); + logger.info(msg, e); + return RpcExecutionException.create(msg, e); + } + + private Object[] getMethodArgs(RpcClientState rpcClientState, Collection parameters) + { + Object[] args = new Object[method.getParameterCount()]; + clientStateArgIdx.ifPresent(idx -> args[idx] = rpcClientState); + Object[] rpcParams = deserializeParameters(parameters); + for (int i = 0; i < rpcParams.length; i++) + { + args[params.get(i).getKey()] = rpcParams[i]; + } + return args; + } + + public ResultSet toResultSet(Object object) + { + if (method.getAnnotation(Rpc.class).multiRow()) + { + return retSerializer.toMultiRowResultSet((Collection) object, rpcObject.getName(), name); + } + else + { + return retSerializer.toResultSet(object, rpcObject.getName(), name); + } + } + + public ResultMessage toResultMessage(Object object) + { + if (object == null) + { + return new ResultMessage.Void(); + } + else + { + return new ResultMessage.Rows(toResultSet(object)); + } + } + + private Object[] deserializeParameters(Collection args) + { + Object[] deserialized = new Object[args.size()]; + + int i = 0; + for (ByteBuffer arg : args) + { + deserialized[i] = arg != null ? argSerializers.get(i).deserialize(arg) : null; + i++; + } + + return deserialized; + } +} diff --git a/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProvider3x.java b/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProvider3x.java new file mode 100644 index 00000000..49fe0126 --- /dev/null +++ b/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProvider3x.java @@ -0,0 +1,17 @@ +/** + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.rpc; + +import java.lang.reflect.Method; + +public class RpcMethodServiceProvider3x implements RpcMethodServiceProvider { + + @Override + public RpcMethod getRpcMethod(Method method, RpcObject rpcObject) { + return new RpcMethod3x(method, rpcObject); + } + +} diff --git a/management-api-shim-3.x/src/main/java/com/datastax/mgmtapi/shim/CassandraAPI3x.java b/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/shim/CassandraAPI3x.java similarity index 97% rename from management-api-shim-3.x/src/main/java/com/datastax/mgmtapi/shim/CassandraAPI3x.java rename to management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/shim/CassandraAPI3x.java index 93b83ad0..6e5b1c9c 100644 --- a/management-api-shim-3.x/src/main/java/com/datastax/mgmtapi/shim/CassandraAPI3x.java +++ b/management-api-agent-3.x/src/main/java/com/datastax/mgmtapi/shim/CassandraAPI3x.java @@ -30,9 +30,7 @@ import io.netty.channel.ChannelInitializer; import org.apache.cassandra.auth.IRoleManager; import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.cql3.CQLStatement; import org.apache.cassandra.db.ConsistencyLevel; -import org.apache.cassandra.db.HintedHandOffManager; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.compaction.CompactionManager; import org.apache.cassandra.dht.IPartitioner; @@ -277,12 +275,6 @@ public IRoleManager getRoleManager() return DatabaseDescriptor.getRoleManager(); } - @Override - public HintedHandOffManager getHintedHandoffManager() - { - return HintedHandOffManager.instance; - } - @Override public CompactionManager getCompactionManager() { diff --git a/management-api-shim-3.x/src/main/java/org/apache/cassandra/locator/K8SeedProvider3x.java b/management-api-agent-3.x/src/main/java/org/apache/cassandra/locator/K8SeedProvider3x.java similarity index 100% rename from management-api-shim-3.x/src/main/java/org/apache/cassandra/locator/K8SeedProvider3x.java rename to management-api-agent-3.x/src/main/java/org/apache/cassandra/locator/K8SeedProvider3x.java diff --git a/management-api-shim-3.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer3x.java b/management-api-agent-3.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer3x.java similarity index 97% rename from management-api-shim-3.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer3x.java rename to management-api-agent-3.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer3x.java index bb9bf82b..567b8320 100644 --- a/management-api-shim-3.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer3x.java +++ b/management-api-agent-3.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer3x.java @@ -171,9 +171,9 @@ public void applyStateTransition(Message.Type requestType, Message.Type response case UNINITIALIZED: if (requestType == Message.Type.STARTUP) { - //if (responseType == Message.Type.AUTHENTICATE) - // state = State.AUTHENTICATION; - //else if (responseType == Message.Type.READY) + if (responseType == Message.Type.AUTHENTICATE) + state = State.AUTHENTICATION; + else if (responseType == Message.Type.READY) state = State.READY; } break; diff --git a/management-api-agent-3.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.CassandraAPIServiceProvider b/management-api-agent-3.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.CassandraAPIServiceProvider new file mode 100644 index 00000000..c13ebafa --- /dev/null +++ b/management-api-agent-3.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.CassandraAPIServiceProvider @@ -0,0 +1 @@ +com.datastax.mgmtapi.CassandraAPIServiceProvider3x diff --git a/management-api-agent-3.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.rpc.RpcMethodServiceProvider b/management-api-agent-3.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.rpc.RpcMethodServiceProvider new file mode 100644 index 00000000..176b677a --- /dev/null +++ b/management-api-agent-3.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.rpc.RpcMethodServiceProvider @@ -0,0 +1 @@ +com.datastax.mgmtapi.rpc.RpcMethodServiceProvider3x diff --git a/management-api-shim-4.x/pom.xml b/management-api-agent-4.x/pom.xml similarity index 56% rename from management-api-shim-4.x/pom.xml rename to management-api-agent-4.x/pom.xml index 11fef375..f80d9341 100644 --- a/management-api-shim-4.x/pom.xml +++ b/management-api-agent-4.x/pom.xml @@ -9,41 +9,69 @@ com.datastax - datastax-mgmtapi-shim-4.x + datastax-mgmtapi-agent-4.x - 4.0-beta1 - 1.9.15 + 4.0-beta4 + 1.10.10 3.1.5 - 4.4.0 + 4.10.0 build_version.sh - - - com.datastax - datastax-mgmtapi-common - ${project.version} - - - org.apache.cassandra - cassandra-all - ${cassandra.version} - - - commons-codec - * - - - provided - - - junit - junit - 4.11 - test - - + + + default + + true + + + + com.datastax + datastax-mgmtapi-common + ${project.version} + + + com.datastax + datastax-mgmtapi-agent-common + ${project.version} + + + net.bytebuddy + byte-buddy + ${bytebuddy.version} + + + net.bytebuddy + byte-buddy-agent + ${bytebuddy.version} + + + org.apache.cassandra + cassandra-all + ${cassandra.version} + + + commons-codec + * + + + provided + + + com.datastax.oss + java-driver-query-builder + ${driver.version} + + + junit + junit + 4.11 + test + + + + @@ -128,6 +156,16 @@ shade + + + + + + com.datastax.mgmtapi.Agent + + + + diff --git a/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProvider4x.java b/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProvider4x.java new file mode 100644 index 00000000..feacc8cd --- /dev/null +++ b/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProvider4x.java @@ -0,0 +1,18 @@ +/** + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi; + +import com.datastax.mgmtapi.shim.CassandraAPI4x; +import com.datastax.mgmtapi.shims.CassandraAPI; + +public class CassandraAPIServiceProvider4x implements CassandraAPIServiceProvider { + + @Override + public CassandraAPI getCassandraAPI() { + return new CassandraAPI4x(); + } + +} diff --git a/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializer4x.java b/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializer4x.java new file mode 100644 index 00000000..d83e4e09 --- /dev/null +++ b/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializer4x.java @@ -0,0 +1,148 @@ +/** + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.rpc; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.BooleanType; +import org.apache.cassandra.db.marshal.ByteType; +import org.apache.cassandra.db.marshal.BytesType; +import org.apache.cassandra.db.marshal.DateType; +import org.apache.cassandra.db.marshal.DoubleType; +import org.apache.cassandra.db.marshal.EmptyType; +import org.apache.cassandra.db.marshal.FloatType; +import org.apache.cassandra.db.marshal.InetAddressType; +import org.apache.cassandra.db.marshal.Int32Type; +import org.apache.cassandra.db.marshal.ListType; +import org.apache.cassandra.db.marshal.LongType; +import org.apache.cassandra.db.marshal.MapType; +import org.apache.cassandra.db.marshal.SetType; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.db.marshal.UUIDType; +import org.apache.cassandra.serializers.TypeSerializer; + +/** + * Uses reflection to look up an appropriate TypeSerializer/AbstractType to serialize objects + * without writing annoying ByteBufferUtil.bytes(X/Y/Z) boilerplate. + */ +class GenericSerializer4x +{ + // I considered using the drivers code (CodecRegistry, TypeCodec, etc) but decided that it made more sense to + // use the server side stuff from Cassandra. + + // extending this (at least for the purpose of RPC calls) is relatively straightforward: write a class that + // extends C*'s TypeSerializer and add to the map. For actually getting data into C*'s UDTs it might be a bit + // trickier. Unfortunately, there is not always a direct 1:1 mapping between Java and Cassandra types. A simple + // example is millisecond timestamps, which could be serialized as 'long' and 'timestamp'. The driver code + // actually has some bounds for this, but I think for us it will be simpler to just write more serializers and + // add them to the map. + private static final ConcurrentHashMap typeMap = new ConcurrentHashMap() {{ + put("void", EmptyType.instance); + put("boolean", BooleanType.instance); + put("java.lang.Boolean", BooleanType.instance); + put("byte", ByteType.instance); + put("java.lang.Byte", ByteType.instance); + put("int", Int32Type.instance); + put("java.lang.Integer", Int32Type.instance); + put("long", LongType.instance); + put("java.lang.Long", LongType.instance); + put("float", FloatType.instance); + put("java.lang.Float", FloatType.instance); + put("double", DoubleType.instance); + put("java.lang.Double", DoubleType.instance); + put("java.lang.String", UTF8Type.instance); + put("java.net.InetAddress", InetAddressType.instance); + put("java.util.Date", DateType.instance); + put("java.nio.ByteBuffer", BytesType.instance); + put("java.util.UUID", UUIDType.instance); + }}; + + static void registerType(String className, AbstractType type) + { + if (typeMap.putIfAbsent(className, type) != null) + { + throw new IllegalStateException("The type " + className + " is already registered."); + } + } + + static TypeSerializer getSerializer(Type type) + { + return getTypeOrException(type).getSerializer(); + } + + static AbstractType getTypeOrException(Type type) + { + AbstractType ctype = getType(type); + + if (ctype == null) + { + throw new AssertionError(String.format("Add type '%s' to GenericSerializer", type.getTypeName())); + } + + return ctype; + } + + static boolean simpleType(Type type) + { + return getType(type) != null; + } + + /** + * Most of the actual work is done here. Note that Generic type information is mostly destroyed at runtime + * (a list is just a list). For the Parameterized types to work correctly you have to call + * Method.getGenericParameterTypes() or something similar. Also, we currently punt on the frozen keyword. + * @return The C* abstract type corresponding to the Java type, or null if not found/impossible. + */ + static AbstractType getType(Type type) + { + assert type != null; + String strType = type.getTypeName(); + + // Rather than hard coding List List List etc we create them as needed. Also there + // is no need for a lock as the actual serializers do that for us. + if (!typeMap.containsKey(strType)) + { + if (type instanceof ParameterizedType) + { + ParameterizedType ptype = (ParameterizedType) type; + + if (ptype.getRawType().getTypeName().equals("java.util.List")) + { + assert ptype.getActualTypeArguments().length == 1; + typeMap.putIfAbsent(strType, + ListType.getInstance(getType(ptype.getActualTypeArguments()[0]), false)); + } + else if (ptype.getRawType().getTypeName().equals("java.util.Set")) + { + assert ptype.getActualTypeArguments().length == 1; + typeMap.putIfAbsent(strType, + SetType.getInstance(getType(ptype.getActualTypeArguments()[0]), false)); + } + else if (ptype.getRawType().getTypeName().equals("java.util.Map")) + { + assert ptype.getActualTypeArguments().length == 2; + typeMap.putIfAbsent(strType, + MapType.getInstance(getType(ptype.getActualTypeArguments()[0]), + getType(ptype.getActualTypeArguments()[1]), false)); + } + else + { + throw new AssertionError("Don't know how to serialize generic type '" + + ptype.getRawType().getTypeName() + "'"); + } + } + else + { + return null; + } + } + + return typeMap.get(strType); + } +} diff --git a/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer4x.java b/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer4x.java new file mode 100644 index 00000000..2da1f232 --- /dev/null +++ b/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer4x.java @@ -0,0 +1,137 @@ +/** + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.rpc; + +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.Lists; + +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.ResultSet; +import org.apache.cassandra.cql3.ResultSet.ResultMetadata; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.TupleType; + +public class ObjectSerializer4x implements ObjectSerializer +{ + public final ImmutableSortedMap serializers; + + public class FieldSerializer + { + public final AbstractType type; + public final Function accessor; + + FieldSerializer(AbstractType type, Function accessor) + { + this.type = type; + this.accessor = accessor; + } + + FieldSerializer(AbstractType type, final Field field) + { + field.setAccessible(true); + this.type = type; + this.accessor = (obj) -> { + try + { + return field.get(obj); + } + catch (IllegalAccessException e) + { + throw new AssertionError("Should not happen as we set the field to accessible."); + } + }; + } + + ByteBuffer serializeField(T obj) + { + Object value = accessor.apply(obj); + if (value == null) + { + return null; + } + return type.getSerializer().serialize(accessor.apply(obj)); + } + } + + /** + * Due to the magic of java generics, the class doesn't have the full generic information, hence the double types. + * Also, this will only serialize **PUBLIC** fields (perhaps this should be changed; it's not totally clear). + * Tag accordingly. + */ + public ObjectSerializer4x(Class clazz, Type genericType) + { + serializers = GenericSerializer4x.simpleType(genericType) ? + ImmutableSortedMap.of("result", new FieldSerializer(GenericSerializer4x.getType(genericType), x -> x)) : + ImmutableSortedMap.copyOf(Arrays.stream(clazz.getFields()) + .collect(Collectors.toMap(field -> field.getName(), + field -> new FieldSerializer(GenericSerializer4x.getType(field.getType()), field)))); + // currently not recursive; multiple ways to do it + } + + public ObjectSerializer4x(Class clazz) + { + this(clazz, clazz); + } + + /** + * Serialize an object into a C* ResultSet, with each field as a named value. + * @param obj The object to serialize + * @param ksName Pretend we are coming from this keyspace + * @param cfName Pretend we are coming from this columnfamily + */ + public ResultSet toResultSet(T obj, String ksName, String cfName) + { + return new ResultSet( + new ResultMetadata(serializers.entrySet().stream() + .map(e -> new ColumnSpecification(ksName, cfName, + new ColumnIdentifier(e.getKey(), true), + e.getValue().type)) + .collect(Collectors.toList())), + Lists.>newArrayList(toByteBufferList(obj))); + } + + /** + * Serialize an object into a C* multi-row ResultSet, with each field as a named value. + * + * @param obj The object to serialize + * @param ksName Pretend we are coming from this keyspace + * @param cfName Pretend we are coming from this columnfamily + */ + public ResultSet toMultiRowResultSet(Collection obj, String ksName, String cfName) + { + return new ResultSet( + new ResultMetadata(serializers.entrySet().stream() + .map(e -> new ColumnSpecification(ksName, cfName, + new ColumnIdentifier(e.getKey(), true), + e.getValue().type)) + .collect(Collectors.toList())), + obj.stream().map(this::toByteBufferList).collect(Collectors.toList())); + } + + public List toByteBufferList(T obj) + { + return serializers.values().stream() + .map(fs -> fs.serializeField(obj)) + .collect(Collectors.toList()); + } + + public ByteBuffer toByteBuffer(T obj) + { + return TupleType.buildValue(serializers.values().stream() + .map(fs -> fs.serializeField(obj)) + .toArray(ByteBuffer[]::new)); + } +} diff --git a/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod4x.java b/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod4x.java new file mode 100644 index 00000000..e32ab1ec --- /dev/null +++ b/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod4x.java @@ -0,0 +1,195 @@ +/** + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.rpc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.OptionalInt; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import com.google.common.base.Preconditions; +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.ResultSet; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.exceptions.RequestExecutionException; +import org.apache.cassandra.serializers.TypeSerializer; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.transport.messages.ResultMessage; + + +public class RpcMethod4x implements RpcMethod +{ + private static final Logger logger = LoggerFactory.getLogger(RpcMethod4x.class); + private final Method method; + private final RpcObject rpcObject; + private final String name; + private final List argSerializers; + private final List argTypes; + private final List argNames; + private final ObjectSerializer4x retSerializer; + private final OptionalInt clientStateArgIdx; + private final List> params; + + RpcMethod4x(Method method, RpcObject rpcObject) + { + this.method = method; + this.rpcObject = rpcObject; + this.name = method.getAnnotation(Rpc.class).name(); + + Annotation[][] allAnnotations = method.getParameterAnnotations(); + params = IntStream.range(0, method.getParameterCount()).boxed() + .flatMap(argIdx -> Arrays.stream(allAnnotations[argIdx]) + .filter(a -> a instanceof RpcParam) + .findFirst() + .map(RpcParam.class::cast) + .map(rpcParam -> Stream.of(Pair.of(argIdx, rpcParam))) + .orElseGet(Stream::empty)) + .collect(Collectors.toList()); + + Class[] paramTypes = method.getParameterTypes(); + clientStateArgIdx = IntStream.range(0, method.getParameterCount()) + .filter(argIdx -> paramTypes[argIdx] == RpcClientState.class) + .findFirst(); + + int expectedParamsCount = params.size() + (clientStateArgIdx.isPresent() ? 1 : 0); + if (method.getParameterCount() != expectedParamsCount) + { + throw new AssertionError(String.format( + "All arguments for %s.%s must be annotated with either RpcParam or RpcClientState", + rpcObject.getName(), + name)); + } + + Type[] genericParamTypes = method.getGenericParameterTypes(); + this.argSerializers = params.stream() + .map(p -> GenericSerializer4x.getSerializer(genericParamTypes[p.getKey()])) + .collect(Collectors.toList()); + + this.argTypes = params.stream() + .map(p -> GenericSerializer4x.getTypeOrException(genericParamTypes[p.getKey()])) + .collect(Collectors.toList()); + + this.argNames = params.stream() + .map(p -> p.getValue().name()) + .collect(Collectors.toList()); + + if (method.getAnnotation(Rpc.class).multiRow()) + { + Preconditions.checkArgument(Collection.class.isAssignableFrom(method.getReturnType()), + "If mutli-row result set is requested, the method return type must be an implementation of java.util.Collection"); + Type elemType = ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0]; + Preconditions.checkArgument(elemType instanceof Class, + "If multi-row result set is request, the element type must be a Class"); + this.retSerializer = new ObjectSerializer4x<>((Class) elemType); + } + else + { + this.retSerializer = new ObjectSerializer4x<>(method.getReturnType(), method.getGenericReturnType()); + } + } + + public String getName() + { + return name; + } + + public int getArgumentCount() + { + return argTypes.size(); + } + + public ColumnSpecification getArgumentSpecification(int position) + { + return new ColumnSpecification("system", rpcObject.getName()+"."+name, new ColumnIdentifier(argNames.get(position), false), argTypes.get(position)); + } + + public ResultMessage execute(ClientState clientState, List parameters) + throws RequestExecutionException + { + try + { + RpcClientState rpcClientState = RpcClientState.fromClientState(clientState); + LazyRef rpcArgs = LazyRef.of(() -> getMethodArgs(rpcClientState, parameters)); + + // endpoint is not explicitly provided or points to this node -> execute locally + return toResultMessage(method.invoke(rpcObject.raw, rpcArgs.get())); + } + catch (Exception e) + { + throw createRpcExecutionException(e); + } + } + + private RpcExecutionException createRpcExecutionException(Throwable e) + { + String msg = String.format("Failed to execute method %s.%s", rpcObject.getName(), name); + logger.info(msg, e); + return RpcExecutionException.create(msg, e); + } + + private Object[] getMethodArgs(RpcClientState rpcClientState, Collection parameters) + { + Object[] args = new Object[method.getParameterCount()]; + clientStateArgIdx.ifPresent(idx -> args[idx] = rpcClientState); + Object[] rpcParams = deserializeParameters(parameters); + for (int i = 0; i < rpcParams.length; i++) + { + args[params.get(i).getKey()] = rpcParams[i]; + } + return args; + } + + public ResultSet toResultSet(Object object) + { + if (method.getAnnotation(Rpc.class).multiRow()) + { + return retSerializer.toMultiRowResultSet((Collection) object, rpcObject.getName(), name); + } + else + { + return retSerializer.toResultSet(object, rpcObject.getName(), name); + } + } + + public ResultMessage toResultMessage(Object object) + { + if (object == null) + { + return new ResultMessage.Void(); + } + else + { + return new ResultMessage.Rows(toResultSet(object)); + } + } + + private Object[] deserializeParameters(Collection args) + { + Object[] deserialized = new Object[args.size()]; + + int i = 0; + for (ByteBuffer arg : args) + { + deserialized[i] = arg != null ? argSerializers.get(i).deserialize(arg) : null; + i++; + } + + return deserialized; + } +} diff --git a/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProvider4x.java b/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProvider4x.java new file mode 100644 index 00000000..0920088d --- /dev/null +++ b/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProvider4x.java @@ -0,0 +1,17 @@ +/** + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.rpc; + +import java.lang.reflect.Method; + +public class RpcMethodServiceProvider4x implements RpcMethodServiceProvider { + + @Override + public RpcMethod getRpcMethod(Method method, RpcObject rpcObject) { + return new RpcMethod4x(method, rpcObject); + } + +} diff --git a/management-api-shim-4.x/src/main/java/com/datastax/mgmtapi/shim/CassandraAPI4x.java b/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/shim/CassandraAPI4x.java similarity index 98% rename from management-api-shim-4.x/src/main/java/com/datastax/mgmtapi/shim/CassandraAPI4x.java rename to management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/shim/CassandraAPI4x.java index 7a23589a..2b69b33f 100644 --- a/management-api-shim-4.x/src/main/java/com/datastax/mgmtapi/shim/CassandraAPI4x.java +++ b/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/shim/CassandraAPI4x.java @@ -32,7 +32,6 @@ import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLStatement; import org.apache.cassandra.db.ConsistencyLevel; -import org.apache.cassandra.db.HintedHandOffManager; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.compaction.CompactionManager; import org.apache.cassandra.dht.IPartitioner; @@ -281,12 +280,6 @@ public IRoleManager getRoleManager() return DatabaseDescriptor.getRoleManager(); } - @Override - public HintedHandOffManager getHintedHandoffManager() - { - return HintedHandOffManager.instance; - } - @Override public CompactionManager getCompactionManager() { diff --git a/management-api-shim-4.x/src/main/java/com/datastax/mgmtapi/shim/RpcStatement.java b/management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/shim/RpcStatement.java similarity index 100% rename from management-api-shim-4.x/src/main/java/com/datastax/mgmtapi/shim/RpcStatement.java rename to management-api-agent-4.x/src/main/java/com/datastax/mgmtapi/shim/RpcStatement.java diff --git a/management-api-shim-4.x/src/main/java/org/apache/cassandra/locator/K8SeedProvider4x.java b/management-api-agent-4.x/src/main/java/org/apache/cassandra/locator/K8SeedProvider4x.java similarity index 100% rename from management-api-shim-4.x/src/main/java/org/apache/cassandra/locator/K8SeedProvider4x.java rename to management-api-agent-4.x/src/main/java/org/apache/cassandra/locator/K8SeedProvider4x.java diff --git a/management-api-agent-4.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer4x.java b/management-api-agent-4.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer4x.java new file mode 100644 index 00000000..0812c737 --- /dev/null +++ b/management-api-agent-4.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer4x.java @@ -0,0 +1,320 @@ +/** + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package org.apache.cassandra.transport; + +import com.datastax.mgmtapi.ipc.IPCController; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelPromise; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.VoidChannelPromise; +import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.util.Attribute; +import org.apache.cassandra.auth.IAuthenticator; +import org.apache.cassandra.cql3.QueryProcessor; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.ClientWarn; +import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.messages.AuthenticateMessage; +import org.apache.cassandra.transport.messages.ErrorMessage; +import org.apache.cassandra.transport.messages.ReadyMessage; +import org.apache.cassandra.transport.messages.StartupMessage; +import org.apache.cassandra.transport.messages.SupportedMessage; +import org.apache.cassandra.utils.JVMStabilityInspector; + +public class UnixSocketServer4x +{ + private static final Logger logger = LoggerFactory.getLogger(IPCController.class); + + // Names of handlers used in pre-V5 pipelines + private static final String ENVELOPE_DECODER = "envelopeDecoder"; + private static final String ENVELOPE_ENCODER = "envelopeEncoder"; + private static final String MESSAGE_DECOMPRESSOR = "decompressor"; + private static final String MESSAGE_COMPRESSOR = "compressor"; + private static final String MESSAGE_DECODER = "messageDecoder"; + private static final String MESSAGE_ENCODER = "messageEncoder"; + private static final String LEGACY_MESSAGE_PROCESSOR = "legacyCqlProcessor"; + private static final String INITIAL_HANDLER = "initialHandler"; + private static final String EXCEPTION_HANDLER = "exceptionHandler"; + + public static ChannelInitializer makeSocketInitializer(final Server.ConnectionTracker connectionTracker) + { + logger.debug("Creating Channel Initializer"); + return new ChannelInitializer() + { + @Override + protected void initChannel(Channel channel) throws Exception + { + ChannelPipeline pipeline = channel.pipeline(); + + pipeline.addLast(ENVELOPE_ENCODER, Envelope.Encoder.instance); + pipeline.addLast(INITIAL_HANDLER, + new PipelineChannelInitializer( + new Envelope.Decoder(), + (channel1, version) -> + new UnixSocketConnection( + channel1, + version, + connectionTracker))); + // The exceptionHandler will take care of handling exceptionCaught(...) events while still running + // on the same EventLoop as all previous added handlers in the pipeline. This is important as the used + // eventExecutorGroup may not enforce strict ordering for channel events. + // As the exceptionHandler runs in the EventLoop as the previous handlers we are sure all exceptions are + // correctly handled before the handler itself is removed. + // See https://issues.apache.org/jira/browse/CASSANDRA-13649 + pipeline.addLast(EXCEPTION_HANDLER, PreV5Handlers.ExceptionHandler.instance); + } + }; + } + + @ChannelHandler.Sharable + static class UnixSockMessage extends SimpleChannelInboundHandler + { + @Override + protected void channelRead0(ChannelHandlerContext ctx, Message.Request request) throws Exception + { + final Message.Response response; + final UnixSocketConnection connection; + long queryStartNanoTime = System.nanoTime(); + + try + { + assert request.connection() instanceof UnixSocketConnection; + connection = (UnixSocketConnection) request.connection(); + if (connection.getVersion().isGreaterOrEqualTo(ProtocolVersion.V4)) + ClientWarn.instance.captureWarnings(); + + QueryState qstate = connection.validateNewMessage(request.type, connection.getVersion(), request.getStreamId()); + //logger.info("Executing {} {} {}", request, connection.getVersion(), request.getStreamId()); + + Message.Response r = request.execute(qstate, queryStartNanoTime); + + //UnixSocket has no auth + response = r instanceof AuthenticateMessage ? new ReadyMessage() : r; + + response.setStreamId(request.getStreamId()); + response.setWarnings(ClientWarn.instance.getWarnings()); + response.attach(connection); + connection.applyStateTransition(request.type, response.type); + } + catch (Throwable t) + { + //logger.warn("Exception encountered", t); + JVMStabilityInspector.inspectThrowable(t); + ExceptionHandlers.UnexpectedChannelExceptionHandler handler = new ExceptionHandlers.UnexpectedChannelExceptionHandler(ctx.channel(), true); + ctx.writeAndFlush(ErrorMessage.fromException(t, handler).setStreamId(request.getStreamId())); + request.getSource().release(); + return; + } + finally + { + ClientWarn.instance.resetWarnings(); + } + + ctx.writeAndFlush(response); + request.getSource().release(); + } + } + + static class UnixSocketConnection extends ServerConnection + { + private enum State { UNINITIALIZED, AUTHENTICATION, READY } + + private final ClientState clientState; + private volatile State state; + // private final ConcurrentMap queryStates = new ConcurrentHashMap<>(); + + public UnixSocketConnection(Channel channel, ProtocolVersion version, Connection.Tracker tracker) + { + super(channel, version, tracker); + this.clientState = ClientState.forInternalCalls(); + this.state = State.UNINITIALIZED; + } + + @Override + public QueryState validateNewMessage(Message.Type type, ProtocolVersion version) + { + return validateNewMessage(type, version, -1); + } + + public QueryState validateNewMessage(Message.Type type, ProtocolVersion version, int streamId) + { + switch (state) + { + case UNINITIALIZED: + if (type != Message.Type.STARTUP && type != Message.Type.OPTIONS) + throw new ProtocolException(String.format("Unexpected message %s, expecting STARTUP or OPTIONS", type)); + break; + case AUTHENTICATION: + // Support both SASL auth from protocol v2 and the older style Credentials auth from v1 + if (type != Message.Type.AUTH_RESPONSE && type != Message.Type.CREDENTIALS) + throw new ProtocolException(String.format("Unexpected message %s, expecting %s", type, version == ProtocolVersion.V1 ? "CREDENTIALS" : "SASL_RESPONSE")); + break; + case READY: + if (type == Message.Type.STARTUP) + throw new ProtocolException("Unexpected message STARTUP, the connection is already initialized"); + break; + default: + throw new AssertionError(); + } + return new QueryState(clientState); + } + + @Override + public void applyStateTransition(Message.Type requestType, Message.Type responseType) + { + switch (state) + { + case UNINITIALIZED: + if (requestType == Message.Type.STARTUP) + { + // Just set the state to READY as the Unix socket needs to bypass authentication + state = State.READY; + } + break; + case AUTHENTICATION: + // Support both SASL auth from protocol v2 and the older style Credentials auth from v1 + assert requestType == Message.Type.AUTH_RESPONSE || requestType == Message.Type.CREDENTIALS; + + if (responseType == Message.Type.READY || responseType == Message.Type.AUTH_SUCCESS) + { + state = State.READY; + // we won't use the authenticator again, null it so that it can be GC'd + } + break; + case READY: + break; + default: + throw new AssertionError(); + } + } + + @Override + public IAuthenticator.SaslNegotiator getSaslNegotiator(QueryState queryState) + { + return null; + } + } + + static class PipelineChannelInitializer extends ByteToMessageDecoder + { + Envelope.Decoder decoder; + Connection.Factory factory; + + PipelineChannelInitializer(Envelope.Decoder decoder, Connection.Factory factory) + { + this.decoder = decoder; + this.factory = factory; + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List out) throws Exception { + Envelope inbound = decoder.decode(buffer); + if (inbound == null) + return; + + try + { + Envelope outbound; + switch (inbound.header.type) + { + case OPTIONS: + logger.debug("OPTIONS received {}", inbound.header.version); + List cqlVersions = new ArrayList<>(); + cqlVersions.add(QueryProcessor.CQL_VERSION.toString()); + + List compressions = new ArrayList<>(); + if (Compressor.SnappyCompressor.instance != null) + compressions.add("snappy"); + // LZ4 is always available since worst case scenario it default to a pure JAVA implem. + compressions.add("lz4"); + + Map> supportedOptions = new HashMap<>(); + supportedOptions.put(StartupMessage.CQL_VERSION, cqlVersions); + supportedOptions.put(StartupMessage.COMPRESSION, compressions); + supportedOptions.put(StartupMessage.PROTOCOL_VERSIONS, ProtocolVersion.supportedVersions()); + SupportedMessage supported = new SupportedMessage(supportedOptions); + outbound = supported.encode(inbound.header.version); + ctx.writeAndFlush(outbound); + break; + + case STARTUP: + Attribute attrConn = ctx.channel().attr(Connection.attributeKey); + Connection connection = attrConn.get(); + if (connection == null) + { + connection = factory.newConnection(ctx.channel(), inbound.header.version); + attrConn.set(connection); + } + assert connection instanceof ServerConnection; + + StartupMessage startup = (StartupMessage) Message.Decoder.decodeMessage(ctx.channel(), inbound); + // InetAddress remoteAddress = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress(); + // final ClientResourceLimits.Allocator allocator = ClientResourceLimits.getAllocatorForEndpoint(remoteAddress); + + ChannelPromise promise; + if (inbound.header.version.isGreaterOrEqualTo(ProtocolVersion.V5)) + { + // v5 not yet supported + logger.warn("PROTOCOL v5 not yet supported."); + + } + // no need to configure the pipeline asynchronously in this case + // the capacity obtained from allocator for the STARTUP message + // is released when flushed by the legacy dispatcher/flusher so + // there's no need to explicitly release that here either. + + ChannelPipeline pipeline = ctx.channel().pipeline(); + pipeline.addBefore(ENVELOPE_ENCODER, ENVELOPE_DECODER, new Envelope.Decoder()); + pipeline.addBefore(INITIAL_HANDLER, MESSAGE_DECOMPRESSOR, Envelope.Decompressor.instance); + pipeline.addBefore(INITIAL_HANDLER, MESSAGE_COMPRESSOR, Envelope.Compressor.instance); + pipeline.addBefore(INITIAL_HANDLER, MESSAGE_DECODER, PreV5Handlers.ProtocolDecoder.instance); + pipeline.addBefore(INITIAL_HANDLER, MESSAGE_ENCODER, PreV5Handlers.ProtocolEncoder.instance); + pipeline.addBefore(INITIAL_HANDLER, LEGACY_MESSAGE_PROCESSOR, new UnixSockMessage()); + pipeline.remove(INITIAL_HANDLER); + + + promise = new VoidChannelPromise(ctx.channel(), false); + + Message.Response response = Dispatcher.processRequest((ServerConnection) connection, startup); + + if (response.type.equals(Message.Type.AUTHENTICATE)) + // bypass authentication + response = new ReadyMessage(); + + outbound = response.encode(inbound.header.version); + ctx.writeAndFlush(outbound, promise); + logger.debug("Configured pipeline: {}", ctx.pipeline()); + break; + + default: + ErrorMessage error = + ErrorMessage.fromException( + new ProtocolException(String.format("Unexpected message %s, expecting STARTUP or OPTIONS", + inbound.header.type))); + outbound = error.encode(inbound.header.version); + ctx.writeAndFlush(outbound); + } + } + finally + { + inbound.release(); + } + } + } +} diff --git a/management-api-agent-4.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.CassandraAPIServiceProvider b/management-api-agent-4.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.CassandraAPIServiceProvider new file mode 100644 index 00000000..450f9f4d --- /dev/null +++ b/management-api-agent-4.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.CassandraAPIServiceProvider @@ -0,0 +1 @@ +com.datastax.mgmtapi.CassandraAPIServiceProvider4x diff --git a/management-api-agent-4.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.rpc.RpcMethodServiceProvider b/management-api-agent-4.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.rpc.RpcMethodServiceProvider new file mode 100644 index 00000000..3554cd27 --- /dev/null +++ b/management-api-agent-4.x/src/main/resources/META-INF/services/com.datastax.mgmtapi.rpc.RpcMethodServiceProvider @@ -0,0 +1 @@ +com.datastax.mgmtapi.rpc.RpcMethodServiceProvider4x diff --git a/management-api-agent-common/README.md b/management-api-agent-common/README.md new file mode 100644 index 00000000..da77f02d --- /dev/null +++ b/management-api-agent-common/README.md @@ -0,0 +1,35 @@ +# Management API Agent + +The Agent is the bridge between the Management API and the instance of Cassandra or DSE it controls. +This is accomplished by adding the agent jarfile to the startup options of the server with the `-javaagent` +directive. See the [main README](../README.md#using-the-service-with-a-locally-installed-c-or-dse-instance) +as an example. + +## Agent Common and Server Specific Agent classes + +Ideally, all of the agent code that interacts with Cassandra or DSE would live in a single place. However, +different versions of the server require different implementations. Anything that can be reused across the +server versions should go into the agent-common project here. Anything that requires a different implementation +based on the server needs to go into the server specific project (i.e. agent-3.x, agent-4.x or agent-dse-6.8). + +## Duplicated Code in Agent Projects +If you look at the Agent sub projects for [3.x](../management-api-agent-3.x), [4.x](../management-api-agent-4.x) +and [dse-6.8](../management-api-agent-dse-6.8), you will see that some of the code appears to be duplicated. +And you would be correct! While those projects do hold implementation differences, some of the code is +completely duplicated. Normally, this code should be in the common package. + +The reason the code is duplicated is due to a change in Cassandra 4.0 [beta3](https://github.com/apache/cassandra/commit/ccab496d2d37c86341d364dea6c27513fda27331#diff-e6e67347a585718be50482cd8ba211647b64f95c543f6e8ab9f15475ba19ee1a) +where [TypeSerializer](https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/serializers/TypeSerializer.java#L26) +was changed from an *interface* to an *abstract class*. While TypeSerializer's API didn't change, +and the Agent code using it compiles just fine against either version, it won't work if you don't match +the Cassandra dependency jars with the server you run against. If you compile against 3.x jars and run +against Cassandra 4.0-beta3 or newer, you will get an exception *java.lang.IncompatibleClassChangeError* +indicating that TypeSerializer was found to be a class, but was expected to be an interface. The same +applies to compiling against 4.0-beta3 jars and running against Cassandra 3.x. + +Because of this, any Agent code that uses TypeSerializer needs to be split into the sub projects so +that the Agent code can be compiled against the correct class type. *NOTE:* This could happen with any +Cassandra classes that are used by the Agent code, and similar changes would require more refactoring of +common code into server specific sub projects. + + diff --git a/management-api-shim-3.x/pom.xml b/management-api-agent-common/pom.xml similarity index 59% rename from management-api-shim-3.x/pom.xml rename to management-api-agent-common/pom.xml index d9343e07..8cbc4bdc 100644 --- a/management-api-shim-3.x/pom.xml +++ b/management-api-agent-common/pom.xml @@ -9,41 +9,64 @@ com.datastax - datastax-mgmtapi-shim-3.x + datastax-mgmtapi-agent-common 3.11.5 - 1.9.15 + 1.10.10 3.1.5 - 4.4.0 + 4.10.0 build_version.sh - - - com.datastax - datastax-mgmtapi-common - ${project.version} - - - org.apache.cassandra - cassandra-all - ${cassandra.version} - - - commons-codec - * - - - provided - - - junit - junit - 4.11 - test - - + + + default + + true + + + + com.datastax + datastax-mgmtapi-common + ${project.version} + + + net.bytebuddy + byte-buddy + ${bytebuddy.version} + + + net.bytebuddy + byte-buddy-agent + ${bytebuddy.version} + + + org.apache.cassandra + cassandra-all + ${cassandra.version} + + + commons-codec + * + + + provided + + + com.datastax.oss + java-driver-query-builder + ${driver.version} + + + junit + junit + 4.11 + test + + + + @@ -128,6 +151,16 @@ shade + + + + + + com.datastax.mgmtapi.Agent + + + + diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/Agent.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/Agent.java similarity index 100% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/Agent.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/Agent.java diff --git a/management-api-agent-common/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProvider.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProvider.java new file mode 100644 index 00000000..d2a44152 --- /dev/null +++ b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProvider.java @@ -0,0 +1,13 @@ +/* + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi; + +import com.datastax.mgmtapi.shims.CassandraAPI; + +public interface CassandraAPIServiceProvider +{ + CassandraAPI getCassandraAPI(); +} diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/NodeOpsProvider.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/NodeOpsProvider.java similarity index 100% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/NodeOpsProvider.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/NodeOpsProvider.java diff --git a/management-api-agent-common/src/main/java/com/datastax/mgmtapi/ShimLoader.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/ShimLoader.java new file mode 100644 index 00000000..14d02a20 --- /dev/null +++ b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/ShimLoader.java @@ -0,0 +1,25 @@ +/* + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; + +import com.datastax.mgmtapi.shims.CassandraAPI; +import com.datastax.mgmtapi.util.ServiceProviderLoader; + +public class ShimLoader +{ + public static final Supplier instance = Suppliers.memoize(ShimLoader::loadShim); + + private static CassandraAPI loadShim() + { + ServiceProviderLoader loader = new ServiceProviderLoader<>(); + CassandraAPIServiceProvider provider = loader.getProvider(CassandraAPIServiceProvider.class); + return provider.getCassandraAPI(); + } + +} diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/interceptors/CassandraDaemonInterceptor.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/interceptors/CassandraDaemonInterceptor.java similarity index 100% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/interceptors/CassandraDaemonInterceptor.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/interceptors/CassandraDaemonInterceptor.java diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/interceptors/CassandraRoleManagerInterceptor.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/interceptors/CassandraRoleManagerInterceptor.java similarity index 100% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/interceptors/CassandraRoleManagerInterceptor.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/interceptors/CassandraRoleManagerInterceptor.java diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/interceptors/QueryHandlerInterceptor.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/interceptors/QueryHandlerInterceptor.java similarity index 100% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/interceptors/QueryHandlerInterceptor.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/interceptors/QueryHandlerInterceptor.java diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/interceptors/QueryHandlerInterceptor4x.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/interceptors/QueryHandlerInterceptor4x.java similarity index 100% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/interceptors/QueryHandlerInterceptor4x.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/interceptors/QueryHandlerInterceptor4x.java diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/interceptors/SystemDistributedReplicationInterceptor.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/interceptors/SystemDistributedReplicationInterceptor.java similarity index 100% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/interceptors/SystemDistributedReplicationInterceptor.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/interceptors/SystemDistributedReplicationInterceptor.java diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/LazyRef.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/LazyRef.java similarity index 100% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/LazyRef.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/LazyRef.java diff --git a/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer.java new file mode 100644 index 00000000..9c8e71eb --- /dev/null +++ b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializer.java @@ -0,0 +1,35 @@ +/** + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.rpc; + +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.List; +import org.apache.cassandra.cql3.ResultSet; + +public interface ObjectSerializer +{ + /** + * Serialize an object into a C* ResultSet, with each field as a named value. + * @param obj The object to serialize + * @param ksName Pretend we are coming from this keyspace + * @param cfName Pretend we are coming from this columnfamily + */ + public ResultSet toResultSet(T obj, String ksName, String cfName); + + /** + * Serialize an object into a C* multi-row ResultSet, with each field as a named value. + * + * @param obj The object to serialize + * @param ksName Pretend we are coming from this keyspace + * @param cfName Pretend we are coming from this columnfamily + */ + public ResultSet toMultiRowResultSet(Collection obj, String ksName, String cfName); + + public List toByteBufferList(T obj); + + public ByteBuffer toByteBuffer(T obj); +} diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/Rpc.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/Rpc.java similarity index 100% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/Rpc.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/Rpc.java diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcClientState.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcClientState.java similarity index 100% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcClientState.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcClientState.java diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcExecutionException.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcExecutionException.java similarity index 100% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcExecutionException.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcExecutionException.java diff --git a/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod.java new file mode 100644 index 00000000..b6632a85 --- /dev/null +++ b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod.java @@ -0,0 +1,22 @@ +/* + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.rpc; + +import java.nio.ByteBuffer; +import java.util.List; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.service.ClientState; + +public interface RpcMethod { + + String getName(); + + public int getArgumentCount(); + + public ColumnSpecification getArgumentSpecification(int i); + + public Object execute(ClientState state, List parameters); +} diff --git a/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProvider.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProvider.java new file mode 100644 index 00000000..3f17d972 --- /dev/null +++ b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProvider.java @@ -0,0 +1,13 @@ +/* + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.rpc; + +import java.lang.reflect.Method; + +public interface RpcMethodServiceProvider { + + RpcMethod getRpcMethod(Method method, RpcObject rpcObject); +} diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcObject.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcObject.java similarity index 85% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcObject.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcObject.java index 2a68740a..b5313197 100644 --- a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcObject.java +++ b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcObject.java @@ -11,10 +11,15 @@ import java.util.Map; import java.util.Optional; +import com.datastax.mgmtapi.util.ServiceProviderLoader; import com.google.common.collect.ImmutableMap; public class RpcObject { + private static final RpcMethodServiceProvider RPC_METHOD_PROVIDER = + new ServiceProviderLoader() + .getProvider(RpcMethodServiceProvider.class); + protected final String name; protected final Object raw; protected final ImmutableMap rpcs; @@ -30,7 +35,7 @@ protected RpcObject(String name, Object object) { if (method.isAnnotationPresent(Rpc.class)) { - RpcMethod rpcMethod = new RpcMethod(method, this); + RpcMethod rpcMethod = RPC_METHOD_PROVIDER.getRpcMethod(method, this); if (found.containsKey(rpcMethod.getName())) { throw new AssertionError(String.format("Naming conflict in class %s: method %s already exists. " + diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcParam.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcParam.java similarity index 100% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcParam.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcParam.java diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcRegistry.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcRegistry.java similarity index 100% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcRegistry.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcRegistry.java diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcResource.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcResource.java similarity index 100% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcResource.java rename to management-api-agent-common/src/main/java/com/datastax/mgmtapi/rpc/RpcResource.java diff --git a/management-api-agent-common/src/main/java/com/datastax/mgmtapi/util/ServiceProviderLoader.java b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/util/ServiceProviderLoader.java new file mode 100644 index 00000000..12dbe7b3 --- /dev/null +++ b/management-api-agent-common/src/main/java/com/datastax/mgmtapi/util/ServiceProviderLoader.java @@ -0,0 +1,46 @@ +/* + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.util; + +import java.util.MissingResourceException; +import java.util.ServiceLoader; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ServiceProviderLoader +{ + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceProviderLoader.class); + + public SP getProvider(Class clazz) + { + ServiceLoader loader = ServiceLoader.load(clazz); + SP tempProvider = null; + for (SP provider : loader) + { + if (tempProvider == null) + { + tempProvider = provider; + } + else + { + LOGGER.error( + String.format( + "ServiceProvider for %s already found. Extra provider: %s", + clazz.getName(), + provider.getClass().getName())); + } + } + if (tempProvider == null) + { + throw new MissingResourceException( + "No ServiceProvider found. Please check that an agent jarfile is deployed.", + clazz.getName(), + "SPI configuration"); + } + return tempProvider; + } +} diff --git a/management-api-agent/src/main/java/org/apache/cassandra/gms/GossiperInterceptor.java b/management-api-agent-common/src/main/java/org/apache/cassandra/gms/GossiperInterceptor.java similarity index 100% rename from management-api-agent/src/main/java/org/apache/cassandra/gms/GossiperInterceptor.java rename to management-api-agent-common/src/main/java/org/apache/cassandra/gms/GossiperInterceptor.java diff --git a/management-api-agent/src/main/java/org/apache/cassandra/locator/K8SeedProvider.java b/management-api-agent-common/src/main/java/org/apache/cassandra/locator/K8SeedProvider.java similarity index 100% rename from management-api-agent/src/main/java/org/apache/cassandra/locator/K8SeedProvider.java rename to management-api-agent-common/src/main/java/org/apache/cassandra/locator/K8SeedProvider.java diff --git a/management-api-shim-dse-6.8/README.md b/management-api-agent-dse-6.8/README.md similarity index 100% rename from management-api-shim-dse-6.8/README.md rename to management-api-agent-dse-6.8/README.md diff --git a/management-api-shim-dse-6.8/pom.xml b/management-api-agent-dse-6.8/pom.xml similarity index 84% rename from management-api-shim-dse-6.8/pom.xml rename to management-api-agent-dse-6.8/pom.xml index 4e4c56d5..64e4b562 100644 --- a/management-api-shim-dse-6.8/pom.xml +++ b/management-api-agent-dse-6.8/pom.xml @@ -9,7 +9,7 @@ 4.0.0 - datastax-mgmtapi-shim-dse-6.8 + datastax-mgmtapi-agent-dse-6.8 @@ -31,7 +31,7 @@ 6.8.2-f57a220 1.9.15 3.1.5 - 4.4.0 + 4.10.0 build_version.sh @@ -41,6 +41,11 @@ datastax-mgmtapi-common ${project.version} + + com.datastax + datastax-mgmtapi-agent-common + ${project.version} + com.datastax.dse dse-commons @@ -145,6 +150,16 @@ shade + + + + + + com.datastax.mgmtapi.Agent + + + + diff --git a/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProviderDse68.java b/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProviderDse68.java new file mode 100644 index 00000000..14b0e979 --- /dev/null +++ b/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/CassandraAPIServiceProviderDse68.java @@ -0,0 +1,18 @@ +/** + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi; + +import com.datastax.mgmtapi.shim.DseAPI68; +import com.datastax.mgmtapi.shims.CassandraAPI; + +public class CassandraAPIServiceProviderDse68 implements CassandraAPIServiceProvider { + + @Override + public CassandraAPI getCassandraAPI() { + return new DseAPI68(); + } + +} diff --git a/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializerDse68.java b/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializerDse68.java new file mode 100644 index 00000000..de8e7421 --- /dev/null +++ b/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/GenericSerializerDse68.java @@ -0,0 +1,148 @@ +/** + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.rpc; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.BooleanType; +import org.apache.cassandra.db.marshal.ByteType; +import org.apache.cassandra.db.marshal.BytesType; +import org.apache.cassandra.db.marshal.DateType; +import org.apache.cassandra.db.marshal.DoubleType; +import org.apache.cassandra.db.marshal.EmptyType; +import org.apache.cassandra.db.marshal.FloatType; +import org.apache.cassandra.db.marshal.InetAddressType; +import org.apache.cassandra.db.marshal.Int32Type; +import org.apache.cassandra.db.marshal.ListType; +import org.apache.cassandra.db.marshal.LongType; +import org.apache.cassandra.db.marshal.MapType; +import org.apache.cassandra.db.marshal.SetType; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.db.marshal.UUIDType; +import org.apache.cassandra.serializers.TypeSerializer; + +/** + * Uses reflection to look up an appropriate TypeSerializer/AbstractType to serialize objects + * without writing annoying ByteBufferUtil.bytes(X/Y/Z) boilerplate. + */ +class GenericSerializerDse68 +{ + // I considered using the drivers code (CodecRegistry, TypeCodec, etc) but decided that it made more sense to + // use the server side stuff from Cassandra. + + // extending this (at least for the purpose of RPC calls) is relatively straightforward: write a class that + // extends C*'s TypeSerializer and add to the map. For actually getting data into C*'s UDTs it might be a bit + // trickier. Unfortunately, there is not always a direct 1:1 mapping between Java and Cassandra types. A simple + // example is millisecond timestamps, which could be serialized as 'long' and 'timestamp'. The driver code + // actually has some bounds for this, but I think for us it will be simpler to just write more serializers and + // add them to the map. + private static final ConcurrentHashMap typeMap = new ConcurrentHashMap() {{ + put("void", EmptyType.instance); + put("boolean", BooleanType.instance); + put("java.lang.Boolean", BooleanType.instance); + put("byte", ByteType.instance); + put("java.lang.Byte", ByteType.instance); + put("int", Int32Type.instance); + put("java.lang.Integer", Int32Type.instance); + put("long", LongType.instance); + put("java.lang.Long", LongType.instance); + put("float", FloatType.instance); + put("java.lang.Float", FloatType.instance); + put("double", DoubleType.instance); + put("java.lang.Double", DoubleType.instance); + put("java.lang.String", UTF8Type.instance); + put("java.net.InetAddress", InetAddressType.instance); + put("java.util.Date", DateType.instance); + put("java.nio.ByteBuffer", BytesType.instance); + put("java.util.UUID", UUIDType.instance); + }}; + + static void registerType(String className, AbstractType type) + { + if (typeMap.putIfAbsent(className, type) != null) + { + throw new IllegalStateException("The type " + className + " is already registered."); + } + } + + static TypeSerializer getSerializer(Type type) + { + return getTypeOrException(type).getSerializer(); + } + + static AbstractType getTypeOrException(Type type) + { + AbstractType ctype = getType(type); + + if (ctype == null) + { + throw new AssertionError(String.format("Add type '%s' to GenericSerializer", type.getTypeName())); + } + + return ctype; + } + + static boolean simpleType(Type type) + { + return getType(type) != null; + } + + /** + * Most of the actual work is done here. Note that Generic type information is mostly destroyed at runtime + * (a list is just a list). For the Parameterized types to work correctly you have to call + * Method.getGenericParameterTypes() or something similar. Also, we currently punt on the frozen keyword. + * @return The C* abstract type corresponding to the Java type, or null if not found/impossible. + */ + static AbstractType getType(Type type) + { + assert type != null; + String strType = type.getTypeName(); + + // Rather than hard coding List List List etc we create them as needed. Also there + // is no need for a lock as the actual serializers do that for us. + if (!typeMap.containsKey(strType)) + { + if (type instanceof ParameterizedType) + { + ParameterizedType ptype = (ParameterizedType) type; + + if (ptype.getRawType().getTypeName().equals("java.util.List")) + { + assert ptype.getActualTypeArguments().length == 1; + typeMap.putIfAbsent(strType, + ListType.getInstance(getType(ptype.getActualTypeArguments()[0]), false)); + } + else if (ptype.getRawType().getTypeName().equals("java.util.Set")) + { + assert ptype.getActualTypeArguments().length == 1; + typeMap.putIfAbsent(strType, + SetType.getInstance(getType(ptype.getActualTypeArguments()[0]), false)); + } + else if (ptype.getRawType().getTypeName().equals("java.util.Map")) + { + assert ptype.getActualTypeArguments().length == 2; + typeMap.putIfAbsent(strType, + MapType.getInstance(getType(ptype.getActualTypeArguments()[0]), + getType(ptype.getActualTypeArguments()[1]), false)); + } + else + { + throw new AssertionError("Don't know how to serialize generic type '" + + ptype.getRawType().getTypeName() + "'"); + } + } + else + { + return null; + } + } + + return typeMap.get(strType); + } +} diff --git a/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializerDse68.java b/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializerDse68.java new file mode 100644 index 00000000..d5047344 --- /dev/null +++ b/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/ObjectSerializerDse68.java @@ -0,0 +1,137 @@ +/** + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.rpc; + +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.Lists; + +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.ResultSet; +import org.apache.cassandra.cql3.ResultSet.ResultMetadata; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.TupleType; + +public class ObjectSerializerDse68 implements ObjectSerializer +{ + public final ImmutableSortedMap serializers; + + public class FieldSerializer + { + public final AbstractType type; + public final Function accessor; + + FieldSerializer(AbstractType type, Function accessor) + { + this.type = type; + this.accessor = accessor; + } + + FieldSerializer(AbstractType type, final Field field) + { + field.setAccessible(true); + this.type = type; + this.accessor = (obj) -> { + try + { + return field.get(obj); + } + catch (IllegalAccessException e) + { + throw new AssertionError("Should not happen as we set the field to accessible."); + } + }; + } + + ByteBuffer serializeField(T obj) + { + Object value = accessor.apply(obj); + if (value == null) + { + return null; + } + return type.getSerializer().serialize(accessor.apply(obj)); + } + } + + /** + * Due to the magic of java generics, the class doesn't have the full generic information, hence the double types. + * Also, this will only serialize **PUBLIC** fields (perhaps this should be changed; it's not totally clear). + * Tag accordingly. + */ + public ObjectSerializerDse68(Class clazz, Type genericType) + { + serializers = GenericSerializerDse68.simpleType(genericType) ? + ImmutableSortedMap.of("result", new FieldSerializer(GenericSerializerDse68.getType(genericType), x -> x)) : + ImmutableSortedMap.copyOf(Arrays.stream(clazz.getFields()) + .collect(Collectors.toMap(field -> field.getName(), + field -> new FieldSerializer(GenericSerializerDse68.getType(field.getType()), field)))); + // currently not recursive; multiple ways to do it + } + + public ObjectSerializerDse68(Class clazz) + { + this(clazz, clazz); + } + + /** + * Serialize an object into a C* ResultSet, with each field as a named value. + * @param obj The object to serialize + * @param ksName Pretend we are coming from this keyspace + * @param cfName Pretend we are coming from this columnfamily + */ + public ResultSet toResultSet(T obj, String ksName, String cfName) + { + return new ResultSet( + new ResultMetadata(serializers.entrySet().stream() + .map(e -> new ColumnSpecification(ksName, cfName, + new ColumnIdentifier(e.getKey(), true), + e.getValue().type)) + .collect(Collectors.toList())), + Lists.>newArrayList(toByteBufferList(obj))); + } + + /** + * Serialize an object into a C* multi-row ResultSet, with each field as a named value. + * + * @param obj The object to serialize + * @param ksName Pretend we are coming from this keyspace + * @param cfName Pretend we are coming from this columnfamily + */ + public ResultSet toMultiRowResultSet(Collection obj, String ksName, String cfName) + { + return new ResultSet( + new ResultMetadata(serializers.entrySet().stream() + .map(e -> new ColumnSpecification(ksName, cfName, + new ColumnIdentifier(e.getKey(), true), + e.getValue().type)) + .collect(Collectors.toList())), + obj.stream().map(this::toByteBufferList).collect(Collectors.toList())); + } + + public List toByteBufferList(T obj) + { + return serializers.values().stream() + .map(fs -> fs.serializeField(obj)) + .collect(Collectors.toList()); + } + + public ByteBuffer toByteBuffer(T obj) + { + return TupleType.buildValue(serializers.values().stream() + .map(fs -> fs.serializeField(obj)) + .toArray(ByteBuffer[]::new)); + } +} diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod.java b/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodDse68.java similarity index 92% rename from management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod.java rename to management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodDse68.java index 4454f8cd..b1ea0b30 100644 --- a/management-api-agent/src/main/java/com/datastax/mgmtapi/rpc/RpcMethod.java +++ b/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodDse68.java @@ -33,20 +33,20 @@ import org.apache.cassandra.transport.messages.ResultMessage; -public class RpcMethod +public class RpcMethodDse68 implements RpcMethod { - private static final Logger logger = LoggerFactory.getLogger(RpcMethod.class); + private static final Logger logger = LoggerFactory.getLogger(RpcMethodDse68.class); private final Method method; private final RpcObject rpcObject; private final String name; private final List argSerializers; private final List argTypes; private final List argNames; - private final ObjectSerializer retSerializer; + private final ObjectSerializerDse68 retSerializer; private final OptionalInt clientStateArgIdx; private final List> params; - RpcMethod(Method method, RpcObject rpcObject) + RpcMethodDse68(Method method, RpcObject rpcObject) { this.method = method; this.rpcObject = rpcObject; @@ -78,11 +78,11 @@ RpcMethod(Method method, RpcObject rpcObject) Type[] genericParamTypes = method.getGenericParameterTypes(); this.argSerializers = params.stream() - .map(p -> GenericSerializer.getSerializer(genericParamTypes[p.getKey()])) + .map(p -> GenericSerializerDse68.getSerializer(genericParamTypes[p.getKey()])) .collect(Collectors.toList()); this.argTypes = params.stream() - .map(p -> GenericSerializer.getTypeOrException(genericParamTypes[p.getKey()])) + .map(p -> GenericSerializerDse68.getTypeOrException(genericParamTypes[p.getKey()])) .collect(Collectors.toList()); this.argNames = params.stream() @@ -96,11 +96,11 @@ RpcMethod(Method method, RpcObject rpcObject) Type elemType = ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0]; Preconditions.checkArgument(elemType instanceof Class, "If multi-row result set is request, the element type must be a Class"); - this.retSerializer = new ObjectSerializer<>((Class) elemType); + this.retSerializer = new ObjectSerializerDse68<>((Class) elemType); } else { - this.retSerializer = new ObjectSerializer<>(method.getReturnType(), method.getGenericReturnType()); + this.retSerializer = new ObjectSerializerDse68<>(method.getReturnType(), method.getGenericReturnType()); } } diff --git a/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProviderDse68.java b/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProviderDse68.java new file mode 100644 index 00000000..ca7c222f --- /dev/null +++ b/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/rpc/RpcMethodServiceProviderDse68.java @@ -0,0 +1,17 @@ +/** + * Copyright DataStax, Inc. + * + * Please see the included license file for details. + */ +package com.datastax.mgmtapi.rpc; + +import java.lang.reflect.Method; + +public class RpcMethodServiceProviderDse68 implements RpcMethodServiceProvider { + + @Override + public RpcMethod getRpcMethod(Method method, RpcObject rpcObject) { + return new RpcMethodDse68(method, rpcObject); + } + +} diff --git a/management-api-shim-dse-6.8/src/main/java/com/datastax/mgmtapi/shim/DseAPI68.java b/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/shim/DseAPI68.java similarity index 98% rename from management-api-shim-dse-6.8/src/main/java/com/datastax/mgmtapi/shim/DseAPI68.java rename to management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/shim/DseAPI68.java index bec5d190..1fced9f5 100644 --- a/management-api-shim-dse-6.8/src/main/java/com/datastax/mgmtapi/shim/DseAPI68.java +++ b/management-api-agent-dse-6.8/src/main/java/com/datastax/mgmtapi/shim/DseAPI68.java @@ -32,11 +32,9 @@ import org.apache.cassandra.auth.IRoleManager; import org.apache.cassandra.concurrent.TPCTaskType; import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.cql3.CQLStatement; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.db.ConsistencyLevel; -import org.apache.cassandra.db.HintedHandOffManager; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.compaction.CompactionManager; import org.apache.cassandra.dht.IPartitioner; @@ -289,12 +287,6 @@ public IRoleManager getRoleManager() return DatabaseDescriptor.getRoleManager(); } - @Override - public HintedHandOffManager getHintedHandoffManager() - { - return HintedHandOffManager.instance; - } - @Override public CompactionManager getCompactionManager() { diff --git a/management-api-shim-dse-6.8/src/main/java/org/apache/cassandra/locator/K8SeedProviderDse68.java b/management-api-agent-dse-6.8/src/main/java/org/apache/cassandra/locator/K8SeedProviderDse68.java similarity index 100% rename from management-api-shim-dse-6.8/src/main/java/org/apache/cassandra/locator/K8SeedProviderDse68.java rename to management-api-agent-dse-6.8/src/main/java/org/apache/cassandra/locator/K8SeedProviderDse68.java diff --git a/management-api-shim-dse-6.8/src/main/java/org/apache/cassandra/transport/UnixSocketServerDse68.java b/management-api-agent-dse-6.8/src/main/java/org/apache/cassandra/transport/UnixSocketServerDse68.java similarity index 100% rename from management-api-shim-dse-6.8/src/main/java/org/apache/cassandra/transport/UnixSocketServerDse68.java rename to management-api-agent-dse-6.8/src/main/java/org/apache/cassandra/transport/UnixSocketServerDse68.java diff --git a/management-api-agent-dse-6.8/src/main/resources/META-INF/services/com.datastax.mgmtapi.CassandraAPIServiceProvider b/management-api-agent-dse-6.8/src/main/resources/META-INF/services/com.datastax.mgmtapi.CassandraAPIServiceProvider new file mode 100644 index 00000000..44fc9290 --- /dev/null +++ b/management-api-agent-dse-6.8/src/main/resources/META-INF/services/com.datastax.mgmtapi.CassandraAPIServiceProvider @@ -0,0 +1 @@ +com.datastax.mgmtapi.CassandraAPIServiceProviderDse68 diff --git a/management-api-agent-dse-6.8/src/main/resources/META-INF/services/com.datastax.mgmtapi.rpc.RpcMethodServiceProvider b/management-api-agent-dse-6.8/src/main/resources/META-INF/services/com.datastax.mgmtapi.rpc.RpcMethodServiceProvider new file mode 100644 index 00000000..51773287 --- /dev/null +++ b/management-api-agent-dse-6.8/src/main/resources/META-INF/services/com.datastax.mgmtapi.rpc.RpcMethodServiceProvider @@ -0,0 +1 @@ +com.datastax.mgmtapi.rpc.RpcMethodServiceProviderDse68 diff --git a/management-api-agent/src/main/java/com/datastax/mgmtapi/ShimLoader.java b/management-api-agent/src/main/java/com/datastax/mgmtapi/ShimLoader.java deleted file mode 100644 index f8c5557c..00000000 --- a/management-api-agent/src/main/java/com/datastax/mgmtapi/ShimLoader.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Please see the included license file for details. - */ -package com.datastax.mgmtapi; - -import java.lang.reflect.Method; -import java.util.Optional; - -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.datastax.mgmtapi.shim.CassandraAPI3x; -import com.datastax.mgmtapi.shim.CassandraAPI4x; -import com.datastax.mgmtapi.shims.CassandraAPI; -import org.apache.cassandra.service.StorageService; - -public class ShimLoader -{ - private static final Logger LOGGER = LoggerFactory.getLogger(NodeOpsProvider.class); - public static final Supplier instance = Suppliers.memoize(ShimLoader::loadShim); - - private static CassandraAPI loadShim() - { - String version = StorageService.instance.getReleaseVersion(); - Optional dseAPI = maybeGetDseAPI(); - if (dseAPI.isPresent()) - { - return dseAPI.get(); - } - - if (version.startsWith("3.")) - return new CassandraAPI3x(); - - if (version.startsWith("4.")) - return new CassandraAPI4x(); - - throw new RuntimeException("No Cassandra API Shim found for version: " + version); - } - - private static Optional maybeGetDseAPI() - { - String dseVersion = "UNKNOWN"; - try - { - Method getDSEReleaseVersion = StorageService.instance.getClass().getDeclaredMethod("getDSEReleaseVersion"); - dseVersion = (String) getDSEReleaseVersion.invoke(StorageService.instance); - if (dseVersion.startsWith("6.8")) - { - // so that we don't have a direct dependency to the DSE shim project - return Optional.of((CassandraAPI) Class.forName("com.datastax.mgmtapi.shim.DseAPI68").getConstructor().newInstance()); - } - } - catch (Exception e) - { - if (!(e instanceof NoSuchMethodException)) - LOGGER.warn(String.format("No DSE API Shim found for DSE Version %s. Error was: %s", dseVersion, e.getMessage()), e); - } - return Optional.empty(); - } -} diff --git a/management-api-common/src/main/java/com/datastax/mgmtapi/shims/CassandraAPI.java b/management-api-common/src/main/java/com/datastax/mgmtapi/shims/CassandraAPI.java index bdb69ddb..15c057a2 100644 --- a/management-api-common/src/main/java/com/datastax/mgmtapi/shims/CassandraAPI.java +++ b/management-api-common/src/main/java/com/datastax/mgmtapi/shims/CassandraAPI.java @@ -10,17 +10,13 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; -import java.util.function.Supplier; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import org.apache.cassandra.auth.IRoleManager; -import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.cql3.CQLStatement; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.db.ConsistencyLevel; -import org.apache.cassandra.db.HintedHandOffManager; import org.apache.cassandra.db.compaction.CompactionManager; import org.apache.cassandra.gms.Gossiper; import org.apache.cassandra.hints.HintsService; @@ -56,9 +52,6 @@ default UntypedResultSet processQuery(String query, ConsistencyLevel consistency IRoleManager getRoleManager(); - @Deprecated - HintedHandOffManager getHintedHandoffManager(); - CompactionManager getCompactionManager(); Gossiper getGossiper(); diff --git a/management-api-server/pom.xml b/management-api-server/pom.xml index e49f88c2..12fde333 100644 --- a/management-api-server/pom.xml +++ b/management-api-server/pom.xml @@ -28,7 +28,7 @@ 2.0.8 4.0.0.Final 4.1.50.Final - 4.4.0 + 4.10.0 3.1.2 3.11.5 @@ -183,7 +183,7 @@ ${basedir}/target/apache-cassandra-${cassandra.version} - -javaagent:${basedir}/../management-api-agent/target/datastax-mgmtapi-agent-${project.version}.jar + -javaagent:${basedir}/../management-api-agent/target/datastax-mgmtapi-agent-3.x-${project.version}.jar 1 1 diff --git a/management-api-server/src/main/java/com/datastax/mgmtapi/UnixSocketCQLAccess.java b/management-api-server/src/main/java/com/datastax/mgmtapi/UnixSocketCQLAccess.java index 73825270..5ef4ce83 100644 --- a/management-api-server/src/main/java/com/datastax/mgmtapi/UnixSocketCQLAccess.java +++ b/management-api-server/src/main/java/com/datastax/mgmtapi/UnixSocketCQLAccess.java @@ -56,6 +56,7 @@ import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; import com.datastax.oss.driver.internal.core.util.collection.QueryPlan; +import com.datastax.oss.driver.internal.core.util.collection.SimpleQueryPlan; import io.netty.channel.Channel; import io.netty.channel.EventLoopGroup; @@ -314,7 +315,7 @@ public void init(Map nodes, DistanceReporter distanceReporter) public Queue newQueryPlan(Request request, Session session) { final Node uxNode = unixSocketNode; - return uxNode == null ? new QueryPlan() : new QueryPlan(uxNode); + return uxNode == null ? QueryPlan.EMPTY : new SimpleQueryPlan(uxNode); } /** diff --git a/management-api-server/src/main/java/com/datastax/mgmtapi/resources/KeyspaceOpsResources.java b/management-api-server/src/main/java/com/datastax/mgmtapi/resources/KeyspaceOpsResources.java index 674eea3c..b4c5f6bb 100644 --- a/management-api-server/src/main/java/com/datastax/mgmtapi/resources/KeyspaceOpsResources.java +++ b/management-api-server/src/main/java/com/datastax/mgmtapi/resources/KeyspaceOpsResources.java @@ -15,7 +15,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/management-api-server/src/main/java/com/datastax/mgmtapi/resources/LifecycleResources.java b/management-api-server/src/main/java/com/datastax/mgmtapi/resources/LifecycleResources.java index 44326afd..77db950c 100644 --- a/management-api-server/src/main/java/com/datastax/mgmtapi/resources/LifecycleResources.java +++ b/management-api-server/src/main/java/com/datastax/mgmtapi/resources/LifecycleResources.java @@ -147,7 +147,7 @@ else if (canConnect) FileUtils.deleteQuietly(app.dbUnixSocketFile); boolean started = ShellUtils.executeShellWithHandlers( - String.format("nohup %s %s -R -Dcassandra.server_process -Dcassandra.skip_default_role_setup=true -Ddb.unix_socket_file=%s %s %s 2>&1", + String.format("nohup %s %s -R -Dcassandra.server_process -Dcassandra.skip_default_role_setup=true -Ddb.unix_socket_file=%s %s %s > /var/log/cassandra/stdout.log 2> /var/log/cassandra/stderr.log", profile != null ? "/tmp/" + profile + "/env.sh" : "", cassandraOrDseCommand, app.dbUnixSocketFile.getAbsolutePath(), @@ -278,7 +278,7 @@ public synchronized Response configureNodeJson(@QueryParam("profile") String pro @Path("/configure") @POST - @Consumes("application/yaml") + @Consumes({"application/yaml", "text/yaml"}) @Operation(description = "Configure Cassandra/DSE. Will fail if Cassandra/DSE is already started") public synchronized Response configureNode(@QueryParam("profile") String profile, String yaml) { diff --git a/management-api-server/src/main/java/com/datastax/mgmtapi/resources/TableOpsResources.java b/management-api-server/src/main/java/com/datastax/mgmtapi/resources/TableOpsResources.java index a9269794..45e9df3c 100644 --- a/management-api-server/src/main/java/com/datastax/mgmtapi/resources/TableOpsResources.java +++ b/management-api-server/src/main/java/com/datastax/mgmtapi/resources/TableOpsResources.java @@ -15,7 +15,7 @@ import java.util.ArrayList; import java.util.List; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import com.datastax.mgmtapi.resources.models.CompactRequest; diff --git a/management-api-server/src/test/java/com/datastax/mgmtapi/helpers/DockerHelper.java b/management-api-server/src/test/java/com/datastax/mgmtapi/helpers/DockerHelper.java index c12ac414..30b845b8 100644 --- a/management-api-server/src/test/java/com/datastax/mgmtapi/helpers/DockerHelper.java +++ b/management-api-server/src/test/java/com/datastax/mgmtapi/helpers/DockerHelper.java @@ -13,7 +13,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; import com.google.common.collect.Lists; @@ -26,12 +29,14 @@ import com.github.dockerjava.api.command.CreateContainerResponse; import com.github.dockerjava.api.command.InspectExecResponse; import com.github.dockerjava.api.command.ListContainersCmd; +import com.github.dockerjava.api.command.ListImagesCmd; import com.github.dockerjava.api.model.Bind; import com.github.dockerjava.api.model.BuildResponseItem; import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.ExposedPort; import com.github.dockerjava.api.model.Frame; import com.github.dockerjava.api.model.HostConfig; +import com.github.dockerjava.api.model.Image; import com.github.dockerjava.api.model.PortBinding; import com.github.dockerjava.api.model.Ports; import com.github.dockerjava.api.model.Volume; @@ -46,11 +51,41 @@ public class DockerHelper { + private static Logger logger = LoggerFactory.getLogger(DockerHelper.class); + + // Keep track of Docker images built during test runs + private static final Set IMAGE_NAMES = new HashSet<>(); + + // Cleanup hook to remove Docker images built for tests + static { + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + if (!Boolean.getBoolean("skip_test_docker_image_cleanup")) { + logger.info("Cleaning up test Docker images"); + DockerClient dockerClient = DockerClientBuilder.getInstance(DefaultDockerClientConfig.createDefaultConfigBuilder().build()).build(); + for (String imageName : IMAGE_NAMES) { + Image image = searchImages(imageName, dockerClient); + if (image != null) { + try { + dockerClient.removeImageCmd(image.getId()).exec(); + } catch (Throwable e) { + logger.info(String.format("Removing image %s did not complete cleanly", imageName)); + } + } + } + } + else + { + logger.info("Skipping test Docker image cleanup"); + } + } + }); + } private DockerClientConfig config; private DockerClient dockerClient; private String container; private File dataDir; - private Logger logger = LoggerFactory.getLogger(DockerHelper.class); public DockerHelper(File dataDir) { this.config = DefaultDockerClientConfig.createDefaultConfigBuilder().build(); @@ -178,6 +213,7 @@ public boolean started() private void buildImageWithBuildx(File dockerFile, File baseDir, String target, String name) throws Exception { ProcessBuilder pb = new ProcessBuilder("docker", "buildx", "build", "--load", + "--progress", "plain", "--tag", name, "--file", dockerFile.getPath(), "--target", target, @@ -192,10 +228,6 @@ private void buildImageWithBuildx(File dockerFile, File baseDir, String target, throw new Exception("Command '" + String.join(" ", pb.command() + "' return error code: " + exitCode)); } } - private String startDocker(File dockerFile, File baseDir, String target, String name, List ports, List volumeDescList, List envList, List cmdList) - { - return startDocker(dockerFile, baseDir, target, name, ports, volumeDescList, envList, cmdList, false); - } private String startDocker(File dockerFile, File baseDir, String target, String name, List ports, List volumeDescList, List envList, List cmdList, boolean useBuildx) { @@ -227,37 +259,47 @@ private String startDocker(File dockerFile, File baseDir, String target, String return containerId.getId(); } - BuildImageResultCallback callback = new BuildImageResultCallback() + // see if we have the image already built + final String imageName = String.format("%s-%s-test", name, dockerFile.getName()).toLowerCase(); + Image image = searchImages(imageName, dockerClient); + if (image == null) { - @Override - public void onNext(BuildResponseItem item) + BuildImageResultCallback callback = new BuildImageResultCallback() { - System.out.println("" + item); - super.onNext(item); - } - }; - - logger.info("Building container: " + name + " from " + dockerFile); - if (useBuildx) - { - try + @Override + public void onNext(BuildResponseItem item) + { + String stream = item.getStream(); + if (stream != null && !stream.equals("null")) + System.out.print(item.getStream()); + super.onNext(item); + } + }; + + logger.info(String.format("Building container: name=%s, Dockerfile=%s, image name=%s", name, dockerFile.getPath(), imageName)); + if (useBuildx) { - buildImageWithBuildx(dockerFile, baseDir, target, name); + try + { + buildImageWithBuildx(dockerFile, baseDir, target, imageName); + } + catch (Exception e) + { + e.printStackTrace(); + logger.error("Unable to build image"); + } } - catch (Exception e) + else { - e.printStackTrace(); - logger.error("Unable to build image"); + dockerClient.buildImageCmd() + .withBaseDirectory(baseDir) + .withDockerfile(dockerFile) + .withTags(Sets.newHashSet(imageName)) + .exec(callback) + .awaitImageId(); } - } - else - { - dockerClient.buildImageCmd() - .withBaseDirectory(baseDir) - .withDockerfile(dockerFile) - .withTags(Sets.newHashSet(name)) - .exec(callback) - .awaitImageId(); + logger.info(String.format("Adding image named %s to set of images to be cleaned up", imageName)); + IMAGE_NAMES.add(imageName); } List tcpPorts = new ArrayList<>(); @@ -285,7 +327,8 @@ public void onNext(BuildResponseItem item) CreateContainerResponse containerResponse; - containerResponse = dockerClient.createContainerCmd(name) + logger.warn("Binding a local temp directory to /var/log/cassandra can cause permissions issues on startup. Skipping volume bindings."); + containerResponse = dockerClient.createContainerCmd(imageName) .withCmd(cmdList) .withEnv(envList) .withExposedPorts(tcpPorts) @@ -293,7 +336,8 @@ public void onNext(BuildResponseItem item) new HostConfig() .withPortBindings(portBindings) .withPublishAllPorts(true) - .withBinds(volumeBindList) + // don't bind /var/log/cassandra, it causes permissions issues with startup + //.withBinds(volumeBindList) ) .withName(name) .exec(); @@ -304,7 +348,7 @@ public void onNext(BuildResponseItem item) @Override public void onNext(Frame item) { - logger.info(new String(item.getPayload())); + System.out.print(new String(item.getPayload())); } }); @@ -333,6 +377,40 @@ private Container searchContainer(String name) return null; } + private static Image searchImages(String imageName, DockerClient dockerClient) + { + ListImagesCmd listImagesCmd = dockerClient.listImagesCmd(); + List images = null; + logger.info(String.format("Searching for image named %s", imageName)); + try { + images = listImagesCmd.exec(); + if (!images.isEmpty()) { + Iterator it = images.iterator(); + while (it.hasNext()) { + Image image = it.next(); + String[] tags = image.getRepoTags(); + if (tags == null) + { + logger.warn(String.format("Image has NULL tags: %s", image.getId())); + } + else + { + for (int i=0; i< tags.length; ++i) { + if (tags[i].startsWith(imageName)) { + logger.info(String.format("Found an image named %s", imageName)); + return image; + } + } + } + } + } + } catch (Exception e) { + logger.warn("Failed to fetch images", e); + } + logger.info(String.format("No image named %s found", imageName)); + return null; + } + public void stopManagementAPI() { if (container != null) diff --git a/management-api-shim-4.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer4x.java b/management-api-shim-4.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer4x.java deleted file mode 100644 index e30c7005..00000000 --- a/management-api-shim-4.x/src/main/java/org/apache/cassandra/transport/UnixSocketServer4x.java +++ /dev/null @@ -1,202 +0,0 @@ -/** - * Copyright DataStax, Inc. - * - * Please see the included license file for details. - */ -package org.apache.cassandra.transport; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.SimpleChannelInboundHandler; -import org.apache.cassandra.auth.IAuthenticator; -import org.apache.cassandra.service.ClientState; -import org.apache.cassandra.service.ClientWarn; -import org.apache.cassandra.service.QueryState; -import org.apache.cassandra.transport.messages.AuthenticateMessage; -import org.apache.cassandra.transport.messages.ErrorMessage; -import org.apache.cassandra.transport.messages.ReadyMessage; -import org.apache.cassandra.utils.JVMStabilityInspector; - -public class UnixSocketServer4x -{ - private static final Logger logger = LoggerFactory.getLogger(UnixSocketServer4x.class); - - public static ChannelInitializer makeSocketInitializer(final Server.ConnectionTracker connectionTracker) - { - // Stateless handlers - final Message.ProtocolDecoder messageDecoder = new Message.ProtocolDecoder(); - final Message.ProtocolEncoder messageEncoder = new Message.ProtocolEncoder(); - final ChannelHandler frameDecompressor = new Frame.InboundBodyTransformer(); - final ChannelHandler frameCompressor = new Frame.OutboundBodyTransformer(); - final Frame.Encoder frameEncoder = new Frame.Encoder(); - final Message.ExceptionHandler exceptionHandler = new Message.ExceptionHandler(); - final UnixSockMessage dispatcher = new UnixSockMessage(); - - return new ChannelInitializer() - { - @Override - protected void initChannel(Channel channel) throws Exception - { - ChannelPipeline pipeline = channel.pipeline(); - - pipeline.addLast("frameDecoder", new Frame.Decoder((channel1, version) -> - new UnixSocketConnection(channel1, version, connectionTracker))); - pipeline.addLast("frameEncoder", frameEncoder); - - pipeline.addLast("frameDecompressor", frameDecompressor); - pipeline.addLast("frameCompressor", frameCompressor); - - pipeline.addLast("messageDecoder", messageDecoder); - pipeline.addLast("messageEncoder", messageEncoder); - - pipeline.addLast("executor", dispatcher); - - // The exceptionHandler will take care of handling exceptionCaught(...) events while still running - // on the same EventLoop as all previous added handlers in the pipeline. This is important as the used - // eventExecutorGroup may not enforce strict ordering for channel events. - // As the exceptionHandler runs in the EventLoop as the previous handlers we are sure all exceptions are - // correctly handled before the handler itself is removed. - // See https://issues.apache.org/jira/browse/CASSANDRA-13649 - pipeline.addLast("exceptionHandler", exceptionHandler); - } - }; - } - - @ChannelHandler.Sharable - static class UnixSockMessage extends SimpleChannelInboundHandler - { - @Override - protected void channelRead0(ChannelHandlerContext ctx, Message.Request request) throws Exception - { - final Message.Response response; - final UnixSocketConnection connection; - long queryStartNanoTime = System.nanoTime(); - - try - { - assert request.connection() instanceof UnixSocketConnection; - connection = (UnixSocketConnection) request.connection(); - if (connection.getVersion().isGreaterOrEqualTo(ProtocolVersion.V4)) - ClientWarn.instance.captureWarnings(); - - QueryState qstate = connection.validateNewMessage(request.type, connection.getVersion(), request.getStreamId()); - //logger.info("Executing {} {} {}", request, connection.getVersion(), request.getStreamId()); - - Message.Response r = request.execute(qstate, queryStartNanoTime); - - //UnixSocket has no auth - response = r instanceof AuthenticateMessage ? new ReadyMessage() : r; - - response.setStreamId(request.getStreamId()); - response.setWarnings(ClientWarn.instance.getWarnings()); - response.attach(connection); - connection.applyStateTransition(request.type, response.type); - } - catch (Throwable t) - { - //logger.warn("Exception encountered", t); - JVMStabilityInspector.inspectThrowable(t); - Message.UnexpectedChannelExceptionHandler handler = new Message.UnexpectedChannelExceptionHandler(ctx.channel(), true); - ctx.writeAndFlush(ErrorMessage.fromException(t, handler).setStreamId(request.getStreamId())); - request.getSourceFrame().release(); - return; - } - finally - { - ClientWarn.instance.resetWarnings(); - } - - ctx.writeAndFlush(response); - request.getSourceFrame().release(); - } - } - - static class UnixSocketConnection extends ServerConnection - { - private enum State { UNINITIALIZED, AUTHENTICATION, READY } - - private final ClientState clientState; - private volatile State state; - private final ConcurrentMap queryStates = new ConcurrentHashMap<>(); - - public UnixSocketConnection(Channel channel, ProtocolVersion version, Connection.Tracker tracker) - { - super(channel, version, tracker); - this.clientState = ClientState.forInternalCalls(); - this.state = State.UNINITIALIZED; - } - - public QueryState validateNewMessage(Message.Type type, ProtocolVersion version) - { - return validateNewMessage(type, version, -1); - } - - public QueryState validateNewMessage(Message.Type type, ProtocolVersion version, int streamId) - { - switch (state) - { - case UNINITIALIZED: - if (type != Message.Type.STARTUP && type != Message.Type.OPTIONS) - throw new ProtocolException(String.format("Unexpected message %s, expecting STARTUP or OPTIONS", type)); - break; - case AUTHENTICATION: - // Support both SASL auth from protocol v2 and the older style Credentials auth from v1 - if (type != Message.Type.AUTH_RESPONSE && type != Message.Type.CREDENTIALS) - throw new ProtocolException(String.format("Unexpected message %s, expecting %s", type, version == ProtocolVersion.V1 ? "CREDENTIALS" : "SASL_RESPONSE")); - break; - case READY: - if (type == Message.Type.STARTUP) - throw new ProtocolException("Unexpected message STARTUP, the connection is already initialized"); - break; - default: - throw new AssertionError(); - } - return new QueryState(clientState); - } - - @Override - public void applyStateTransition(Message.Type requestType, Message.Type responseType) - { - switch (state) - { - case UNINITIALIZED: - if (requestType == Message.Type.STARTUP) - { - if (responseType == Message.Type.AUTHENTICATE) - state = State.AUTHENTICATION; - else if (responseType == Message.Type.READY) - state = State.READY; - } - break; - case AUTHENTICATION: - // Support both SASL auth from protocol v2 and the older style Credentials auth from v1 - assert requestType == Message.Type.AUTH_RESPONSE || requestType == Message.Type.CREDENTIALS; - - if (responseType == Message.Type.READY || responseType == Message.Type.AUTH_SUCCESS) - { - state = State.READY; - // we won't use the authenticator again, null it so that it can be GC'd - } - break; - case READY: - break; - default: - throw new AssertionError(); - } - } - - public IAuthenticator.SaslNegotiator getSaslNegotiator(QueryState queryState) - { - return null; - } - } -} diff --git a/pom.xml b/pom.xml index 47c0731b..677af333 100644 --- a/pom.xml +++ b/pom.xml @@ -27,9 +27,9 @@ management-api-common - management-api-shim-3.x - management-api-shim-4.x - management-api-agent + management-api-agent-common + management-api-agent-3.x + management-api-agent-4.x management-api-server @@ -43,10 +43,10 @@ management-api-common - management-api-shim-3.x - management-api-shim-4.x - management-api-shim-dse-6.8 - management-api-agent + management-api-agent-common + management-api-agent-3.x + management-api-agent-4.x + management-api-agent-dse-6.8 management-api-server diff --git a/scripts/docker-entrypoint.sh b/scripts/docker-entrypoint.sh index e5613f6e..4e7d52b9 100755 --- a/scripts/docker-entrypoint.sh +++ b/scripts/docker-entrypoint.sh @@ -4,76 +4,75 @@ set -e # first arg is `-f` or `--some-option` # or there are no args if [ "$#" -eq 0 ] || [ "${1#-}" != "$1" ]; then - set -- cassandra -f "$@" + set -- cassandra -f "$@" fi if [ "$CASSANDRA_CONF" == "" ]; then - export CASSANDRA_CONF=/etc/cassandra + export CASSANDRA_CONF=/etc/cassandra fi # allow the container to be started with `--user` if [ "$1" = 'mgmtapi' -a "$(id -u)" = '0' ]; then - find "$CASSANDRA_CONF" /var/lib/cassandra /var/log/cassandra \ - \! -user cassandra -exec chown cassandra '{}' + - # exec gosu cassandra "$BASH_SOURCE" "$@" + find "$CASSANDRA_CONF" /var/lib/cassandra /var/log/cassandra \ + \! -user cassandra -exec chown cassandra '{}' + + # exec gosu cassandra "$BASH_SOURCE" "$@" fi _ip_address() { - # scrape the first non-localhost IP address of the container - # in Swarm Mode, we often get two IPs -- the container IP, and the (shared) VIP, and the container IP should always be first - ip address | awk ' - $1 == "inet" && $NF != "lo" { - gsub(/\/.+$/, "", $2) - print $2 - exit - } - ' + # scrape the first non-localhost IP address of the container + # in Swarm Mode, we often get two IPs -- the container IP, and the (shared) VIP, and the container IP should always be first + ip address | awk ' + $1 == "inet" && $NF != "lo" { + gsub(/\/.+$/, "", $2) + print $2 + exit + } + ' } # "sed -i", but without "mv" (which doesn't work on a bind-mounted file, for example) _sed-in-place() { - local filename="$1"; shift - local tempFile - tempFile="$(mktemp)" - sed "$@" "$filename" > "$tempFile" - cat "$tempFile" > "$filename" - rm "$tempFile" + local filename="$1"; shift + local tempFile + tempFile="$(mktemp)" + sed "$@" "$filename" > "$tempFile" + cat "$tempFile" > "$filename" + rm "$tempFile" } _metrics_collector_supported() { - # currently, metrics collector does not work on arm64 - [ "$(uname -m)" != "aarch64" ] + # currently, metrics collector does not work on arm64 + [ "$(uname -m)" != "aarch64" ] } if [ "$1" = 'mgmtapi' ]; then - echo "Starting Management API" - - # Copy over any config files mounted at /config - # cp /config/cassandra.yaml /etc/cassandra/cassandra.yaml - if [ -d "/config" ] && ! [ "/config" -ef "$CASSANDRA_CONF" ]; then - cp -R /config/* "${CASSANDRA_CONF:-/etc/cassandra}" - fi - - # Make sure the management api agent jar is set - # We do this here for the following reasons: - # 1. configbuilder will overwrite the cassandra-env-sh, so we don't want to set this after - # 2. We don't wan't operator or configbuilder to care so much about the version number or - # the fact this jar even exists. - - if _metrics_collector_supported && ! grep -qxF "JVM_OPTS=\"\$JVM_OPTS -javaagent:/opt/mcac-agent/lib/datastax-mcac-agent.jar\"" < /etc/cassandra/cassandra-env.sh ; then - # ensure newline at end of file - echo "" >> /etc/cassandra/cassandra-env.sh - echo "JVM_OPTS=\"\$JVM_OPTS -javaagent:/opt/mcac-agent/lib/datastax-mcac-agent.jar\"" >> /etc/cassandra/cassandra-env.sh - - echo "" >> /opt/mcac-agent/config/metric-collector.yaml - echo "data_dir_max_size_in_mb: 100" >> /opt/mcac-agent/config/metric-collector.yaml - fi - - if ! grep -qxF "JVM_OPTS=\"\$JVM_OPTS -javaagent:/etc/cassandra/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar\"" < /etc/cassandra/cassandra-env.sh ; then - # ensure newline at end of file - echo "" >> /etc/cassandra/cassandra-env.sh - echo "JVM_OPTS=\"\$JVM_OPTS -javaagent:/etc/cassandra/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar\"" >> /etc/cassandra/cassandra-env.sh - fi + echo "Starting Management API" + + # Copy over any config files mounted at /config + # cp /config/cassandra.yaml /etc/cassandra/cassandra.yaml + if [ -d "/config" ] && ! [ "/config" -ef "$CASSANDRA_CONF" ]; then + cp -R /config/* "${CASSANDRA_CONF:-/etc/cassandra}" + fi + + # Make sure the management api agent jar is set + # We do this here for the following reasons: + # 1. configbuilder will overwrite the cassandra-env-sh, so we don't want to set this after + # 2. We don't wan't operator or configbuilder to care so much about the version number or + # the fact this jar even exists. + + if _metrics_collector_supported && ! grep -qxF "JVM_OPTS=\"\$JVM_OPTS -javaagent:${MCAC_PATH}/lib/datastax-mcac-agent.jar\"" < ${CASSANDRA_CONF}/cassandra-env.sh ; then + # ensure newline at end of file + echo "" >> ${CASSANDRA_CONF}/cassandra-env.sh + echo "JVM_OPTS=\"\$JVM_OPTS -javaagent:${MCAC_PATH}/lib/datastax-mcac-agent.jar\"" >> ${CASSANDRA_CONF}/cassandra-env.sh + echo "" >> ${MCAC_PATH}/config/metric-collector.yaml + echo "data_dir_max_size_in_mb: 100" >> ${MCAC_PATH}/config/metric-collector.yaml + fi + + if ! grep -qxF "JVM_OPTS=\"\$JVM_OPTS -javaagent:${MAAC_PATH}/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar\"" < ${CASSANDRA_CONF}/cassandra-env.sh ; then + # ensure newline at end of file + echo "" >> ${CASSANDRA_CONF}/cassandra-env.sh + echo "JVM_OPTS=\"\$JVM_OPTS -javaagent:${MAAC_PATH}/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar\"" >> ${CASSANDRA_CONF}/cassandra-env.sh + fi # Set this if you want to ignore default env variables, i.e. when running inside an operator if [ $IGNORE_DEFAULTS ] || [ $USE_MGMT_API ]; then @@ -134,57 +133,53 @@ if [ "$1" = 'mgmtapi' ]; then -r 's/^('"$rackdc"'=).*/\1 '"$val"'/' fi done - fi - - MGMT_API_ARGS="" - - # Hardcoding these for now - MGMT_API_CASSANDRA_SOCKET="--cassandra-socket /tmp/cassandra.sock" - MGMT_API_LISTEN_TCP="--host tcp://0.0.0.0:8080" - MGMT_API_LISTEN_SOCKET="--host file:///tmp/oss-mgmt.sock" - - MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_CASSANDRA_SOCKET $MGMT_API_LISTEN_TCP $MGMT_API_LISTEN_SOCKET" - - # These will generally come from the k8s operator - if [ ! -z "$MGMT_API_EXPLICIT_START" ]; then - MGMT_API_EXPLICIT_START="--explicit-start $MGMT_API_EXPLICIT_START" - MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_EXPLICIT_START" - fi - - if [ ! -z "$MGMT_API_TLS_CA_CERT_FILE" ]; then - MGMT_API_TLS_CA_CERT_FILE="--tlscacert $MGMT_API_TLS_CA_CERT_FILE" - MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_TLS_CA_CERT_FILE" - fi - if [ ! -z "$MGMT_API_TLS_CERT_FILE" ]; then - MGMT_API_TLS_CERT_FILE="--tlscert $MGMT_API_TLS_CERT_FILE" - MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_TLS_CERT_FILE" - fi - if [ ! -z "$MGMT_API_TLS_KEY_FILE" ]; then - MGMT_API_TLS_KEY_FILE="--tlskey $MGMT_API_TLS_KEY_FILE" - MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_TLS_KEY_FILE" - fi - - if [ ! -z "$MGMT_API_PID_FILE" ]; then - MGMT_API_PID_FILE="--pidfile $MGMT_API_PID_FILE" - MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_PID_FILE" - fi - - MGMT_API_CASSANDRA_HOME="--cassandra-home /var/lib/cassandra/" - MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_CASSANDRA_HOME" - - if [ ! -z "$MGMT_API_NO_KEEP_ALIVE" ]; then - MGMT_API_NO_KEEP_ALIVE="--no-keep-alive $MGMT_API_NO_KEEP_ALIVE" - MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_NO_KEEP_ALIVE" - fi - - cp /tmp/datastax-mgmtapi-common-0.1.0-SNAPSHOT.jar /etc/cassandra - cp /tmp/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar /etc/cassandra - - MGMT_API_JAR="$(find "/opt/mgmtapi" -name *server*.jar)" - - echo "Running" java ${MGMT_API_JAVA_OPTS} -Xms128m -Xmx128m -jar "$MGMT_API_JAR" $MGMT_API_ARGS - exec /tini -g -- gosu cassandra java ${MGMT_API_JAVA_OPTS} -Xms128m -Xmx128m -jar "$MGMT_API_JAR" $MGMT_API_ARGS - + fi + + MGMT_API_ARGS="" + + # Hardcoding these for now + MGMT_API_CASSANDRA_SOCKET="--cassandra-socket /tmp/cassandra.sock" + MGMT_API_LISTEN_TCP="--host tcp://0.0.0.0:8080" + MGMT_API_LISTEN_SOCKET="--host file:///tmp/oss-mgmt.sock" + + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_CASSANDRA_SOCKET $MGMT_API_LISTEN_TCP $MGMT_API_LISTEN_SOCKET" + + # These will generally come from the k8s operator + if [ ! -z "$MGMT_API_EXPLICIT_START" ]; then + MGMT_API_EXPLICIT_START="--explicit-start $MGMT_API_EXPLICIT_START" + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_EXPLICIT_START" + fi + + if [ ! -z "$MGMT_API_TLS_CA_CERT_FILE" ]; then + MGMT_API_TLS_CA_CERT_FILE="--tlscacert $MGMT_API_TLS_CA_CERT_FILE" + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_TLS_CA_CERT_FILE" + fi + if [ ! -z "$MGMT_API_TLS_CERT_FILE" ]; then + MGMT_API_TLS_CERT_FILE="--tlscert $MGMT_API_TLS_CERT_FILE" + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_TLS_CERT_FILE" + fi + if [ ! -z "$MGMT_API_TLS_KEY_FILE" ]; then + MGMT_API_TLS_KEY_FILE="--tlskey $MGMT_API_TLS_KEY_FILE" + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_TLS_KEY_FILE" + fi + + if [ ! -z "$MGMT_API_PID_FILE" ]; then + MGMT_API_PID_FILE="--pidfile $MGMT_API_PID_FILE" + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_PID_FILE" + fi + + MGMT_API_CASSANDRA_HOME="--cassandra-home /var/lib/cassandra/" + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_CASSANDRA_HOME" + + if [ ! -z "$MGMT_API_NO_KEEP_ALIVE" ]; then + MGMT_API_NO_KEEP_ALIVE="--no-keep-alive $MGMT_API_NO_KEEP_ALIVE" + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_NO_KEEP_ALIVE" + fi + + MGMT_API_JAR="$(find "${MAAC_PATH}" -name *server*.jar)" + + echo "Running" java ${MGMT_API_JAVA_OPTS} -Xms128m -Xmx128m -jar "$MGMT_API_JAR" $MGMT_API_ARGS + java ${MGMT_API_JAVA_OPTS} -Xms128m -Xmx128m -jar "$MGMT_API_JAR" $MGMT_API_ARGS fi exec "$@" diff --git a/scripts/dse-6.8-docker-entrypoint.sh b/scripts/dse-6.8-docker-entrypoint.sh new file mode 100755 index 00000000..3d2f216a --- /dev/null +++ b/scripts/dse-6.8-docker-entrypoint.sh @@ -0,0 +1,154 @@ +#!/bin/bash +set -e + +# first arg is `-f` or `--some-option` +# or there are no args +if [ "$#" -eq 0 ] || [ "${1#-}" != "$1" ]; then + set -- dse cassandra -f "$@" +fi + +if [ "$CASSANDRA_CONF" == "" ]; then + export CASSANDRA_CONF=/opt/dse/resources/cassandra/conf +fi + +# allow the container to be started with `--user` +if [ "$1" = 'mgmtapi' -a "$(id -u)" = '0' ]; then + find "$CASSANDRA_CONF" /var/lib/cassandra /var/log/cassandra \ + \! -user dse -exec chown dse '{}' + +fi + +_ip_address() { + # scrape the first non-localhost IP address of the container + # in Swarm Mode, we often get two IPs -- the container IP, and the (shared) VIP, and the container IP should always be first + ip address | awk ' + $1 == "inet" && $NF != "lo" { + gsub(/\/.+$/, "", $2) + print $2 + exit + } + ' +} + +# "sed -i", but without "mv" (which doesn't work on a bind-mounted file, for example) +_sed-in-place() { + local filename="$1"; shift + local tempFile + tempFile="$(mktemp)" + sed "$@" "$filename" > "$tempFile" + cat "$tempFile" > "$filename" + rm "$tempFile" +} + +if [ "$1" = 'mgmtapi' ]; then + echo "Starting Management API" + + if ! grep -qxF "JVM_OPTS=\"\$JVM_OPTS -javaagent:${MAAC_PATH}/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar\"" < ${CASSANDRA_CONF}/cassandra-env.sh ; then + # ensure newline at end of file + echo "" >> ${CASSANDRA_CONF}/cassandra-env.sh + echo "JVM_OPTS=\"\$JVM_OPTS -javaagent:${MAAC_PATH}/datastax-mgmtapi-agent-0.1.0-SNAPSHOT.jar\"" >> ${CASSANDRA_CONF}/cassandra-env.sh + fi + + CASSANDRA_NATIVE_TRANSPORT_ADDRESS='0.0.0.0' + CASSANDRA_NATIVE_TRANSPORT_BROADCAST_ADDRESS="$(_ip_address)" + + : ${CASSANDRA_LISTEN_ADDRESS='auto'} + if [ "$CASSANDRA_LISTEN_ADDRESS" = 'auto' ]; then + CASSANDRA_LISTEN_ADDRESS="$(_ip_address)" + fi + + : ${CASSANDRA_BROADCAST_ADDRESS="$CASSANDRA_LISTEN_ADDRESS"} + + if [ "$CASSANDRA_BROADCAST_ADDRESS" = 'auto' ]; then + CASSANDRA_BROADCAST_ADDRESS="$(_ip_address)" + fi + : ${CASSANDRA_BROADCAST_RPC_ADDRESS:=$CASSANDRA_BROADCAST_ADDRESS} + + if [ -n "${CASSANDRA_NAME:+1}" ]; then + : ${CASSANDRA_SEEDS:="cassandra"} + fi + : ${CASSANDRA_SEEDS:="$CASSANDRA_BROADCAST_ADDRESS"} + + CASSANDRA_YAML="cassandra.yaml" + if [ $CASSANDRA_DEPLOYMENT ]; then + CASSANDRA_DEPLOYMENT=`echo "$CASSANDRA_DEPLOYMENT" | awk '{print tolower($0)}'` + CASSANDRA_YAML="cassandra-$CASSANDRA_DEPLOYMENT.yaml" + fi + + _sed-in-place "$CASSANDRA_CONF/$CASSANDRA_YAML" \ + -r 's/(- seeds:).*/\1 "'"$CASSANDRA_SEEDS"'"/' + + for yaml in \ + native_transport_broadcast_address \ + native_transport_address \ + cluster_name \ + endpoint_snitch \ + listen_address \ + num_tokens \ + ; do + var="CASSANDRA_${yaml^^}" + val="${!var}" + if [ "$val" ]; then + _sed-in-place "$CASSANDRA_CONF/$CASSANDRA_YAML" \ + -r 's/^(# )?('"$yaml"':).*/\2 '"$val"'/' + fi + done + + for rackdc in dc rack; do + var="CASSANDRA_${rackdc^^}" + val="${!var}" + if [ "$val" ]; then + _sed-in-place "$CASSANDRA_CONF/cassandra-rackdc.properties" \ + -r 's/^('"$rackdc"'=).*/\1 '"$val"'/' + fi + done +fi + + MGMT_API_ARGS="" + + # Hardcoding these for now + MGMT_API_CASSANDRA_SOCKET="--db-socket /tmp/db.sock" + MGMT_API_LISTEN_TCP="--host tcp://0.0.0.0:8080" + MGMT_API_LISTEN_SOCKET="--host file:///tmp/oss-mgmt.sock" + + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_CASSANDRA_SOCKET $MGMT_API_LISTEN_TCP $MGMT_API_LISTEN_SOCKET" + + # These will generally come from the k8s operator + if [ ! -z "$MGMT_API_EXPLICIT_START" ]; then + MGMT_API_EXPLICIT_START="--explicit-start $MGMT_API_EXPLICIT_START" + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_EXPLICIT_START" + fi + + if [ ! -z "$MGMT_API_TLS_CA_CERT_FILE" ]; then + MGMT_API_TLS_CA_CERT_FILE="--tlscacert $MGMT_API_TLS_CA_CERT_FILE" + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_TLS_CA_CERT_FILE" + fi + if [ ! -z "$MGMT_API_TLS_CERT_FILE" ]; then + MGMT_API_TLS_CERT_FILE="--tlscert $MGMT_API_TLS_CERT_FILE" + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_TLS_CERT_FILE" + fi + if [ ! -z "$MGMT_API_TLS_KEY_FILE" ]; then + MGMT_API_TLS_KEY_FILE="--tlskey $MGMT_API_TLS_KEY_FILE" + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_TLS_KEY_FILE" + fi + + if [ ! -z "$MGMT_API_PID_FILE" ]; then + MGMT_API_PID_FILE="--pidfile $MGMT_API_PID_FILE" + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_PID_FILE" + fi + + MGMT_API_DSE_HOME="--db-home ${DSE_HOME}" + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_DSE_HOME" + + if [ ! -z "$MGMT_API_NO_KEEP_ALIVE" ]; then + MGMT_API_NO_KEEP_ALIVE="--no-keep-alive $MGMT_API_NO_KEEP_ALIVE" + MGMT_API_ARGS="$MGMT_API_ARGS $MGMT_API_NO_KEEP_ALIVE" + fi + + MGMT_API_JAR="$(find "${MAAC_PATH}" -name *server*.jar)" + + echo "Running" java ${MGMT_API_JAVA_OPTS} -Xms128m -Xmx128m -jar "$MGMT_API_JAR" $MGMT_API_ARGS + exec java ${MGMT_API_JAVA_OPTS} -Xms128m -Xmx128m -jar "$MGMT_API_JAR" $MGMT_API_ARGS + +fi + +exec "$@"