|
15 | 15 | */ |
16 | 16 | package org.glassfish.tests.embedded.runnable; |
17 | 17 |
|
| 18 | +import com.sun.tools.attach.VirtualMachine; |
| 19 | +import com.sun.tools.attach.VirtualMachineDescriptor; |
| 20 | + |
18 | 21 | import java.io.File; |
19 | 22 | import java.io.IOException; |
20 | | -import java.util.ArrayList; |
21 | 23 | import java.util.List; |
22 | 24 | import java.util.Optional; |
| 25 | +import java.util.Properties; |
23 | 26 | import java.util.Set; |
24 | 27 | import java.util.concurrent.TimeUnit; |
25 | 28 | import java.util.logging.Logger; |
26 | 29 |
|
| 30 | +import javax.management.JMException; |
27 | 31 | import javax.management.MBeanServerConnection; |
28 | 32 | import javax.management.ObjectName; |
29 | 33 | import javax.management.remote.JMXConnector; |
30 | 34 | import javax.management.remote.JMXConnectorFactory; |
31 | 35 | import javax.management.remote.JMXServiceURL; |
32 | 36 |
|
33 | 37 | import org.glassfish.tests.embedded.runnable.TestArgumentProviders.GfEmbeddedJarNameProvider; |
| 38 | +import org.jboss.shrinkwrap.api.ShrinkWrap; |
| 39 | +import org.jboss.shrinkwrap.api.exporter.ZipExporter; |
| 40 | +import org.jboss.shrinkwrap.api.spec.WebArchive; |
34 | 41 | import org.junit.jupiter.params.ParameterizedTest; |
35 | 42 | import org.junit.jupiter.params.provider.ArgumentsSource; |
36 | 43 |
|
| 44 | +import static java.util.logging.Level.WARNING; |
| 45 | +import static org.glassfish.tests.embedded.runnable.GfEmbeddedUtils.runGlassFishEmbedded; |
| 46 | +import static org.glassfish.tests.embedded.runnable.ShrinkwrapUtils.logArchiveContent; |
| 47 | +import static org.glassfish.tests.embedded.runnable.TestUtils.waitFor; |
37 | 48 | import static org.junit.jupiter.api.Assertions.assertNotNull; |
38 | | -import static org.junit.jupiter.api.Assertions.assertTrue; |
| 49 | +import static org.junit.jupiter.api.Assumptions.assumeTrue; |
39 | 50 |
|
40 | 51 | /** |
41 | 52 | * @author Ondro Mihalyi |
42 | 53 | */ |
43 | 54 | public class MonitoringTest { |
44 | 55 |
|
45 | 56 | private static final Logger LOG = Logger.getLogger(MonitoringTest.class.getName()); |
46 | | - private static final int JMX_PORT = 8686; |
47 | | - private static final int WAIT_SECONDS = 30; |
48 | 57 |
|
49 | 58 | @ParameterizedTest |
50 | 59 | @ArgumentsSource(GfEmbeddedJarNameProvider.class) |
51 | 60 | void testJmxMonitoringWithFlashlightAgent(String gfEmbeddedJarName) throws Exception { |
| 61 | + assumeTrue(!gfEmbeddedJarName.endsWith("web.jar"), |
| 62 | + "AMX is not supported by glassfish-embedded-web.jar, skipping this test scenario"); |
52 | 63 | Process gfEmbeddedProcess = null; |
53 | 64 | JMXConnector jmxConnector = null; |
| 65 | + File warFile = null; |
54 | 66 | try { |
55 | | - gfEmbeddedProcess = startGlassFishWithJmx(gfEmbeddedJarName); |
| 67 | + // an app needs to be deployed to initialize request monitoring |
| 68 | + warFile = createEmptyApp(); |
| 69 | + gfEmbeddedProcess = startGlassFishWithJmx(gfEmbeddedJarName, warFile); |
56 | 70 |
|
57 | 71 | jmxConnector = connectToJmx(); |
58 | 72 | MBeanServerConnection mbsc = jmxConnector.getMBeanServerConnection(); |
59 | 73 |
|
60 | | - bootAmxAndWait(mbsc); |
| 74 | + bootAmx(mbsc); |
61 | 75 |
|
62 | 76 | verifyMonitoringMBeans(mbsc); |
63 | 77 |
|
64 | 78 | } finally { |
65 | | - cleanup(jmxConnector, gfEmbeddedProcess); |
| 79 | + cleanup(jmxConnector, gfEmbeddedProcess, warFile); |
66 | 80 | } |
67 | 81 | } |
68 | 82 |
|
69 | | - private File createMonitoringPropertiesFile() throws Exception { |
70 | | - File propertiesFile = File.createTempFile("monitoring", ".properties"); |
71 | | - java.nio.file.Files.write(propertiesFile.toPath(), List.of( |
72 | | - "configs.config.server-config.monitoring-service.module-monitoring-levels.http-service=HIGH", |
73 | | - "configs.config.server-config.monitoring-service.module-monitoring-levels.thread-pool=HIGH" |
74 | | - )); |
75 | | - return propertiesFile; |
76 | | - } |
| 83 | + private File createEmptyApp() throws Exception { |
| 84 | + WebArchive war = ShrinkWrap.create(WebArchive.class, "empty-app.war") |
| 85 | + .addAsWebInfResource("", "beans.xml"); |
77 | 86 |
|
78 | | - private Process startGlassFishWithJmx(String gfEmbeddedJarName) throws IOException { |
79 | | - List<String> arguments = new ArrayList<>(); |
80 | | - arguments.add(ProcessHandle.current().info().command().get()); |
81 | | - arguments.addAll(List.of( |
82 | | - "-javaagent:flashlight-agent.jar", |
83 | | - "-Dcom.sun.management.jmxremote", |
84 | | - "-Dcom.sun.management.jmxremote.port=" + JMX_PORT, |
85 | | - "-Dcom.sun.management.jmxremote.authenticate=false", |
86 | | - "-Dcom.sun.management.jmxremote.ssl=false", |
87 | | - "-jar", gfEmbeddedJarName, |
88 | | - "--noPort", |
89 | | - "enable-monitoring --modules http-service" |
90 | | - )); |
91 | | - |
92 | | - return new ProcessBuilder() |
93 | | - .redirectOutput(ProcessBuilder.Redirect.PIPE) |
94 | | - .redirectError(ProcessBuilder.Redirect.PIPE) |
95 | | - .command(arguments) |
96 | | - .start(); |
| 87 | + File warFile = File.createTempFile("empty-app", ".war"); |
| 88 | + war.as(ZipExporter.class).exportTo(warFile, true); |
| 89 | + logArchiveContent(war, "empty-app.war", LOG::info); |
| 90 | + return warFile; |
97 | 91 | } |
98 | 92 |
|
99 | | - private JMXConnector connectToJmx() throws Exception { |
100 | | - JMXServiceURL serviceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + JMX_PORT + "/jmxrmi"); |
| 93 | + private Process startGlassFishWithJmx(String gfEmbeddedJarName, File warFile) throws IOException { |
| 94 | + return runGlassFishEmbedded(gfEmbeddedJarName, true, |
| 95 | + List.of("-Dcom.sun.management.jmxremote", |
| 96 | + "-javaagent:flashlight-agent.jar"), |
| 97 | + "enable-monitoring --modules http-service", |
| 98 | + warFile.getAbsolutePath()); |
| 99 | + } |
101 | 100 |
|
102 | | - for (int i = 0; i < WAIT_SECONDS * 2; i++) { |
| 101 | + private JMXConnector connectToJmx() throws InterruptedException { |
| 102 | + return waitFor("JMX connector", () -> { |
| 103 | + VirtualMachine vm = null; |
| 104 | + String connectorAddress = null; |
103 | 105 | try { |
104 | | - return JMXConnectorFactory.connect(serviceURL, null); |
| 106 | + // Find GlassFish process by looking for our jar |
| 107 | + for (VirtualMachineDescriptor vmd : VirtualMachine.list()) { |
| 108 | + if (vmd.displayName().contains("glassfish-embedded")) { |
| 109 | + vm = VirtualMachine.attach(vmd.id()); |
| 110 | + |
| 111 | + // Get or create JMX connector address |
| 112 | + Properties props = vm.getAgentProperties(); |
| 113 | + vm.detach(); |
| 114 | + String connectorAddressProperty = "com.sun.management.jmxremote.localConnectorAddress"; |
| 115 | + connectorAddress = props.getProperty(connectorAddressProperty); |
| 116 | + |
| 117 | + if (connectorAddress != null) { |
| 118 | + LOG.info("Got connector address: " + connectorAddress); |
| 119 | + JMXServiceURL serviceURL = new JMXServiceURL(connectorAddress); |
| 120 | + return JMXConnectorFactory.connect(serviceURL, null); |
| 121 | + } else { |
| 122 | + throw new UnsupportedOperationException("Connector address not available!"); |
| 123 | + } |
| 124 | + } |
| 125 | + } |
105 | 126 | } catch (Exception e) { |
106 | | - Thread.sleep(500); |
| 127 | + LOG.log(WARNING, e.getMessage(), e); |
| 128 | + if (vm != null) try { vm.detach(); } catch (Exception ignored) {} |
107 | 129 | } |
108 | | - } |
109 | | - throw new IllegalStateException("Could not connect to JMX in " + WAIT_SECONDS + " seconds"); |
| 130 | + return (JMXConnector)null; |
| 131 | + }); |
110 | 132 | } |
111 | 133 |
|
112 | | - private void bootAmxAndWait(MBeanServerConnection mbsc) throws Exception { |
| 134 | + private void bootAmx(MBeanServerConnection mbsc) throws Exception { |
113 | 135 | ObjectName bootAMXObjectName = new ObjectName("amx-support:type=boot-amx"); |
114 | 136 |
|
115 | | - for (int i = 0; i < WAIT_SECONDS * 2; i++) { |
116 | | - if (mbsc.isRegistered(bootAMXObjectName)) { |
117 | | - break; |
118 | | - } |
119 | | - Thread.sleep(500); |
120 | | - } |
121 | | - |
122 | | - |
123 | | - assertTrue(mbsc.isRegistered(bootAMXObjectName), "bootAMX is registered"); |
| 137 | + waitFor("bootAMX", () -> mbsc.isRegistered(bootAMXObjectName) ? true : null); |
124 | 138 |
|
125 | 139 | mbsc.invoke(bootAMXObjectName, "bootAMX", null, null); |
126 | | - |
127 | | - // Wait for AMX runtime to be available |
128 | | - for (int i = 0; i < WAIT_SECONDS * 2; i++) { |
129 | | - Set<ObjectName> runtimeBeans = mbsc.queryNames(new ObjectName("amx:pp=/,type=runtime"), null); |
130 | | - if (!runtimeBeans.isEmpty()) { |
131 | | - return; |
132 | | - } |
133 | | - Thread.sleep(500); |
134 | | - } |
135 | | - throw new IllegalStateException("AMX runtime not available after " + WAIT_SECONDS + " seconds"); |
136 | 140 | } |
137 | 141 |
|
138 | | - private void verifyMonitoringMBeans(MBeanServerConnection mbsc) throws Exception { |
139 | | - Set<ObjectName> requestBeans = mbsc.queryNames(new ObjectName("amx:type=request-mon,*"), null); |
140 | | - assertTrue(!requestBeans.isEmpty(), "Request monitoring MBean should be present"); |
| 142 | + private void verifyMonitoringMBeans(final MBeanServerConnection mbsc) throws InterruptedException, IOException, JMException { |
| 143 | + Set<ObjectName> requestBeans = waitFor("equest-mon bean", () -> { |
| 144 | + Set<ObjectName> result = mbsc.queryNames(new ObjectName("amx:type=request-mon,*"), null); |
| 145 | + return result.isEmpty() ? null : result; |
| 146 | + }); |
141 | 147 |
|
142 | 148 | // Verify we can read monitoring data |
143 | 149 | ObjectName requestBean = requestBeans.iterator().next(); |
144 | 150 | assertNotNull(mbsc.getAttribute(requestBean, "countrequests"), "Should be able to read request count"); |
145 | 151 | } |
146 | 152 |
|
147 | | - private void cleanup(JMXConnector jmxConnector, Process process) throws InterruptedException { |
| 153 | + private void cleanup(JMXConnector jmxConnector, Process process, File... files) throws InterruptedException { |
148 | 154 | if (jmxConnector != null) try { jmxConnector.close(); } catch (Exception ignored) {} |
149 | 155 | if (process != null && process.isAlive()) { |
150 | | - process.destroyForcibly(); |
| 156 | + process.destroy(); |
151 | 157 | process.waitFor(5, TimeUnit.SECONDS); |
| 158 | + if (process.isAlive()) { |
| 159 | + process.destroy(); |
| 160 | + process.waitFor(5, TimeUnit.SECONDS); |
| 161 | + } |
152 | 162 | } |
| 163 | + cleanupFiles(files); |
153 | 164 | } |
154 | 165 |
|
155 | 166 | private void cleanupFiles(File... files) { |
|
0 commit comments