Mercurial > hg > thermostat-ng > agent
changeset 2427:bc83ef2d7047
Allow re-using of old attached Byteman agent.
Reviewed-by: neugens
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-August/020642.html
author | Severin Gehwolf <sgehwolf@redhat.com> |
---|---|
date | Tue, 23 Aug 2016 18:47:09 +0200 |
parents | ec9be9806a79 |
children | 88f534ba35ba |
files | vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentAttachManager.java vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentInfo.java vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAttacher.java vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentAttachManagerTest.java vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentInfoTest.java vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAttacherTest.java |
diffstat | 6 files changed, 221 insertions(+), 29 deletions(-) [+] |
line wrap: on
line diff
--- a/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentAttachManager.java Wed Aug 24 12:06:35 2016 +0200 +++ b/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentAttachManager.java Tue Aug 23 18:47:09 2016 +0200 @@ -39,6 +39,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; @@ -65,7 +66,9 @@ private static final String BYTEMAN_HELPER_DIR = BYTEMAN_PLUGIN_LIBS_DIR + File.separator + "thermostat-helper"; private static final String BYTEMAN_HOME_PROPERTY = "org.jboss.byteman.home"; private static final Logger logger = LoggingUtils.getLogger(BytemanAgentAttachManager.class); + // package-private for testing + static final String THERMOSTAT_HELPER_SOCKET_NAME_PROPERTY = "org.jboss.byteman.thermostat.socketName"; static List<String> helperJars; private final SubmitHelper submit; @@ -94,16 +97,11 @@ logger.warning("Failed to attach byteman agent for VM '" + vmPid + "'. Skipping rule updater and IPC channel."); return null; } - if (info.isAttachFailedNoSuchProcess()) { - logger.finest("Process with pid " + vmPid + " went away before we could attach the byteman agent to it."); + VmSocketIdentifier socketId = new VmSocketIdentifier(vmId.get(), vmPid, writerId.getWriterID()); + boolean postAttachGood = performPostAttachSteps(info, socketId); + if (!postAttachGood) { return null; } - logger.fine("Attached byteman agent to VM '" + vmPid + "' at port: '" + info.getAgentListenPort()); - if (!addThermostatHelperJarsToClasspath(info)) { - logger.warning("VM '" + vmPid + "': Failed to add helper jars to target VM's classpath."); - return null; - } - VmSocketIdentifier socketId = new VmSocketIdentifier(vmId.get(), vmPid, writerId.getWriterID()); ThermostatIPCCallbacks callback = new BytemanMetricsReceiver(vmBytemanDao, socketId); ipcManager.startIPCEndpoint(socketId, callback); // Add a status record to storage @@ -115,6 +113,26 @@ return status; } + private boolean performPostAttachSteps(BytemanAgentInfo info, VmSocketIdentifier socketId) { + if (info.isAttachFailedNoSuchProcess()) { + logger.finest("Process with pid " + info.getVmPid() + " went away before we could attach the byteman agent to it."); + return false; + } + if (info.isOldAttach()) { + logger.finest("Proceeding with post-attach steps for already attached byteman agent"); + Properties properties = new Properties(); + properties.setProperty(THERMOSTAT_HELPER_SOCKET_NAME_PROPERTY, socketId.getName()); + return submit.setSystemProperties(properties, info); + } else { + logger.fine("Attached byteman agent to VM '" + info.getVmPid() + "' at port: '" + info.getAgentListenPort()); + boolean addJarsSuccess = addThermostatHelperJarsToClasspath(info); + if (!addJarsSuccess) { + logger.warning("VM '" + info.getVmPid() + "': Failed to add helper jars to target VM's classpath."); + } + return addJarsSuccess; + } + } + private boolean addThermostatHelperJarsToClasspath(BytemanAgentInfo info) { return submit.addJarsToSystemClassLoader(helperJars, info); } @@ -181,5 +199,17 @@ } } + boolean setSystemProperties(Properties properties, BytemanAgentInfo info) { + Submit submit = new Submit(null /* localhost */, info.getAgentListenPort()); + try { + String result = submit.setSystemProperties(properties); + logger.fine("Re-set system properties with result: " + result); + return true; + } catch (Exception e) { + logger.log(Level.INFO, e.getMessage(), e); + return false; + } + } + } }
--- a/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentInfo.java Wed Aug 24 12:06:35 2016 +0200 +++ b/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentInfo.java Tue Aug 23 18:47:09 2016 +0200 @@ -46,14 +46,16 @@ private final String vmId; private final String writerId; private final boolean isAttachFailedNoProc; + private final boolean isOldAttach; - BytemanAgentInfo(int vmPid, int agentListenPort, String listenHost, String vmId, String writerId, boolean isAttachFailedNoProc) { + BytemanAgentInfo(int vmPid, int agentListenPort, String listenHost, String vmId, String writerId, boolean isAttachFailedNoProc, boolean isOldAttach) { this.agentListenPort = agentListenPort; this.listenHost = listenHost; this.vmPid = vmPid; this.vmId = vmId; this.writerId = writerId; this.isAttachFailedNoProc = isAttachFailedNoProc; + this.isOldAttach = isOldAttach; } int getVmPid() { @@ -80,6 +82,10 @@ return isAttachFailedNoProc; } + boolean isOldAttach() { + return isOldAttach; + } + @Override public boolean equals(Object other) { if (other == null) { @@ -94,11 +100,12 @@ Objects.equals(vmPid, o.vmPid) && Objects.equals(vmId, o.vmId) && Objects.equals(writerId, o.writerId) && - Objects.equals(isAttachFailedNoProc, o.isAttachFailedNoProc); + Objects.equals(isAttachFailedNoProc, o.isAttachFailedNoProc) && + Objects.equals(isOldAttach, o.isOldAttach); } @Override public int hashCode() { - return Objects.hash(agentListenPort, listenHost, vmPid, vmId, writerId, isAttachFailedNoProc); + return Objects.hash(agentListenPort, listenHost, vmPid, vmId, writerId, isAttachFailedNoProc, isOldAttach); } }
--- a/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAttacher.java Wed Aug 24 12:06:35 2016 +0200 +++ b/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAttacher.java Tue Aug 23 18:47:09 2016 +0200 @@ -92,11 +92,12 @@ VmSocketIdentifier sockIdentifier = new VmSocketIdentifier(vmId, pid, agentId); String[] btmInstallProps = buildBytemanInstallProps(sockIdentifier, port); boolean agentJarToBootClassPath = true; - int actualPort = installer.install(Integer.toString(pid), agentJarToBootClassPath, false, null /* localhost */, port, btmInstallProps); + InstallResult result = installer.install(Integer.toString(pid), agentJarToBootClassPath, false, null /* localhost */, port, btmInstallProps); + int actualPort = result.getPort(); // Port might have changed here if agent rebooted and targed jvm // stayed alive if (actualPort > 0) { - return new BytemanAgentInfo(pid, actualPort, null, vmId, agentId, false); + return new BytemanAgentInfo(pid, actualPort, null, vmId, agentId, false, result.isOldAttach()); } else { return null; } @@ -107,7 +108,7 @@ return handleAttachFailure(e, vmId, port, pid); } catch (IOException e) { if (!processChecker.exists(pid)) { - return new BytemanAgentInfo(pid, port, null, vmId, agentId, true); + return new BytemanAgentInfo(pid, port, null, vmId, agentId, true, false); } return handleAttachFailure(e, vmId, port, pid); } @@ -151,7 +152,7 @@ private static final int UNKNOWN_PORT = -1; - int install(String vmPid, boolean addToBoot, boolean setPolicy, String hostname, int port, String[] properties) + InstallResult install(String vmPid, boolean addToBoot, boolean setPolicy, String hostname, int port, String[] properties) throws IllegalArgumentException, FileNotFoundException, IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { @@ -159,17 +160,36 @@ boolean loaded = Boolean.parseBoolean(propVal); if (!loaded) { Install.install(vmPid, addToBoot, setPolicy, hostname, port, properties); - return port; + return new InstallResult(port, false); } else { try { int oldPort = Integer.parseInt(Install.getSystemProperty(vmPid, BYTEMAN_AGENT_PORT_PROPERTY)); logger.finest("VM (pid: " + vmPid + "): Not installing byteman agent since one is already attached on port " + oldPort); - return oldPort; + return new InstallResult(oldPort, true); } catch (NumberFormatException e) { logger.info("VM (pid: " + vmPid + "): Has a byteman agent already attached, but it wasn't thermostat that attached it"); - return UNKNOWN_PORT; + return new InstallResult(UNKNOWN_PORT, true); } } } } + + static class InstallResult { + + private final int port; + private final boolean isOldAttach; + + InstallResult(int port, boolean isOldAttach) { + this.port = port; + this.isOldAttach = isOldAttach; + } + + boolean isOldAttach() { + return isOldAttach; + } + + int getPort() { + return port; + } + } } \ No newline at end of file
--- a/vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentAttachManagerTest.java Wed Aug 24 12:06:35 2016 +0200 +++ b/vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentAttachManagerTest.java Tue Aug 23 18:47:09 2016 +0200 @@ -45,12 +45,14 @@ import static org.mockito.Matchers.isA; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.File; import java.io.IOException; import java.util.List; +import java.util.Properties; import org.junit.After; import org.junit.Before; @@ -72,7 +74,7 @@ private static final VmId SOME_VM_ID = new VmId("some-vm-id"); private static final int SOME_VM_PID = 99910; private static final String SOME_AGENT_ID = "some-agent-id"; - private static final BytemanAgentInfo SOME_SUCCESS_BYTEMAN_INFO = new BytemanAgentInfo(SOME_VM_PID, 3344, null, SOME_VM_ID.get(), SOME_AGENT_ID, false); + private static final BytemanAgentInfo SOME_SUCCESS_BYTEMAN_INFO = new BytemanAgentInfo(SOME_VM_PID, 3344, null, SOME_VM_ID.get(), SOME_AGENT_ID, false, false); private BytemanAgentAttachManager manager; private IPCEndpointsManager ipcManager; @@ -103,7 +105,7 @@ int vmPid = 1001; String agentId = "working-agent-id"; int listenPort = 9881; - BytemanAgentInfo bytemanAgentInfo = new BytemanAgentInfo(vmPid, listenPort, null, workingVmId, agentId, false); + BytemanAgentInfo bytemanAgentInfo = new BytemanAgentInfo(vmPid, listenPort, null, workingVmId, agentId, false, false); when(bytemanAttacher.attach(workingVmId, vmPid, agentId)).thenReturn(bytemanAgentInfo); // mock that installing of helper jars works @@ -136,6 +138,53 @@ assertEquals(listenPort, bytemanStatus.getListenPort()); } + @SuppressWarnings("unchecked") + @Test + public void canUseOldAttachedAgentStartIPCandAddsStatus() { + String workingVmId = "working-vm-id"; + int vmPid = 1001; + String agentId = "working-agent-id"; + int listenPort = 9881; + BytemanAgentInfo bytemanAgentInfo = new BytemanAgentInfo(vmPid, listenPort, null, workingVmId, agentId, false, true); + when(bytemanAttacher.attach(workingVmId, vmPid, agentId)).thenReturn(bytemanAgentInfo); + + // mock setting system properties' success + when(submit.setSystemProperties(any(Properties.class), any(BytemanAgentInfo.class))).thenReturn(true); + + VmId vmId = new VmId(workingVmId); + WriterID writerId = mock(WriterID.class); + when(writerId.getWriterID()).thenReturn(agentId); + manager.setWriterId(writerId); + VmBytemanStatus bytemanStatus = manager.attachBytemanToVm(vmId, vmPid); + VmSocketIdentifier socketId = new VmSocketIdentifier(workingVmId, vmPid, agentId); + + // IPC endpoint must be started + verify(ipcManager).startIPCEndpoint(eq(socketId), isA(BytemanMetricsReceiver.class)); + + // Status should have been updated/inserted + ArgumentCaptor<VmBytemanStatus> statusCaptor = ArgumentCaptor.forClass(VmBytemanStatus.class); + verify(vmBytemanDao).addOrReplaceBytemanStatus(statusCaptor.capture()); + VmBytemanStatus capturedStatus = statusCaptor.getValue(); + assertNotNull(capturedStatus); + assertEquals(workingVmId, capturedStatus.getVmId()); + assertEquals(agentId, capturedStatus.getAgentId()); + assertEquals(listenPort, capturedStatus.getListenPort()); + assertNull(capturedStatus.getRule()); + assertTrue(capturedStatus.getTimeStamp() > 0); + + // Helper jars must not have been added again to classpath + verify(submit, times(0)).addJarsToSystemClassLoader(any(List.class), any(BytemanAgentInfo.class)); + + // Verify properties got set appropriately + ArgumentCaptor<Properties> propsCaptor = ArgumentCaptor.forClass(Properties.class); + verify(submit).setSystemProperties(propsCaptor.capture(), eq(bytemanAgentInfo)); + Properties props = propsCaptor.getValue(); + String propVal = props.getProperty(BytemanAgentAttachManager.THERMOSTAT_HELPER_SOCKET_NAME_PROPERTY); + assertEquals(socketId.getName(), propVal); + + assertEquals(listenPort, bytemanStatus.getListenPort()); + } + @Test public void failureToAttachDoesNotStartIPC() throws Exception { BytemanAttacher failAttacher = getFailureAttacher();
--- a/vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentInfoTest.java Wed Aug 24 12:06:35 2016 +0200 +++ b/vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentInfoTest.java Tue Aug 23 18:47:09 2016 +0200 @@ -45,6 +45,7 @@ public class BytemanAgentInfoTest { private static final boolean SOME_ATTACH_FAILED = false; + private static final boolean SOME_IS_OLD_ATTACH = false; private static final int SOME_VM_PID = 99292; private static final int SOME_LISTEN_PORT = 3321; private static final String SOME_LISTEN_HOST = "foo-bar"; @@ -53,10 +54,22 @@ @Test public void testEqualsHashCode() { - BytemanAgentInfo bytemanInfo = new BytemanAgentInfo(SOME_VM_PID, SOME_LISTEN_PORT, SOME_LISTEN_HOST, SOME_VM_ID, SOME_AGENT_ID, SOME_ATTACH_FAILED); + BytemanAgentInfo bytemanInfo = new BytemanAgentInfo(SOME_VM_PID, + SOME_LISTEN_PORT, + SOME_LISTEN_HOST, + SOME_VM_ID, + SOME_AGENT_ID, + SOME_ATTACH_FAILED, + SOME_IS_OLD_ATTACH); assertFalse(bytemanInfo.equals(null)); assertFalse(bytemanInfo.equals(null)); // multiple calls - BytemanAgentInfo info2 = new BytemanAgentInfo(SOME_VM_PID, SOME_LISTEN_PORT, SOME_LISTEN_HOST, SOME_VM_ID, SOME_AGENT_ID, SOME_ATTACH_FAILED); + BytemanAgentInfo info2 = new BytemanAgentInfo(SOME_VM_PID, + SOME_LISTEN_PORT, + SOME_LISTEN_HOST, + SOME_VM_ID, + SOME_AGENT_ID, + SOME_ATTACH_FAILED, + SOME_IS_OLD_ATTACH); assertTrue(bytemanInfo.equals(info2)); assertTrue(info2.equals(bytemanInfo)); // reflexive property assertTrue(bytemanInfo.equals(info2)); // multiple calls @@ -67,35 +80,88 @@ @Test public void testNotEquals() { - BytemanAgentInfo bytemanInfo = new BytemanAgentInfo(SOME_VM_PID, SOME_LISTEN_PORT, SOME_LISTEN_HOST, SOME_VM_ID, SOME_AGENT_ID, SOME_ATTACH_FAILED); + BytemanAgentInfo bytemanInfo = new BytemanAgentInfo(SOME_VM_PID, + SOME_LISTEN_PORT, + SOME_LISTEN_HOST, + SOME_VM_ID, + SOME_AGENT_ID, + SOME_ATTACH_FAILED, + SOME_IS_OLD_ATTACH); // vmPid different - BytemanAgentInfo other = new BytemanAgentInfo(992, SOME_LISTEN_PORT, SOME_LISTEN_HOST, SOME_VM_ID, SOME_AGENT_ID, SOME_ATTACH_FAILED); + BytemanAgentInfo other = new BytemanAgentInfo(992, + SOME_LISTEN_PORT, + SOME_LISTEN_HOST, + SOME_VM_ID, + SOME_AGENT_ID, + SOME_ATTACH_FAILED, + SOME_IS_OLD_ATTACH); assertFalse(bytemanInfo.equals(other)); assertFalse(other.equals(bytemanInfo)); // listen port different - other = new BytemanAgentInfo(SOME_VM_PID, 5554, SOME_LISTEN_HOST, SOME_VM_ID, SOME_AGENT_ID, SOME_ATTACH_FAILED); + other = new BytemanAgentInfo(SOME_VM_PID, + 5554, + SOME_LISTEN_HOST, + SOME_VM_ID, + SOME_AGENT_ID, + SOME_ATTACH_FAILED, + SOME_IS_OLD_ATTACH); assertFalse(bytemanInfo.equals(other)); assertFalse(other.equals(bytemanInfo)); // listen host different - other = new BytemanAgentInfo(SOME_VM_PID, SOME_LISTEN_PORT, null, SOME_VM_ID, SOME_AGENT_ID, SOME_ATTACH_FAILED); + other = new BytemanAgentInfo(SOME_VM_PID, + SOME_LISTEN_PORT, + null, + SOME_VM_ID, + SOME_AGENT_ID, + SOME_ATTACH_FAILED, + SOME_IS_OLD_ATTACH); assertFalse(bytemanInfo.equals(other)); assertFalse(other.equals(bytemanInfo)); // vmId different - other = new BytemanAgentInfo(SOME_VM_PID, SOME_LISTEN_PORT, SOME_LISTEN_HOST, "other", SOME_AGENT_ID, SOME_ATTACH_FAILED); + other = new BytemanAgentInfo(SOME_VM_PID, + SOME_LISTEN_PORT, + SOME_LISTEN_HOST, + "other", + SOME_AGENT_ID, + SOME_ATTACH_FAILED, + SOME_IS_OLD_ATTACH); assertFalse(bytemanInfo.equals(other)); assertFalse(other.equals(bytemanInfo)); // agentId different - other = new BytemanAgentInfo(SOME_VM_PID, SOME_LISTEN_PORT, SOME_LISTEN_HOST, SOME_VM_ID, "foobar", SOME_ATTACH_FAILED); + other = new BytemanAgentInfo(SOME_VM_PID, + SOME_LISTEN_PORT, + SOME_LISTEN_HOST, + SOME_VM_ID, + "foobar", + SOME_ATTACH_FAILED, + SOME_IS_OLD_ATTACH); assertFalse(bytemanInfo.equals(other)); assertFalse(other.equals(bytemanInfo)); // attachFailed different - other = new BytemanAgentInfo(SOME_VM_PID, SOME_LISTEN_PORT, SOME_LISTEN_HOST, SOME_VM_ID, SOME_AGENT_ID, true); + other = new BytemanAgentInfo(SOME_VM_PID, + SOME_LISTEN_PORT, + SOME_LISTEN_HOST, + SOME_VM_ID, + SOME_AGENT_ID, + true, + SOME_IS_OLD_ATTACH); + assertFalse(bytemanInfo.equals(other)); + assertFalse(other.equals(bytemanInfo)); + + // old attach different + other = new BytemanAgentInfo(SOME_VM_PID, + SOME_LISTEN_PORT, + SOME_LISTEN_HOST, + SOME_VM_ID, + SOME_AGENT_ID, + SOME_ATTACH_FAILED, + true); assertFalse(bytemanInfo.equals(other)); assertFalse(other.equals(bytemanInfo)); }
--- a/vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAttacherTest.java Wed Aug 24 12:06:35 2016 +0200 +++ b/vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAttacherTest.java Tue Aug 23 18:47:09 2016 +0200 @@ -58,6 +58,7 @@ import com.redhat.thermostat.agent.utils.ProcessChecker; import com.redhat.thermostat.shared.config.CommonPaths; import com.redhat.thermostat.vm.byteman.agent.internal.BytemanAttacher.BtmInstallHelper; +import com.redhat.thermostat.vm.byteman.agent.internal.BytemanAttacher.InstallResult; public class BytemanAttacherTest { @@ -102,6 +103,8 @@ @Test public void attachSetsAppropriateProperties() throws Exception { BtmInstallHelper installer = mock(BtmInstallHelper.class); + when(installer.install(any(String.class), any(boolean.class), any(boolean.class), any(String.class), any(int.class), any(String[].class))) + .thenReturn(new InstallResult(8888, true)); ArgumentCaptor<String[]> propsCaptor = ArgumentCaptor.forClass(String[].class); BytemanAttacher attacher = new BytemanAttacher(installer, processChecker, paths); attacher.attach("testVmId", 9999, "fooAgent"); @@ -122,6 +125,23 @@ } /* + * Tests whether an old byteman agent attached by a previous thermostat + * agent run can be re-used when the JVM persists + */ + @Test + public void canReuseOldAttach() throws Exception { + int port = 39203; + BtmInstallHelper installer = mock(BtmInstallHelper.class); + when(installer.install(any(String.class), any(boolean.class), any(boolean.class), any(String.class), any(int.class), any(String[].class))) + .thenReturn(new InstallResult(port, true)); + BytemanAttacher attacher = new BytemanAttacher(installer, mock(ProcessChecker.class), paths); + + int somePid = 3222; + BytemanAgentInfo info = attacher.attach("someVmId", somePid, "someAgent"); + assertTrue(info.isOldAttach()); + } + + /* * English locale: "No such process" exception message. It shouldn't matter * though. Verified by next test. */