This is an automated email from the ASF dual-hosted git repository. klund pushed a commit to branch support/1.13 in repository https://gitbox.apache.org/repos/asf/geode.git
The following commit(s) were added to refs/heads/support/1.13 by this push: new ad4db34 GEODE-9064: Configure serial filter during ManagementAgent start (#6196) (#6338) ad4db34 is described below commit ad4db347e3ef3ec13d722c4b330cce448feb7e4d Author: Kirk Lund <kl...@apache.org> AuthorDate: Mon Apr 26 09:47:07 2021 -0700 GEODE-9064: Configure serial filter during ManagementAgent start (#6196) (#6338) The Geode JMX layer will configure the system property “jmx.remote.rmi.server.serial.filter.pattern" to only accept JDK classes identified as open-types required for JMX. If the system property already has a value then Geode will log a statement and leave the system property alone. (cherry picked from commit 8b2ee6d99ab16a11a129ccd3d4f3cc517997e8c8) --- ...gerConfiguresJmxSerialFilterAcceptanceTest.java | 115 +++++++ ...gerConfiguresJmxSerialFilterAcceptanceTest.java | 111 +++++++ ...erConfiguresJmxSerialFilterIntegrationTest.java | 204 ++++++++++++ ...erConfiguresJmxSerialFilterIntegrationTest.java | 201 ++++++++++++ .../internal/JmxRmiOpenTypesSerialFilter.java | 148 +++++++++ .../management/internal/JmxRmiSerialFilter.java | 21 ++ .../geode/management/internal/ManagementAgent.java | 7 +- .../internal/ManagementAgentFactory.java | 25 ++ .../internal/SystemManagementService.java | 25 +- .../internal/JmxRmiOpenTypesSerialFilterTest.java | 351 +++++++++++++++++++++ .../internal/SystemManagementServiceTest.java | 6 +- geode-junit/build.gradle | 1 + .../geode/test/junit/rules/CloseableReference.java | 209 ++++++++++++ .../test/junit/rules/CloseableReferenceTest.java | 204 ++++++++++++ 14 files changed, 1612 insertions(+), 16 deletions(-) diff --git a/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/LocatorManagerConfiguresJmxSerialFilterAcceptanceTest.java b/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/LocatorManagerConfiguresJmxSerialFilterAcceptanceTest.java new file mode 100644 index 0000000..de0b8c8 --- /dev/null +++ b/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/LocatorManagerConfiguresJmxSerialFilterAcceptanceTest.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.management; + +import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast; +import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost; +import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPorts; +import static org.apache.geode.management.internal.JmxRmiOpenTypesSerialFilter.PROPERTY_NAME; +import static org.apache.geode.test.awaitility.GeodeAwaitility.await; +import static org.assertj.core.api.Assumptions.assumeThat; + +import java.nio.file.Path; + +import org.apache.commons.lang3.JavaVersion; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import org.apache.geode.test.assertj.LogFileAssert; +import org.apache.geode.test.junit.rules.RequiresGeodeHome; +import org.apache.geode.test.junit.rules.gfsh.GfshRule; + +public class LocatorManagerConfiguresJmxSerialFilterAcceptanceTest { + + private static final String NAME = "the-locator"; + + private Path workingDir; + private int locatorPort; + private int jmxPort; + private Path locatorLogFile; + + @Rule + public RequiresGeodeHome requiresGeodeHome = new RequiresGeodeHome(); + @Rule + public GfshRule gfshRule = new GfshRule(); + + @Before + public void setUpOutputFiles() { + TemporaryFolder temporaryFolder = gfshRule.getTemporaryFolder(); + + workingDir = temporaryFolder.getRoot().toPath().toAbsolutePath(); + locatorLogFile = workingDir.resolve(NAME + ".log"); + } + + @Before + public void setUpRandomPorts() { + int[] ports = getRandomAvailableTCPPorts(2); + + locatorPort = ports[0]; + jmxPort = ports[1]; + } + + @Test + public void startingLocatorWithJmxManager_configuresSerialFilter_atLeastJava9() { + assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue(); + + String startLocatorCommand = String.join(" ", + "start locator", + "--name=" + NAME, + "--dir=" + workingDir, + "--port=" + locatorPort, + "--J=-Dgemfire.enable-cluster-configuration=false", + "--J=-Dgemfire.http-service-port=0", + "--J=-Dgemfire.jmx-manager=true", + "--J=-Dgemfire.jmx-manager-port=" + jmxPort, + "--J=-Dgemfire.jmx-manager-start=true"); + + gfshRule.execute(startLocatorCommand); + + await().untilAsserted(() -> { + LogFileAssert.assertThat(locatorLogFile.toFile()) + .as(locatorLogFile.toFile().getAbsolutePath()) + .exists() + .contains("System property " + PROPERTY_NAME + " is now configured with"); + }); + } + + @Test + public void startingLocatorWithJmxManager_configuresSerialFilter_atMostJava8() { + assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue(); + + String startLocatorCommand = String.join(" ", + "start locator", + "--name=" + NAME, + "--dir=" + workingDir, + "--port=" + locatorPort, + "--J=-Dgemfire.enable-cluster-configuration=false", + "--J=-Dgemfire.http-service-port=0", + "--J=-Dgemfire.jmx-manager=true", + "--J=-Dgemfire.jmx-manager-port=" + jmxPort, + "--J=-Dgemfire.jmx-manager-start=true"); + + gfshRule.execute(startLocatorCommand); + + await().untilAsserted(() -> { + LogFileAssert.assertThat(locatorLogFile.toFile()) + .as(locatorLogFile.toFile().getAbsolutePath()) + .exists() + .doesNotContain("System property " + PROPERTY_NAME + " is now configured with"); + }); + } +} diff --git a/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/ServerManagerConfiguresJmxSerialFilterAcceptanceTest.java b/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/ServerManagerConfiguresJmxSerialFilterAcceptanceTest.java new file mode 100644 index 0000000..7754dec --- /dev/null +++ b/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/ServerManagerConfiguresJmxSerialFilterAcceptanceTest.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.management; + +import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast; +import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost; +import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPort; +import static org.apache.geode.management.internal.JmxRmiOpenTypesSerialFilter.PROPERTY_NAME; +import static org.apache.geode.test.awaitility.GeodeAwaitility.await; +import static org.assertj.core.api.Assumptions.assumeThat; + +import java.nio.file.Path; + +import org.apache.commons.lang3.JavaVersion; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import org.apache.geode.test.assertj.LogFileAssert; +import org.apache.geode.test.junit.rules.RequiresGeodeHome; +import org.apache.geode.test.junit.rules.gfsh.GfshRule; + +public class ServerManagerConfiguresJmxSerialFilterAcceptanceTest { + + private static final String NAME = "the-server"; + + private Path workingDir; + private int jmxPort; + private Path serverLogFile; + + @Rule + public RequiresGeodeHome requiresGeodeHome = new RequiresGeodeHome(); + @Rule + public GfshRule gfshRule = new GfshRule(); + + @Before + public void setUpOutputFiles() { + TemporaryFolder temporaryFolder = gfshRule.getTemporaryFolder(); + + workingDir = temporaryFolder.getRoot().toPath().toAbsolutePath(); + serverLogFile = workingDir.resolve(NAME + ".log"); + } + + @Before + public void setUpRandomPorts() { + jmxPort = getRandomAvailableTCPPort(); + } + + @Test + public void startingServerWithJmxManager_configuresSerialFilter_atLeastJava9() { + assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue(); + + String startServerCommand = String.join(" ", + "start server", + "--name=" + NAME, + "--dir=" + workingDir, + "--disable-default-server", + "--J=-Dgemfire.enable-cluster-configuration=false", + "--J=-Dgemfire.http-service-port=0", + "--J=-Dgemfire.jmx-manager=true", + "--J=-Dgemfire.jmx-manager-port=" + jmxPort, + "--J=-Dgemfire.jmx-manager-start=true"); + + gfshRule.execute(startServerCommand); + + await().untilAsserted(() -> { + LogFileAssert.assertThat(serverLogFile.toFile()) + .as(serverLogFile.toFile().getAbsolutePath()) + .exists() + .contains("System property " + PROPERTY_NAME + " is now configured with"); + }); + } + + @Test + public void startingServerWithJmxManager_configuresSerialFilter_atMostJava8() { + assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue(); + + String startServerCommand = String.join(" ", + "start server", + "--name=" + NAME, + "--dir=" + workingDir, + "--disable-default-server", + "--J=-Dgemfire.enable-cluster-configuration=false", + "--J=-Dgemfire.http-service-port=0", + "--J=-Dgemfire.jmx-manager=true", + "--J=-Dgemfire.jmx-manager-port=" + jmxPort, + "--J=-Dgemfire.jmx-manager-start=true"); + + gfshRule.execute(startServerCommand); + + await().untilAsserted(() -> { + LogFileAssert.assertThat(serverLogFile.toFile()) + .as(serverLogFile.toFile().getAbsolutePath()) + .exists() + .doesNotContain("System property " + PROPERTY_NAME + " is now configured with"); + }); + } +} diff --git a/geode-core/src/integrationTest/java/org/apache/geode/management/internal/LocatorManagerConfiguresJmxSerialFilterIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/management/internal/LocatorManagerConfiguresJmxSerialFilterIntegrationTest.java new file mode 100644 index 0000000..47f196f --- /dev/null +++ b/geode-core/src/integrationTest/java/org/apache/geode/management/internal/LocatorManagerConfiguresJmxSerialFilterIntegrationTest.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.management.internal; + +import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast; +import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost; +import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT; +import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER; +import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT; +import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START; +import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE; +import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPorts; +import static org.apache.geode.management.internal.JmxRmiOpenTypesSerialFilter.PROPERTY_NAME; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; + +import java.io.File; + +import org.apache.commons.lang3.JavaVersion; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import org.apache.geode.distributed.LocatorLauncher; +import org.apache.geode.test.junit.rules.CloseableReference; + +public class LocatorManagerConfiguresJmxSerialFilterIntegrationTest { + + private static final String NAME = "locator"; + + private File workingDirectory; + private int locatorPort; + private int jmxPort; + private String expectedSerialFilter; + + @Rule + public CloseableReference<LocatorLauncher> locator = new CloseableReference<>(); + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Before + public void setUp() throws Exception { + workingDirectory = temporaryFolder.newFolder(NAME); + int[] ports = getRandomAvailableTCPPorts(2); + locatorPort = ports[0]; + jmxPort = ports[1]; + expectedSerialFilter = new JmxRmiOpenTypesSerialFilter().createSerialFilterPattern(); + } + + @After + public void tearDown() { + System.clearProperty(PROPERTY_NAME); + } + + @Test + public void startingLocatorWithJmxManager_configuresSerialFilter_atLeastJava9() { + assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue(); + + locator.set(new LocatorLauncher.Builder() + .setMemberName(NAME) + .setPort(locatorPort) + .setWorkingDirectory(workingDirectory.getAbsolutePath()) + .set(HTTP_SERVICE_PORT, "0") + .set(JMX_MANAGER, "true") + .set(JMX_MANAGER_PORT, String.valueOf(jmxPort)) + .set(JMX_MANAGER_START, "true") + .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath()) + .build()) + .get() + .start(); + + String serialFilter = System.getProperty(PROPERTY_NAME); + assertThat(serialFilter).isEqualTo(expectedSerialFilter); + } + + @Test + public void startingLocatorWithJmxManager_changesEmptySerialFilter_atLeastJava9() { + assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue(); + + System.setProperty(PROPERTY_NAME, ""); + + locator.set(new LocatorLauncher.Builder() + .setMemberName(NAME) + .setPort(locatorPort) + .setWorkingDirectory(workingDirectory.getAbsolutePath()) + .set(HTTP_SERVICE_PORT, "0") + .set(JMX_MANAGER, "true") + .set(JMX_MANAGER_PORT, String.valueOf(jmxPort)) + .set(JMX_MANAGER_START, "true") + .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath()) + .build()) + .get() + .start(); + + String serialFilter = System.getProperty(PROPERTY_NAME); + assertThat(serialFilter).isEqualTo(expectedSerialFilter); + } + + @Test + public void startingLocatorWithJmxManager_skipsNonEmptySerialFilter_atLeastJava9() { + assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue(); + + String existingSerialFilter = "!*"; + System.setProperty(PROPERTY_NAME, existingSerialFilter); + + locator.set(new LocatorLauncher.Builder() + .setMemberName(NAME) + .setPort(locatorPort) + .setWorkingDirectory(workingDirectory.getAbsolutePath()) + .set(HTTP_SERVICE_PORT, "0") + .set(JMX_MANAGER, "true") + .set(JMX_MANAGER_PORT, String.valueOf(jmxPort)) + .set(JMX_MANAGER_START, "true") + .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath()) + .build()) + .get() + .start(); + + String serialFilter = System.getProperty(PROPERTY_NAME); + assertThat(serialFilter).isEqualTo(existingSerialFilter); + } + + @Test + public void startingLocatorWithJmxManager_skipsSerialFilter_atMostJava8() { + assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue(); + + locator.set(new LocatorLauncher.Builder() + .setMemberName(NAME) + .setPort(locatorPort) + .setWorkingDirectory(workingDirectory.getAbsolutePath()) + .set(HTTP_SERVICE_PORT, "0") + .set(JMX_MANAGER, "true") + .set(JMX_MANAGER_PORT, String.valueOf(jmxPort)) + .set(JMX_MANAGER_START, "true") + .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath()) + .build()) + .get() + .start(); + + String serialFilter = System.getProperty(PROPERTY_NAME); + assertThat(serialFilter).isNull(); + } + + @Test + public void startingLocatorWithJmxManager_skipsEmptySerialFilter_atMostJava8() { + assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue(); + + System.setProperty(PROPERTY_NAME, ""); + + locator.set(new LocatorLauncher.Builder() + .setMemberName(NAME) + .setPort(locatorPort) + .setWorkingDirectory(workingDirectory.getAbsolutePath()) + .set(HTTP_SERVICE_PORT, "0") + .set(JMX_MANAGER, "true") + .set(JMX_MANAGER_PORT, String.valueOf(jmxPort)) + .set(JMX_MANAGER_START, "true") + .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath()) + .build()) + .get() + .start(); + + String serialFilter = System.getProperty(PROPERTY_NAME); + assertThat(serialFilter).isEqualTo(""); + } + + @Test + public void startingLocatorWithJmxManager_skipsNonEmptySerialFilter_atMostJava8() { + assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue(); + + String existingSerialFilter = "!*"; + System.setProperty(PROPERTY_NAME, existingSerialFilter); + + locator.set(new LocatorLauncher.Builder() + .setMemberName(NAME) + .setPort(locatorPort) + .setWorkingDirectory(workingDirectory.getAbsolutePath()) + .set(HTTP_SERVICE_PORT, "0") + .set(JMX_MANAGER, "true") + .set(JMX_MANAGER_PORT, String.valueOf(jmxPort)) + .set(JMX_MANAGER_START, "true") + .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath()) + .build()) + .get() + .start(); + + String serialFilter = System.getProperty(PROPERTY_NAME); + assertThat(serialFilter).isEqualTo(existingSerialFilter); + } +} diff --git a/geode-core/src/integrationTest/java/org/apache/geode/management/internal/ServerManagerConfiguresJmxSerialFilterIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/management/internal/ServerManagerConfiguresJmxSerialFilterIntegrationTest.java new file mode 100644 index 0000000..f77438e --- /dev/null +++ b/geode-core/src/integrationTest/java/org/apache/geode/management/internal/ServerManagerConfiguresJmxSerialFilterIntegrationTest.java @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.management.internal; + +import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast; +import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost; +import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT; +import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER; +import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT; +import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START; +import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE; +import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPort; +import static org.apache.geode.management.internal.JmxRmiOpenTypesSerialFilter.PROPERTY_NAME; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; + +import java.io.File; + +import org.apache.commons.lang3.JavaVersion; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import org.apache.geode.distributed.ServerLauncher; +import org.apache.geode.test.junit.rules.CloseableReference; + +public class ServerManagerConfiguresJmxSerialFilterIntegrationTest { + + private static final String NAME = "server"; + + private File workingDirectory; + private int jmxPort; + private String expectedSerialFilter; + + @Rule + public CloseableReference<ServerLauncher> server = new CloseableReference<>(); + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Before + public void setUp() throws Exception { + workingDirectory = temporaryFolder.newFolder(NAME); + jmxPort = getRandomAvailableTCPPort(); + expectedSerialFilter = new JmxRmiOpenTypesSerialFilter().createSerialFilterPattern(); + } + + @After + public void tearDown() { + System.clearProperty(PROPERTY_NAME); + } + + @Test + public void startingServerWithJmxManager_configuresSerialFilter_atLeastJava9() { + assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue(); + + server.set(new ServerLauncher.Builder() + .setMemberName(NAME) + .setDisableDefaultServer(true) + .setWorkingDirectory(workingDirectory.getAbsolutePath()) + .set(HTTP_SERVICE_PORT, "0") + .set(JMX_MANAGER, "true") + .set(JMX_MANAGER_PORT, String.valueOf(jmxPort)) + .set(JMX_MANAGER_START, "true") + .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath()) + .build()) + .get() + .start(); + + String serialFilter = System.getProperty(PROPERTY_NAME); + assertThat(serialFilter).isEqualTo(expectedSerialFilter); + } + + @Test + public void startingServerWithJmxManager_changesEmptySerialFilter_atLeastJava9() { + assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue(); + + System.setProperty(PROPERTY_NAME, ""); + + server.set(new ServerLauncher.Builder() + .setMemberName(NAME) + .setDisableDefaultServer(true) + .setWorkingDirectory(workingDirectory.getAbsolutePath()) + .set(HTTP_SERVICE_PORT, "0") + .set(JMX_MANAGER, "true") + .set(JMX_MANAGER_PORT, String.valueOf(jmxPort)) + .set(JMX_MANAGER_START, "true") + .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath()) + .build()) + .get() + .start(); + + String serialFilter = System.getProperty(PROPERTY_NAME); + assertThat(serialFilter).isEqualTo(expectedSerialFilter); + } + + @Test + public void startingServerWithJmxManager_skipsNonEmptySerialFilter_atLeastJava9() { + assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue(); + + String existingSerialFilter = "!*"; + System.setProperty(PROPERTY_NAME, existingSerialFilter); + + server.set(new ServerLauncher.Builder() + .setMemberName(NAME) + .setDisableDefaultServer(true) + .setWorkingDirectory(workingDirectory.getAbsolutePath()) + .set(HTTP_SERVICE_PORT, "0") + .set(JMX_MANAGER, "true") + .set(JMX_MANAGER_PORT, String.valueOf(jmxPort)) + .set(JMX_MANAGER_START, "true") + .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath()) + .build()) + .get() + .start(); + + String serialFilter = System.getProperty(PROPERTY_NAME); + assertThat(serialFilter).isEqualTo(existingSerialFilter); + } + + @Test + public void startingServerWithJmxManager_skipsSerialFilter_atMostJava8() { + assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue(); + + server.set(new ServerLauncher.Builder() + .setMemberName(NAME) + .setDisableDefaultServer(true) + .setWorkingDirectory(workingDirectory.getAbsolutePath()) + .set(HTTP_SERVICE_PORT, "0") + .set(JMX_MANAGER, "true") + .set(JMX_MANAGER_PORT, String.valueOf(jmxPort)) + .set(JMX_MANAGER_START, "true") + .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath()) + .build()) + .get() + .start(); + + String serialFilter = System.getProperty(PROPERTY_NAME); + assertThat(serialFilter).isNull(); + } + + @Test + public void startingServerWithJmxManager_skipsEmptySerialFilter_atMostJava8() { + assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue(); + + System.setProperty(PROPERTY_NAME, ""); + + server.set(new ServerLauncher.Builder() + .setMemberName(NAME) + .setDisableDefaultServer(true) + .setWorkingDirectory(workingDirectory.getAbsolutePath()) + .set(HTTP_SERVICE_PORT, "0") + .set(JMX_MANAGER, "true") + .set(JMX_MANAGER_PORT, String.valueOf(jmxPort)) + .set(JMX_MANAGER_START, "true") + .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath()) + .build()) + .get() + .start(); + + String serialFilter = System.getProperty(PROPERTY_NAME); + assertThat(serialFilter).isEqualTo(""); + } + + @Test + public void startingServerWithJmxManager_skipsNonEmptySerialFilter_atMostJava8() { + assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue(); + + String existingSerialFilter = "!*"; + System.setProperty(PROPERTY_NAME, existingSerialFilter); + + server.set(new ServerLauncher.Builder() + .setMemberName(NAME) + .setDisableDefaultServer(true) + .setWorkingDirectory(workingDirectory.getAbsolutePath()) + .set(HTTP_SERVICE_PORT, "0") + .set(JMX_MANAGER, "true") + .set(JMX_MANAGER_PORT, String.valueOf(jmxPort)) + .set(JMX_MANAGER_START, "true") + .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath()) + .build()) + .get() + .start(); + + String serialFilter = System.getProperty(PROPERTY_NAME); + assertThat(serialFilter).isEqualTo(existingSerialFilter); + } +} diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiOpenTypesSerialFilter.java b/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiOpenTypesSerialFilter.java new file mode 100644 index 0000000..803bc03 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiOpenTypesSerialFilter.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.management.internal; + +import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.rmi.MarshalledObject; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import javax.management.ObjectName; +import javax.management.openmbean.ArrayType; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularType; + +import org.apache.commons.lang3.JavaVersion; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; + +import org.apache.geode.annotations.VisibleForTesting; +import org.apache.geode.logging.internal.log4j.api.LogService; + +/** + * Configure the “jmx.remote.rmi.server.serial.filter.pattern” system property if Java version is + * greater than Java 8. The serial pattern will be configured to accept only standard JMX + * open-types. If the system property already has a non-null value, then leave it as is. + */ +public class JmxRmiOpenTypesSerialFilter implements JmxRmiSerialFilter { + + private static final Logger logger = LogService.getLogger(); + + @VisibleForTesting + public static final String PROPERTY_NAME = "jmx.remote.rmi.server.serial.filter.pattern"; + + private final Consumer<String> infoLogger; + private final Supplier<Boolean> supportsSerialFilter; + + JmxRmiOpenTypesSerialFilter() { + this(logger::info, () -> isJavaVersionAtLeast(JavaVersion.JAVA_9)); + } + + @VisibleForTesting + JmxRmiOpenTypesSerialFilter(Consumer<String> infoLogger, Supplier<Boolean> supportsSerialFilter) { + this.infoLogger = infoLogger; + this.supportsSerialFilter = supportsSerialFilter; + } + + @Override + public void configureSerialFilter() { + if (supportsDedicatedSerialFilter()) { + setPropertyValueUnlessExists(createSerialFilterPattern()); + } + } + + @VisibleForTesting + boolean supportsDedicatedSerialFilter() { + return supportsSerialFilter.get(); + } + + /** + * Sets the value of the system property {@code jmx.remote.rmi.server.serial.filter.pattern} + * unless it exists with a value that is not null or empty (""). + */ + @VisibleForTesting + void setPropertyValueUnlessExists(String value) { + String existingValue = System.getProperty(PROPERTY_NAME); + if (StringUtils.isNotEmpty(existingValue)) { + infoLogger.accept("System property " + PROPERTY_NAME + " is already configured."); + return; + } + + System.setProperty(PROPERTY_NAME, value); + infoLogger.accept("System property " + PROPERTY_NAME + " is now configured with '" + + value + "'."); + } + + /** + * Returns a serial filter pattern that accepts all open MBean data types and rejects everything + * not included in the pattern. + */ + @VisibleForTesting + String createSerialFilterPattern() { + // note: java.util.* may also be needed + return new StringBuilder() + // accept all open MBean data types + .append(Boolean.class.getName()) + .append(';') + .append(Byte.class.getName()) + .append(';') + .append(Character.class.getName()) + .append(';') + .append(Short.class.getName()) + .append(';') + .append(Integer.class.getName()) + .append(';') + .append(Long.class.getName()) + .append(';') + .append(Float.class.getName()) + .append(';') + .append(Double.class.getName()) + .append(';') + .append(String.class.getName()) + .append(';') + .append(BigInteger.class.getName()) + .append(';') + .append(BigDecimal.class.getName()) + .append(';') + .append(ObjectName.class.getName()) + .append(';') + .append(OpenType.class.getName()) + .append(';') + .append(CompositeData.class.getName()) + .append(';') + .append(TabularData.class.getName()) + .append(';') + .append(SimpleType.class.getName()) + .append(';') + .append(CompositeType.class.getName()) + .append(';') + .append(TabularType.class.getName()) + .append(';') + .append(ArrayType.class.getName()) + .append(';') + .append(MarshalledObject.class.getName()) + .append(';') + // reject all other classes + .append("!*") + .toString(); + } +} diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java b/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java new file mode 100644 index 0000000..4a187e0 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.management.internal; + +@FunctionalInterface +public interface JmxRmiSerialFilter { + + void configureSerialFilter(); +} diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java b/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java index 6d721ab..3ad1102 100755 --- a/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java +++ b/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java @@ -108,6 +108,8 @@ public class ManagementAgent { private int port; private RemoteStreamExporter remoteStreamExporter = null; + private final JmxRmiSerialFilter serialFilter; + /** * This system property is set to true when the embedded HTTP server is started so that the * embedded pulse webapp can use a local MBeanServer instead of a remote JMX connection. @@ -118,10 +120,12 @@ public class ManagementAgent { private static final String PULSE_USESSL_MANAGER = "pulse.useSSL.manager"; private static final String PULSE_USESSL_LOCATOR = "pulse.useSSL.locator"; - public ManagementAgent(DistributionConfig config, InternalCache cache) { + public ManagementAgent(DistributionConfig config, InternalCache cache, + JmxRmiSerialFilter serialFilter) { this.config = config; this.cache = cache; this.securityService = cache.getSecurityService(); + this.serialFilter = serialFilter; } public synchronized boolean isRunning() { @@ -130,6 +134,7 @@ public class ManagementAgent { public synchronized void startAgent() { + serialFilter.configureSerialFilter(); loadWebApplications(); if (!this.running && this.config.getJmxManagerPort() != 0) { diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgentFactory.java b/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgentFactory.java new file mode 100644 index 0000000..bff205e --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgentFactory.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.management.internal; + +import org.apache.geode.distributed.internal.DistributionConfig; +import org.apache.geode.internal.cache.InternalCache; + +@FunctionalInterface +interface ManagementAgentFactory { + + ManagementAgent create(DistributionConfig config, InternalCache cache, + JmxRmiSerialFilter serialFilter); +} diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/SystemManagementService.java b/geode-core/src/main/java/org/apache/geode/management/internal/SystemManagementService.java index 2e9403e..a5bf2f4 100755 --- a/geode-core/src/main/java/org/apache/geode/management/internal/SystemManagementService.java +++ b/geode-core/src/main/java/org/apache/geode/management/internal/SystemManagementService.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; -import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; @@ -41,7 +40,6 @@ import org.apache.geode.annotations.VisibleForTesting; import org.apache.geode.cache.execute.FunctionService; import org.apache.geode.distributed.DistributedMember; import org.apache.geode.distributed.DistributedSystemDisconnectedException; -import org.apache.geode.distributed.internal.DistributionConfig; import org.apache.geode.distributed.internal.InternalDistributedSystem; import org.apache.geode.distributed.internal.ResourceEvent; import org.apache.geode.distributed.internal.membership.InternalDistributedMember; @@ -115,7 +113,7 @@ public class SystemManagementService extends BaseManagementService { private final StatisticsFactory statisticsFactory; private final StatisticsClock statisticsClock; private final FederatingManagerFactory federatingManagerFactory; - + private final Function<SystemManagementService, LocalManager> localManagerFactory; /** * whether the service is closed or not if cache is closed automatically this service will be @@ -137,30 +135,34 @@ public class SystemManagementService extends BaseManagementService { * Managing node. */ private ManagementMembershipListener listener; - private final Function<SystemManagementService, LocalManager> localManagerFactory; static BaseManagementService newSystemManagementService( InternalCacheForClientAccess cache) { - return newSystemManagementService(cache, NotificationHub::new, + return newSystemManagementService( + cache, + NotificationHub::new, SystemManagementService::createLocalManager, - createFederatingManagerFactory(), ManagementAgent::new); + createFederatingManagerFactory(), + ManagementAgent::new); } @VisibleForTesting - static BaseManagementService newSystemManagementService(InternalCacheForClientAccess cache, + static BaseManagementService newSystemManagementService( + InternalCacheForClientAccess cache, Function<ManagementResourceRepo, NotificationHub> notificationHubFactory, Function<SystemManagementService, LocalManager> localManagerFactory, FederatingManagerFactory federatingManagerFactory, - BiFunction<DistributionConfig, InternalCacheForClientAccess, ManagementAgent> managementAgentFactory) { + ManagementAgentFactory managementAgentFactory) { return new SystemManagementService(cache, notificationHubFactory, localManagerFactory, federatingManagerFactory, managementAgentFactory).init(); } - private SystemManagementService(InternalCacheForClientAccess cache, + private SystemManagementService( + InternalCacheForClientAccess cache, Function<ManagementResourceRepo, NotificationHub> notificationHubFactory, Function<SystemManagementService, LocalManager> localManagerFactory, FederatingManagerFactory federatingManagerFactory, - BiFunction<DistributionConfig, InternalCacheForClientAccess, ManagementAgent> managementAgentFactory) { + ManagementAgentFactory managementAgentFactory) { this.cache = cache; system = cache.getInternalDistributedSystem(); this.localManagerFactory = localManagerFactory; @@ -177,7 +179,8 @@ public class SystemManagementService extends BaseManagementService { notificationHub = notificationHubFactory.apply(repo); if (system.getConfig().getJmxManager()) { - agent = managementAgentFactory.apply(system.getConfig(), cache); + agent = managementAgentFactory.create(system.getConfig(), cache, + new JmxRmiOpenTypesSerialFilter()); } else { agent = null; } diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/JmxRmiOpenTypesSerialFilterTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/JmxRmiOpenTypesSerialFilterTest.java new file mode 100644 index 0000000..c8f8ea9 --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/management/internal/JmxRmiOpenTypesSerialFilterTest.java @@ -0,0 +1,351 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.management.internal; + +import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast; +import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost; +import static org.apache.geode.util.internal.UncheckedUtils.uncheckedCast; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.rmi.MarshalledObject; +import java.util.function.Consumer; + +import javax.management.ObjectName; +import javax.management.openmbean.ArrayType; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularType; + +import org.apache.commons.lang3.JavaVersion; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.RestoreSystemProperties; + +public class JmxRmiOpenTypesSerialFilterTest { + + private Consumer<String> infoLogger; + + @Rule + public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); + + @Before + public void setUp() { + infoLogger = uncheckedCast(mock(Consumer.class)); + } + + @Test + public void propertyValueIsNullByDefault() { + String propertyValue = System.getProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME); + + assertThat(propertyValue).isNull(); + } + + @Test + public void supportsDedicatedSerialFilter_returnsTrue_atLeastJava9() { + assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue(); + + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + boolean result = jmxRmiOpenTypesSerialFilter.supportsDedicatedSerialFilter(); + + assertThat(result).isTrue(); + } + + @Test + public void supportsDedicatedSerialFilter_returnsFalse_atMostJava8() { + assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue(); + + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + boolean result = jmxRmiOpenTypesSerialFilter.supportsDedicatedSerialFilter(); + + assertThat(result).isFalse(); + } + + @Test + public void setPropertyValue_setsValue_ifExistingValueIsNull() { + String value = "value-of-property"; + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + jmxRmiOpenTypesSerialFilter.setPropertyValueUnlessExists(value); + + String propertyValue = System.getProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME); + assertThat(propertyValue).isEqualTo(value); + } + + @Test + public void setPropertyValue_setsValue_ifExistingValueIsEmpty() { + System.setProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME, ""); + String value = "value-of-property"; + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + jmxRmiOpenTypesSerialFilter.setPropertyValueUnlessExists(value); + + String propertyValue = System.getProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME); + assertThat(propertyValue).isEqualTo(value); + } + + @Test + public void setPropertyValue_logsMessage_ifExistingValueIsEmpty() { + System.setProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME, ""); + String value = "value-of-property"; + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = + new JmxRmiOpenTypesSerialFilter(infoLogger, () -> true); + + jmxRmiOpenTypesSerialFilter.setPropertyValueUnlessExists(value); + + String expectedLogMessage = "System property " + JmxRmiOpenTypesSerialFilter.PROPERTY_NAME + + " is now configured with '" + value + "'."; + verify(infoLogger).accept(expectedLogMessage); + } + + @Test + public void setPropertyValue_leavesExistingValue_ifExistingValueIsNotEmpty() { + String existingValue = "existing-value-of-property"; + System.setProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME, existingValue); + String value = "value-of-property"; + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + jmxRmiOpenTypesSerialFilter.setPropertyValueUnlessExists(value); + + String propertyValue = System.getProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME); + assertThat(propertyValue).isEqualTo(existingValue); + } + + @Test + public void setPropertyValue_logsMessage_ifExistingValueIsNotEmpty() { + String existingValue = "existing-value-of-property"; + System.setProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME, existingValue); + String value = "value-of-property"; + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = + new JmxRmiOpenTypesSerialFilter(infoLogger, () -> true); + + jmxRmiOpenTypesSerialFilter.setPropertyValueUnlessExists(value); + + String expectedLogMessage = "System property " + JmxRmiOpenTypesSerialFilter.PROPERTY_NAME + + " is already configured."; + verify(infoLogger).accept(expectedLogMessage); + } + + @Test + public void createSerialFilterPattern_includesBoolean() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(Boolean.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesByte() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(Byte.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesCharacter() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(Character.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesShort() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(Short.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesInteger() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(Integer.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesLong() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(Long.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesFloat() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(Float.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesDouble() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(Double.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesString() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(String.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesBigInteger() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(BigInteger.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesBigDecimal() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(BigDecimal.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesObjectName() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(ObjectName.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesCompositeData() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(CompositeData.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesTabularData() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(TabularData.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesSimpleType() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(SimpleType.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesCompositeType() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(CompositeType.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesTabularType() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(TabularType.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesArrayType() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(ArrayType.class.getName()); + } + + @Test + public void createSerialFilterPattern_includesMarshalledObject() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).contains(MarshalledObject.class.getName()); + } + + @Test + public void createSerialFilterPattern_rejectsAllOtherTypes() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter(); + + String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern(); + + assertThat(result).endsWith("!*"); + } + + @Test + public void configureSerialFilterIfEmpty_setsPropertyValue_atLeastJava9() { + JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = + new JmxRmiOpenTypesSerialFilter(infoLogger, () -> true); + + jmxRmiOpenTypesSerialFilter.configureSerialFilter(); + + String propertyValue = System.getProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME); + assertThat(propertyValue).isNotEmpty(); + } + + @Test + public void configureSerialFilterIfEmpty_setsPropertyValue_atMostJava8() { + JmxRmiSerialFilter jmxRmiOpenTypesSerialFilter = + new JmxRmiOpenTypesSerialFilter(infoLogger, () -> false); + + jmxRmiOpenTypesSerialFilter.configureSerialFilter(); + + String propertyValue = System.getProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME); + assertThat(propertyValue).isNull(); + } +} diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/SystemManagementServiceTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/SystemManagementServiceTest.java index 627a5b4..e5e792e 100644 --- a/geode-core/src/test/java/org/apache/geode/management/internal/SystemManagementServiceTest.java +++ b/geode-core/src/test/java/org/apache/geode/management/internal/SystemManagementServiceTest.java @@ -12,7 +12,6 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package org.apache.geode.management.internal; import static org.apache.geode.distributed.internal.ResourceEvent.MANAGER_START; @@ -29,7 +28,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.mockito.quality.Strictness.LENIENT; -import java.util.function.BiFunction; import java.util.function.Function; import org.junit.Before; @@ -66,7 +64,7 @@ public class SystemManagementServiceTest { @Mock private ManagementAgent managementAgent; @Mock - private BiFunction<DistributionConfig, InternalCacheForClientAccess, ManagementAgent> managementAgentFactory; + private ManagementAgentFactory managementAgentFactory; @Mock private Function<ManagementResourceRepo, NotificationHub> notificationHubFactory; @Mock @@ -87,7 +85,7 @@ public class SystemManagementServiceTest { .create(any(), any(), any(), any(), any(), any(), any(), any(), any())) .thenReturn(federatingManager); - when(managementAgentFactory.apply(any(), any())).thenReturn(managementAgent); + when(managementAgentFactory.create(any(), any(), any())).thenReturn(managementAgent); when(notificationHubFactory.apply(any())).thenReturn(mock(NotificationHub.class)); when(localManagerFactory.apply(any())).thenReturn(mock(LocalManager.class)); } diff --git a/geode-junit/build.gradle b/geode-junit/build.gradle index d880f6a..455fca8 100755 --- a/geode-junit/build.gradle +++ b/geode-junit/build.gradle @@ -23,6 +23,7 @@ apply from: "${project.projectDir}/../gradle/publish-java.gradle" dependencies { compile(platform(project(':boms:geode-all-bom'))) + testImplementation(project(':geode-common')) compileOnly(project(':geode-core')) compileOnly(project(':geode-logging')) compileOnly(project(':geode-serialization')) diff --git a/geode-junit/src/main/java/org/apache/geode/test/junit/rules/CloseableReference.java b/geode-junit/src/main/java/org/apache/geode/test/junit/rules/CloseableReference.java new file mode 100644 index 0000000..f2bbd64 --- /dev/null +++ b/geode-junit/src/main/java/org/apache/geode/test/junit/rules/CloseableReference.java @@ -0,0 +1,209 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.test.junit.rules; + +import static org.apache.geode.util.internal.UncheckedUtils.uncheckedCast; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.geode.test.junit.rules.serializable.SerializableExternalResource; + +/** + * CloseableReference is a JUnit Rule that provides automated tearDown for an atomic reference. If + * the referenced value is an {@code AutoCloseable} or {@code Closeable} then it will be auto-closed + * and set to null during tear down. + * + * <p> + * If the referenced value is not an {@code AutoCloseable} or {@code Closeable}, the + * {@code CloseableReference} will use reflection to invoke any method named {@code close}, + * {@code disconnect}, or {@code stop} regardless of what interfaces are implemented unless + * {@code autoClose} is set to false. + * + * <p> + * If the referenced value is null then it will be ignored during tear down. + * + * <p> + * In the following example, the test has a {@code ServerLauncher} which will be + * auto-stopped and set to null during tear down: + * + * <pre> + * {@literal @}Rule + * public CloseableReference<ServerLauncher> server = new CloseableReference<>(); + * + * {@literal @}Before + * public void setUp() throws IOException { + * server.set(new ServerLauncher.Builder() + * .setMemberName("server1") + * .setDisableDefaultServer(true) + * .reinitializeWithWorkingDirectory(temporaryFolder.newFolder("server1").getAbsolutePath()) + * .build()); + * + * server.get().start(); + * } + * + * {@literal @}Test + * public void serverHasCache() { + * assertThat(server.get().getCache()).isNotNull(); + * } + * </pre> + * + * <p> + * In the following example, the test has a {@code Cache} which will be auto-closed and set to null + * during tear down: + * + * <pre> + * {@literal @}Rule + * public CloseableReference<Cache> cache = new CloseableReference<>(); + * + * {@literal @}Before + * public void setUp() { + * cache.set(new CacheFactory().create()); + * } + * + * {@literal @}Test + * public void cacheExists() { + * assertThat(cache.get()).isNotNull(); + * } + * </pre> + * + * <p> + * In the following example, the test has a {@code DistributedSystem} which will be + * auto-disconnected and set to null during tear down: + * + * <pre> + * {@literal @}Rule + * public CloseableReference<DistributedSystem> system = new CloseableReference<>(); + * + * {@literal @}Before + * public void setUp() { + * system.set(DistributedSystem.connect()); + * } + * + * {@literal @}Test + * public void distributedSystemExists() { + * assertThat(system.get()).isNotNull(); + * } + * </pre> + * + * <p> + * To disable auto-closing in a test, specify {@code autoClose(false)}: + * + * <pre> + * {@literal @}Rule + * public CloseableReference<ServerLauncher> serverLauncher = + * new CloseableReference<>().autoClose(false); + * </pre> + * + * <p> + * The {@code CloseableReference} value will still be set to null during tear down even if + * auto-closing is disabled. + */ +@SuppressWarnings({"serial", "WeakerAccess"}) +public class CloseableReference<V> extends SerializableExternalResource { + + private static final AtomicReference<Object> reference = new AtomicReference<>(); + + private final AtomicBoolean autoClose = new AtomicBoolean(true); + + /** + * Set false to disable autoClose during tearDown. Default is true. + */ + public CloseableReference<V> autoClose(boolean value) { + autoClose.set(value); + return this; + } + + /** + * Gets the current value. + * + * @return the current value + */ + public V get() { + return uncheckedCast(reference.get()); + } + + /** + * Sets to the given value. + * + * @param newValue the new value + */ + public CloseableReference<V> set(V newValue) { + reference.set(newValue); + return this; + } + + @Override + protected void after() { + V value = get(); + if (value == null) { + return; + } + reference.set(null); + + if (autoClose.get()) { + autoClose(value); + } + } + + private void autoClose(V value) { + if (value instanceof AutoCloseable) { + close((AutoCloseable) value); + + } else if (hasMethod(value.getClass(), "close")) { + invokeMethod(value, "close"); + + } else if (hasMethod(value.getClass(), "disconnect")) { + invokeMethod(value, "disconnect"); + + } else if (hasMethod(value.getClass(), "stop")) { + invokeMethod(value, "stop"); + } + } + + private static void close(AutoCloseable autoCloseable) { + try { + autoCloseable.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static boolean hasMethod(Class<?> objectClass, String methodName) { + try { + Method method = objectClass.getMethod(methodName); + // currently only supports public method with zero parameters + if (method.getParameterCount() == 0 && + Modifier.isPublic(method.getModifiers())) { + return true; + } + } catch (NoSuchMethodException e) { + // ignore + } + return false; + } + + private static void invokeMethod(Object object, String methodName) { + try { + Method method = object.getClass().getMethod(methodName); + method.invoke(object); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/geode-junit/src/test/java/org/apache/geode/test/junit/rules/CloseableReferenceTest.java b/geode-junit/src/test/java/org/apache/geode/test/junit/rules/CloseableReferenceTest.java new file mode 100644 index 0000000..786e2e6 --- /dev/null +++ b/geode-junit/src/test/java/org/apache/geode/test/junit/rules/CloseableReferenceTest.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.test.junit.rules; + +import static org.apache.geode.util.internal.UncheckedUtils.uncheckedCast; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.Result; + +import org.apache.geode.test.junit.runners.TestRunner; + +public class CloseableReferenceTest { + + private static final AtomicReference<CloseableReference<?>> RULE = new AtomicReference<>(); + private static final AtomicReference<Object> VALUE = new AtomicReference<>(); + + @After + public void tearDown() { + RULE.set(null); + VALUE.set(null); + } + + @Test + public void closesAutoCloseable() throws Exception { + VALUE.set(mock(AutoCloseable.class)); + + Result result = TestRunner.runTest(WithCloseableReference.class); + assertThat(result.wasSuccessful()).isTrue(); + + AutoCloseable value = uncheckedCast(VALUE.get()); + verify(value).close(); + } + + @Test + public void nullsOutAutoCloseable() { + VALUE.set(mock(AutoCloseable.class)); + + Result result = TestRunner.runTest(WithCloseableReference.class); + assertThat(result.wasSuccessful()).isTrue(); + + CloseableReference<AutoCloseable> reference = uncheckedCast(RULE.get()); + assertThat(reference.get()).isNull(); + } + + @Test + public void closesCloseable() throws IOException { + VALUE.set(mock(Closeable.class)); + + Result result = TestRunner.runTest(WithCloseableReference.class); + assertThat(result.wasSuccessful()).isTrue(); + + Closeable value = uncheckedCast(VALUE.get()); + verify(value).close(); + } + + @Test + public void nullsOutCloseable() { + VALUE.set(mock(Closeable.class)); + + Result result = TestRunner.runTest(WithCloseableReference.class); + assertThat(result.wasSuccessful()).isTrue(); + + CloseableReference<Closeable> reference = uncheckedCast(RULE.get()); + assertThat(reference.get()).isNull(); + } + + @Test + public void disconnectDisconnectable() { + VALUE.set(mock(Disconnectable.class)); + + Result result = TestRunner.runTest(WithCloseableReference.class); + assertThat(result.wasSuccessful()).isTrue(); + + Disconnectable value = uncheckedCast(VALUE.get()); + verify(value).disconnect(); + } + + @Test + public void nullsOutDisconnectable() { + VALUE.set(mock(Disconnectable.class)); + + Result result = TestRunner.runTest(WithCloseableReference.class); + assertThat(result.wasSuccessful()).isTrue(); + + CloseableReference<Disconnectable> reference = uncheckedCast(RULE.get()); + assertThat(reference.get()).isNull(); + } + + @Test + public void disconnectStoppable() { + VALUE.set(mock(Stoppable.class)); + + Result result = TestRunner.runTest(WithCloseableReference.class); + assertThat(result.wasSuccessful()).isTrue(); + + Stoppable value = uncheckedCast(VALUE.get()); + verify(value).stop(); + } + + @Test + public void nullsOutStoppable() { + VALUE.set(mock(Stoppable.class)); + + Result result = TestRunner.runTest(WithCloseableReference.class); + assertThat(result.wasSuccessful()).isTrue(); + + CloseableReference<Stoppable> reference = uncheckedCast(RULE.get()); + assertThat(reference.get()).isNull(); + } + + @Test + public void skipsCloseIfAutoCloseIsFalse() { + VALUE.set(mock(AutoCloseable.class)); + + Result result = TestRunner.runTest(WithAutoCloseFalse.class); + assertThat(result.wasSuccessful()).isTrue(); + + AutoCloseable value = uncheckedCast(VALUE.get()); + verifyNoInteractions(value); + } + + @Test + public void nullsOutValueEvenIfAutoCloseIsFalse() { + VALUE.set(mock(AutoCloseable.class)); + + Result result = TestRunner.runTest(WithAutoCloseFalse.class); + assertThat(result.wasSuccessful()).isTrue(); + + CloseableReference<AutoCloseable> reference = uncheckedCast(RULE.get()); + assertThat(reference.get()).isNull(); + } + + private static void capture(CloseableReference<?> closeableReference) { + RULE.set(closeableReference); + closeableReference.set(uncheckedCast(VALUE.get())); + } + + public static class WithCloseableReference { + + @Rule + public CloseableReference<?> closeableReference = new CloseableReference<>(); + + @Before + public void setUp() { + capture(closeableReference); + } + + @Test + public void notClosed() { + verifyNoInteractions(closeableReference.get()); + } + } + + public static class WithAutoCloseFalse { + + @Rule + public CloseableReference<?> closeableReference = new CloseableReference<>().autoClose(false); + + @Before + public void setUp() { + capture(closeableReference); + } + + @Test + public void notClosed() { + verifyNoInteractions(closeableReference.get()); + } + } + + @FunctionalInterface + private interface Disconnectable { + + void disconnect(); + } + + @FunctionalInterface + private interface Stoppable { + + void stop(); + } +}