Mercurial > hg > thermostat-ng > agent
view plugins/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanRequestReceiver.java @ 2760:22f2b3ea3609
Add byteman integration services
Reviewed-by: jerboaa
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-September/025152.html
author | Jie Kang <jkang@redhat.com> |
---|---|
date | Fri, 22 Sep 2017 10:10:52 -0400 |
parents | 5f97c4af3bf3 |
children |
line wrap: on
line source
/* * Copyright 2012-2017 Red Hat, Inc. * * This file is part of Thermostat. * * Thermostat is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * Thermostat is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Thermostat; see the file COPYING. If not see * <http://www.gnu.org/licenses/>. * * Linking this code with other modules is making a combined work * based on this code. Thus, the terms and conditions of the GNU * General Public License cover the whole combination. * * As a special exception, the copyright holders of this code give * you permission to link this code with independent modules to * produce an executable, regardless of the license terms of these * independent modules, and to copy and distribute the resulting * executable under terms of your choice, provided that you also * meet, for each linked independent module, the terms and conditions * of the license of that module. An independent module is a module * which is not derived from or based on this code. If you modify * this code, you may extend this exception to your version of the * library, but you are not obligated to do so. If you do not wish * to do so, delete this exception statement from your version. */ package com.redhat.thermostat.vm.byteman.agent.internal; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.jboss.byteman.agent.submit.ScriptText; import org.jboss.byteman.agent.submit.Submit; import com.redhat.thermostat.agent.ipc.server.AgentIPCService; import com.redhat.thermostat.commands.agent.receiver.RequestReceiver; import com.redhat.thermostat.commands.model.AgentRequest; import com.redhat.thermostat.commands.model.WebSocketResponse; import com.redhat.thermostat.commands.model.WebSocketResponse.ResponseType; import com.redhat.thermostat.common.portability.ProcessUserInfoBuilder; import com.redhat.thermostat.common.portability.ProcessUserInfoBuilderFactory; import com.redhat.thermostat.common.portability.UserNameUtil; import com.redhat.thermostat.common.portability.linux.ProcDataSource; import com.redhat.thermostat.common.utils.LoggingUtils; import com.redhat.thermostat.jvm.overview.agent.model.VmId; import com.redhat.thermostat.shared.config.CommonPaths; import com.redhat.thermostat.storage.core.WriterID; import com.redhat.thermostat.vm.byteman.agent.VmBytemanDAO; import com.redhat.thermostat.vm.byteman.agent.VmBytemanStatus; import com.redhat.thermostat.vm.byteman.agent.internal.BytemanRequest.RequestAction; /** * Receiver class for Byteman action command channel requests. * */ @Component @Service(value = RequestReceiver.class) @Property(name = "servicename", value = BytemanRequestReceiver.ACTION_NAME) public class BytemanRequestReceiver implements RequestReceiver { public static final String ACTION_NAME = "byteman"; private static final int ILLEGAL_INT_VAL = -1; private static final int ILLEGAL_PORT = -2; private static final Logger logger = LoggingUtils.getLogger(BytemanRequestReceiver.class); private final BytemanAgentAttachManager attachManager; public BytemanRequestReceiver() { this(new BytemanAgentAttachManager()); } // package-private for testing BytemanRequestReceiver(BytemanAgentAttachManager attachManager) { this.attachManager = attachManager; } @Reference private VmBytemanDAO vmBytemanDao; @Reference private WriterID writerId; @Reference private CommonPaths commonPaths; @Reference private AgentIPCService agentIpcService; @Reference private UserNameUtil userNameUtil; //////////////////////////////////////////////// // methods used by DS //////////////////////////////////////////////// protected void bindWriterId(WriterID writerId) { this.writerId = writerId; attachManager.setWriterId(writerId); } protected void unbindWriterId(WriterID writerId) { this.writerId = null; attachManager.setWriterId(null); } protected void bindVmBytemanDao(VmBytemanDAO dao) { this.vmBytemanDao = dao; attachManager.setVmBytemanDao(dao); } protected void unbindVmBytemanDao(VmBytemanDAO dao) { this.vmBytemanDao = null; attachManager.setVmBytemanDao(null); } protected void bindCommonPaths(CommonPaths paths) { attachManager.setPaths(paths); } protected void unbindCommonPaths(CommonPaths paths) { // helper jars don't strictly need unsetting so we don't // call setPaths(null) } protected void bindAgentIpcService(AgentIPCService ipcService) { IPCEndpointsManager ipcEndpointsManager = new IPCEndpointsManager(ipcService); attachManager.setIpcManager(ipcEndpointsManager); BytemanAttacher bmAttacher = new BytemanAttacher(ipcService); attachManager.setAttacher(bmAttacher); } protected void unbindAgentIpcService(AgentIPCService ipcService) { attachManager.setIpcManager(null); attachManager.setAttacher(null); } protected void bindUserNameUtil(UserNameUtil userNameUtil) { ProcessUserInfoBuilder userInfoBuilder = ProcessUserInfoBuilderFactory.createBuilder(new ProcDataSource(), userNameUtil); attachManager.setUserInfoBuilder(userInfoBuilder); } protected void unbindUserNameUtil(UserNameUtil userNameUtil) { attachManager.setUserInfoBuilder(null); } //////////////////////////////////////////////// // end methods used by DS //////////////////////////////////////////////// @Override public WebSocketResponse receive(AgentRequest request) { // Sanity check. We should never get requests outside our action domain. if (!ACTION_NAME.equals(request.getAction())) { logger.severe("Received action '" + request.getAction() + "' for receiver '" + ACTION_NAME + "'"); return new WebSocketResponse(request.getSequenceId(), ResponseType.ERROR); } String vmId = request.getJvmId(); String actionStr = request.getParam(BytemanRequest.ACTION_PARAM_NAME); String portStr = request.getParam(BytemanRequest.LISTEN_PORT_PARAM_NAME); String vmPidStr = request.getParam(BytemanRequest.VM_PID_PARAM_NAME); RequestAction action; int vmPid; int port; try { action = RequestAction.fromIntString(actionStr); } catch (IllegalArgumentException e) { logger.log(Level.WARNING, "Illegal action received", e); return error(request.getSequenceId()); } if ((vmPid = tryParseInt(vmPidStr, "VM pid not a number!", ILLEGAL_INT_VAL)) == ILLEGAL_INT_VAL) { return error(request.getSequenceId()); } if ((port = tryParseInt(portStr, "Listen port not an integer!", ILLEGAL_PORT)) == ILLEGAL_PORT) { return error(request.getSequenceId()); } if (!isPortValid(port)) { logger.warning("Listen port is invalid. Got value '" + port + "'"); return error(request.getSequenceId()); } logger.fine("Processing request for vmId: " + vmId + ", pid: " + vmPid + ", Action: " + action + ", port: " + portStr); WebSocketResponse response; switch(action) { case LOAD_RULES: String rule = request.getParam(BytemanRequest.RULE_PARAM_NAME); response = attachAndLoadRules(port, new VmId(vmId), vmPid, rule, request.getSequenceId()); break; case UNLOAD_RULES: response = unloadRules(port, new VmId(vmId), request.getSequenceId()); break; default: logger.warning("Unknown action: " + action); return error(request.getSequenceId()); } return response; } private boolean isPortValid(int port) { if (port <= 0) { // only agent not attached is a valid but negative value return port == BytemanRequest.NOT_ATTACHED_PORT; } return true; } private int tryParseInt(String intStr, String msg, int defaultVal) { try { return Integer.parseInt(intStr); } catch (NumberFormatException e) { logger.log(Level.WARNING, msg + " Param was '" + intStr + "'", e); return defaultVal; } } private WebSocketResponse attachAndLoadRules(int listenPort, VmId vmId, int vmPid, String bytemanRules, long sequenceId) { int actualListenPort = attachAgentIfRequired(listenPort, vmId, vmPid); if (actualListenPort == BytemanRequest.NOT_ATTACHED_PORT) { logger.log(Level.WARNING, "Failed to attach byteman agent. Cannot load rules."); return error(sequenceId); } return loadRules(actualListenPort, vmId, bytemanRules, sequenceId); } private int attachAgentIfRequired(int listenPort, VmId vmId, int vmPid) { if (isAgentAttached(listenPort)) { return listenPort; } int actualListenPort = BytemanRequest.NOT_ATTACHED_PORT; VmBytemanStatus status = attachManager.attachBytemanToVm(vmId, vmPid); if (status != null) { actualListenPort = status.getListenPort(); } return actualListenPort; } private boolean isAgentAttached(int listenPort) { // if we come here with a negative port value we know the agent has not // yet been attached. return listenPort != BytemanRequest.NOT_ATTACHED_PORT; } private WebSocketResponse loadRules(int listenPort, VmId vmId, String bytemanRules, final long sequence) { Submit submit = getSubmit(listenPort); try { List<ScriptText> existingScripts = submit.getAllScripts(); if (existingScripts.size() > 0) { String deleteResult = submit.deleteAllRules(); logger.fine("Deleted rules with result: " + deleteResult); } List<InputStream> list = Arrays.<InputStream>asList(new ByteArrayInputStream(bytemanRules.getBytes(Charset.forName("UTF-8")))); String addRulesResult = submit.addRulesFromResources(list); logger.info("Added byteman rules for VM with id '" + vmId.get() + "' with result: " + addRulesResult); VmBytemanStatus status = new VmBytemanStatus(writerId.getWriterID()); status.setListenPort(listenPort); status.setRule(bytemanRules); status.setTimeStamp(System.currentTimeMillis()); status.setJvmId(vmId.get()); vmBytemanDao.addBytemanStatus(status); return ok(sequence); } catch (Exception e) { logger.log(Level.WARNING, "Failed to submit byteman rules", e); return error(sequence); } } private WebSocketResponse unloadRules(int listenPort, VmId vmId, long sequence) { Submit submit = getSubmit(listenPort); try { List<ScriptText> list = submit.getAllScripts(); // Avoid no scripts to delete errors if (!list.isEmpty()) { String deleteAllResult = submit.deleteAllRules(); logger.info("Removed all byteman rules for VM with id '" + vmId.get() + "' with result: " + deleteAllResult); // update the corresponding status in storage VmBytemanStatus newStatus = new VmBytemanStatus(writerId.getWriterID()); newStatus.setListenPort(listenPort); newStatus.setRule(null); newStatus.setTimeStamp(System.currentTimeMillis()); newStatus.setJvmId(vmId.get()); vmBytemanDao.addBytemanStatus(newStatus); } return ok(sequence); } catch (Exception e) { logger.log(Level.WARNING, "Failed to delete rules", e); return error(sequence); } } private WebSocketResponse error(long sequence) { return new WebSocketResponse(sequence, ResponseType.ERROR); } private WebSocketResponse ok(long sequence) { return new WebSocketResponse(sequence, ResponseType.OK); } protected Submit getSubmit(int listenPort) { return new Submit(null /* localhost */, listenPort); } }