/* * RHQ Management Platform * Copyright (C) 2005-2013 Red Hat, Inc. * All rights reserved. * * This program 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 version 2 of the License. * * This program 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 this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.rhq.enterprise.server.core; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.util.Date; import java.util.List; import java.util.Properties; import java.util.jar.JarEntry; import java.util.jar.JarFile; import javax.ejb.Asynchronous; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.interceptor.ExcludeDefaultInterceptors; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.NonUniqueResultException; import javax.persistence.PersistenceContext; import javax.persistence.Query; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.maven.artifact.versioning.ComparableVersion; import org.rhq.core.clientapi.server.core.AgentVersion; import org.rhq.core.clientapi.server.core.PingRequest; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.authz.Permission; import org.rhq.core.domain.common.composite.SystemSetting; import org.rhq.core.domain.criteria.AgentCriteria; import org.rhq.core.domain.install.remote.AgentInstall; import org.rhq.core.domain.measurement.AvailabilityType; import org.rhq.core.domain.resource.Agent; import org.rhq.core.domain.resource.composite.AgentLastAvailabilityPingComposite; import org.rhq.core.domain.server.PersistenceUtility; import org.rhq.core.domain.util.PageControl; import org.rhq.core.domain.util.PageList; import org.rhq.core.util.MessageDigestGenerator; import org.rhq.core.util.exception.ThrowableUtil; import org.rhq.core.util.file.FileUtil; import org.rhq.core.util.obfuscation.Obfuscator; import org.rhq.core.util.stream.StreamUtil; import org.rhq.enterprise.server.RHQConstants; import org.rhq.enterprise.server.agentclient.AgentClient; import org.rhq.enterprise.server.auth.SubjectManagerLocal; import org.rhq.enterprise.server.authz.AuthorizationManagerLocal; import org.rhq.enterprise.server.authz.PermissionException; import org.rhq.enterprise.server.authz.RequiredPermission; import org.rhq.enterprise.server.cloud.FailoverListManagerLocal; import org.rhq.enterprise.server.core.comm.ServerCommunicationsServiceMBean; import org.rhq.enterprise.server.core.comm.ServerCommunicationsServiceUtil; import org.rhq.enterprise.server.measurement.AvailabilityManagerLocal; import org.rhq.enterprise.server.system.SystemManagerLocal; import org.rhq.enterprise.server.util.CriteriaQueryGenerator; import org.rhq.enterprise.server.util.CriteriaQueryRunner; import org.rhq.enterprise.server.util.LookupUtil; import org.rhq.enterprise.server.util.concurrent.AvailabilityReportSerializer; /** * Manages the access to {@link Agent} objects.<br> * <br> * Some of these methods need to execute as fast as possible. So, @ExcludeDefaultInterceptors has been added * to all methods that don't explicitly need a permission check (those without Subject as the first parameter). * * @author John Mazzitelli */ @Stateless public class AgentManagerBean implements AgentManagerLocal, AgentManagerRemote { private static final Log LOG = LogFactory.getLog(AgentManagerBean.class); @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME) private EntityManager entityManager; @EJB //@IgnoreDependency private FailoverListManagerLocal failoverListManager; @EJB //@IgnoreDependency private AvailabilityManagerLocal availabilityManager; @EJB private AgentManagerLocal agentManager; @EJB private SystemManagerLocal systemManager; @EJB //@IgnoreDependency private SubjectManagerLocal subjectManager; @EJB private AuthorizationManagerLocal authorizationManager; // constants used for the agent update version file private static final String RHQ_SERVER_VERSION = "rhq-server.version"; private static final String RHQ_SERVER_BUILD_NUMBER = "rhq-server.build-number"; private static final String RHQ_AGENT_LATEST_VERSION = "rhq-agent.latest.version"; private static final String RHQ_AGENT_LATEST_BUILD_NUMBER = "rhq-agent.latest.build-number"; private static final String RHQ_AGENT_LATEST_MD5 = "rhq-agent.latest.md5"; private static final String RHQ_AGENT_SUPPORTED_BUILDS = "rhq-agent.supported.builds"; @ExcludeDefaultInterceptors public void createAgent(Agent agent) { entityManager.persist(agent); if (LOG.isDebugEnabled()) { LOG.debug("Persisted new agent: " + agent); } } @Override @RequiredPermission(Permission.MANAGE_INVENTORY) public AgentInstall getAgentInstallByAgentName(Subject user, String agentName) { if (agentName == null) { return null; } Query q = entityManager.createNamedQuery(AgentInstall.QUERY_FIND_BY_NAME); q.setParameter("agentName", agentName); try { AgentInstall ai; ai = (AgentInstall) q.getSingleResult(); entityManager.detach(ai); deobfuscateAgentInstall(ai); return ai; } catch (NoResultException e) { return null; } catch (NonUniqueResultException e) { return null; } } @Override @RequiredPermission(Permission.MANAGE_INVENTORY) public AgentInstall updateAgentInstall(Subject user, AgentInstall agentInstall) { // make sure we encode the password before persisting it obfuscateAgentInstall(agentInstall); if (agentInstall.getId() == 0) { // Caller gave us an agentInstall without an ID - see if there is already an entity with the given agent name. // If there is one already with the given agent name then update that record, overlaying it with the data from agentInstall. // If there is nothing with the given agent name, persist a new record with the data in agentInstall. // Note that if the caller didn't give us either an install ID or an agent name, we still persist the entity // with the expectation that the new ID will be used later by a new agent (new agent will register with this new ID and // will tell us what agent name it wants to use). AgentInstall existing = getAgentInstallByAgentName(user, agentInstall.getAgentName()); if (existing != null) { existing.overlay(agentInstall); // note: "existing" is detached agentInstall = entityManager.merge(existing); } else { entityManager.persist(agentInstall); } } else { // Caller gave us an agentInstall with an ID. See if there is already an entity with the given ID. // If there is an entity with the given ID, but that entity's agentName does not match the agentName given in agentInstall, // abort - one agent can't change another agent's install data. // If there is an entity with the given ID and the agentName matches then update that record, overlaying it with data from agentInstall. // If there is no entity with the given ID then throw an error. AgentInstall existing = entityManager.find(AgentInstall.class, agentInstall.getId()); if (existing != null) { if (agentInstall.getAgentName() != null) { if (existing.getAgentName() != null && !agentInstall.getAgentName().equals(existing.getAgentName())) { throw new IllegalStateException("Updating agent install ID [" + agentInstall.getId() + "] with a mismatched agent name is not allowed"); } // It is possible some past installs were aborted, or this agent is getting reinstalled. // This cases like this, we'll have duplicate rows with the same agent name - this just deletes // those older rows since this new agent supercedes the other ones. Query q = entityManager.createNamedQuery(AgentInstall.QUERY_FIND_BY_NAME); q.setParameter("agentName", agentInstall.getAgentName()); List<AgentInstall> otherAgentInstalls = q.getResultList(); if (otherAgentInstalls != null) { for (AgentInstall otherAgentInstall : otherAgentInstalls) { if (otherAgentInstall.getId() != agentInstall.getId()) { entityManager.remove(otherAgentInstall); } } } } existing.overlay(agentInstall); // modify the attached hibernate entity agentInstall = existing; } else { throw new IllegalStateException("Agent install ID [" + agentInstall.getId() + "] does not exist. Cannot update install info for agent [" + agentInstall.getAgentName() + "]"); } } // don't allow empty strings in credentials, use null String s = agentInstall.getSshUsername(); if (s != null && s.trim().length() == 0) { agentInstall.setSshUsername(null); } s = agentInstall.getSshPassword(); if (s != null && s.trim().length() == 0) { agentInstall.setSshPassword(null); } // let the caller have the decoded data - we need to flush and detach the entity so we can deobfuscate PW and not push it to DB entityManager.flush(); entityManager.detach(agentInstall); deobfuscateAgentInstall(agentInstall); return agentInstall; } private void obfuscateAgentInstall(AgentInstall ai) { try { String pw = ai.getSshPassword(); if (pw != null && pw.length() > 0) { ai.setSshPassword(Obfuscator.encode(pw)); } } catch (Exception e) { ai.setSshPassword(""); LOG.debug("Failed to obfuscate password for agent [" + ai.getAgentName() + "]. Will be emptied."); } } private void deobfuscateAgentInstall(AgentInstall ai) { try { String pw = ai.getSshPassword(); if (pw != null && pw.length() > 0) { ai.setSshPassword(Obfuscator.decode(pw)); } } catch (Exception e) { ai.setSshPassword(""); LOG.debug("Failed to deobfuscate password for agent [" + ai.getAgentName() + "]. Will be emptied."); } } @ExcludeDefaultInterceptors public void deleteAgent(Agent agent) { agent = entityManager.find(Agent.class, agent.getId()); failoverListManager.deleteServerListsForAgent(agent); entityManager.remove(agent); Query q = entityManager.createNamedQuery(AgentInstall.QUERY_DELETE_BY_NAME); q.setParameter("agentName", agent.getName()); q.executeUpdate(); destroyAgentClient(agent); LOG.info("Removed agent: " + agent); } @ExcludeDefaultInterceptors public void destroyAgentClient(Agent agent) { ServerCommunicationsServiceMBean bootstrap = ServerCommunicationsServiceUtil.getService(); try { // note, this destroys the KnownAgentClient on this HA node only. It will leave other HA nodes "dirty", but // that is OK as there is logic in place to handle dirty entries. bootstrap.destroyKnownAgentClient(agent); if (LOG.isDebugEnabled()) { LOG.debug("agent client destroyed for agent: " + agent); } } catch (Exception e) { // certain unit tests won't create the agentClient LOG.warn("Could not destroy agent client for agent [" + agent + "]: " + ThrowableUtil.getAllMessages(e)); } } @ExcludeDefaultInterceptors public Agent updateAgent(Agent agent) { agent = entityManager.merge(agent); if (LOG.isDebugEnabled()) { LOG.debug("Updated agent: " + agent); } return agent; } @ExcludeDefaultInterceptors public AgentClient getAgentClient(Agent agent) { AgentClient client = null; try { ServerCommunicationsServiceMBean bootstrap = ServerCommunicationsServiceUtil.getService(); client = bootstrap.getKnownAgentClient(agent); if (client != null) { // We assume the caller is asking for a client so it can send the agent messages, // so let's start the sender automatically for the caller so it doesn't need to remember to do it client.startSending(); } else { if (LOG.isDebugEnabled()) { LOG.debug("There is no agent client for agent: " + agent); } } } catch (Throwable t) { if (LOG.isDebugEnabled()) { LOG.debug("Could not get agent client for agent: " + agent, t); } } return client; } /* * Removed ExcludeDefaultInterceptors annotation to enable permission and session check by the container. */ public AgentClient getAgentClient(Subject subject, int resourceId) { Agent agent = getAgentByResourceId(subject, resourceId); if (agent == null) { if (LOG.isDebugEnabled()) { LOG.debug("Resource [" + resourceId + "] does not exist or has no agent assigned"); } return null; } return getAgentClient(agent); } // Let the agent continue shutting down without waiting for a lengthy backfill. Also, this is called // only from the agent, ignore the standard interceptors @Asynchronous @ExcludeDefaultInterceptors public void agentIsShuttingDown(String agentName) { Agent downedAgent = getAgentByName(agentName); ServerCommunicationsServiceMBean server_bootstrap = ServerCommunicationsServiceUtil.getService(); server_bootstrap.removeDownedAgent(downedAgent.getRemoteEndpoint()); LOG.info("Agent with name [" + agentName + "] just went down"); agentManager.backfillAgentInNewTransaction(subjectManager.getOverlord(), agentName, downedAgent.getId()); return; } @ExcludeDefaultInterceptors public void agentIsAlive(Agent agent) { // This method needs to be very fast. It is currently designed to be called from // the security token command authenticator. It calls this method every time // a message comes in and the auth token needs to be verified. This method takes // a detached agent rather than an agent name because I don't want to waste a round // trip looking up the agent from the DB when I already know the caller (the // authenticator) just got the agent object. // When the authenticator calls us, it means it just got a message from the agent, // so we can use this to our benefit. Since we got a message from the given agent, // we know it is up! So this is a simple way for us to detect agents coming online // without us having to periodically poll each and every agent. try { ServerCommunicationsServiceMBean server_comm = ServerCommunicationsServiceUtil.getService(); server_comm.addStartedAgent(agent); } catch (Exception e) { LOG.info("Cannot flag the agent as started for some reason", e); } return; } @SuppressWarnings("unchecked") @ExcludeDefaultInterceptors public void checkForSuspectAgents() { LOG.debug("Checking to see if there are agents that we suspect are down..."); long maximumQuietTimeAllowed = 300000L; try { String prop = systemManager.getUnmaskedSystemSettings(true).get(SystemSetting.AGENT_MAX_QUIET_TIME_ALLOWED); if (prop != null) { maximumQuietTimeAllowed = Long.parseLong(prop); } } catch (Exception e) { LOG.warn("Agent quiet time config is invalid in DB, defaulting to: " + maximumQuietTimeAllowed, e); } List<AgentLastAvailabilityPingComposite> records; long nowEpoch = System.currentTimeMillis(); Query q = entityManager.createNamedQuery(Agent.QUERY_FIND_ALL_SUSPECT_AGENTS); q.setParameter("dateThreshold", nowEpoch - maximumQuietTimeAllowed); records = q.getResultList(); ServerCommunicationsServiceMBean serverComm = null; for (AgentLastAvailabilityPingComposite record : records) { long lastReport = record.getLastAvailabilityPing(); long timeSinceLastReport = nowEpoch - lastReport; // Only show this message a few times so we do not flood the log with the same message if the agent is down a long time // we show it as soon as we detect it going down (within twice max quiet time allowed) or we show it // after every 6th hour it's detected to be down (again, within twice max quiet time). Effectively, you'll see // this message appear about 4 times per 6-hours for a downed agent if using the default max quiet time. // Note that in here we also make sure the agent client is shutdown. We do it here because, even if the agent // was already backfilled, we still want to do this in case somehow the client was started again. if ((timeSinceLastReport % 21600000L) < (maximumQuietTimeAllowed * 2L)) { if (serverComm == null) { serverComm = ServerCommunicationsServiceUtil.getService(); } serverComm.removeDownedAgent(record.getRemoteEndpoint()); } // we can avoid doing this over and over again for agents that are down a long time by seeing if it's // already backfilled. Note that we do not log the above warn message down here in this if-statement, // because I think we still want to log that we think an agent is down periodically. // If it turns out we do not want to be that noisy, just move that warn message down in here so we only ever log // about a downed agent once, at the time it is first backfilled. if (!record.isBackFilled()) { LOG.info("Have not heard from agent [" + record.getAgentName() + "] since [" + new Date(record.getLastAvailabilityPing()) + "]. Will be backfilled since we suspect it is down"); agentManager.backfillAgentInNewTransaction(subjectManager.getOverlord(), record.getAgentName(), record.getAgentId()); } } LOG.debug("Finished checking for suspected agents"); return; } @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void backfillAgentInNewTransaction(Subject subject, String agentName, int agentId) { // make sure we lock out all processing of any availability reports that might come our way to avoid concurrency // problems AvailabilityReportSerializer.getSingleton().lock(agentName); try { // This marks the top-level platform DOWN since we have not heard from it and all // child resources UNKNOWN since they are not reporting and may very well be up availabilityManager.updateAgentResourceAvailabilities(agentId, AvailabilityType.DOWN, AvailabilityType.UNKNOWN); // This marks the Agent as backfilled=true. Although doing this in its own transaction means the // agent will be reflected as backfilled before the resource availability changes are committed, that // should not matter, as it affects only availability reporting which 1) is locked out (see a few line above) // and 2) at worst would only request a full avail report anyway, when detecting a backfilled agent. // What this buys is it shortens the locking on the agent table, because backfilling a large inventory // can take time. Performing this after the backfill to minimize the window between the commits as // much as possible. agentManager.setAgentBackfilledInNewTransaction(agentId, true); } finally { AvailabilityReportSerializer.getSingleton().unlock(agentName); } } @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void setAgentBackfilledInNewTransaction(int agentId, boolean backfilled) { Query query = entityManager.createNamedQuery(Agent.QUERY_SET_AGENT_BACKFILLED); query.setParameter("agentId", agentId); query.setParameter("backfilled", backfilled); query.executeUpdate(); } public boolean isAgentBackfilled(int agentId) { // query returns 0 if the agent's platform is DOWN (or does not exist), 1 if not Query query = entityManager.createNamedQuery(Agent.QUERY_IS_AGENT_BACKFILLED); query.setParameter("agentId", agentId); Long backfilledCount = (Long) query.getSingleResult(); return backfilledCount != 0L; } @SuppressWarnings("unchecked") @ExcludeDefaultInterceptors public List<Agent> getAllAgents() { return entityManager.createNamedQuery(Agent.QUERY_FIND_ALL).getResultList(); } /** Methods with page control are typically accessed by the GUI, as such apply permission check. */ @SuppressWarnings("unchecked") @RequiredPermission(Permission.MANAGE_INVENTORY) public PageList<Agent> getAgentsByServer(Subject subject, Integer serverId, PageControl pageControl) { pageControl.initDefaultOrderingField("a.name"); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, Agent.QUERY_FIND_BY_SERVER, pageControl); Query countQuery = PersistenceUtility.createCountQuery(entityManager, Agent.QUERY_FIND_BY_SERVER); query.setParameter("serverId", serverId); countQuery.setParameter("serverId", serverId); long count = (Long) countQuery.getSingleResult(); List<Agent> results = query.getResultList(); return new PageList<Agent>(results, (int) count, pageControl); } @ExcludeDefaultInterceptors public int getAgentCount() { return ((Number) entityManager.createNamedQuery(Agent.QUERY_COUNT_ALL).getSingleResult()).intValue(); } @ExcludeDefaultInterceptors public Agent getAgentByAgentToken(String token) { Agent agent; try { Query query = entityManager.createNamedQuery(Agent.QUERY_FIND_BY_AGENT_TOKEN); query.setParameter("agentToken", token); agent = (Agent) query.getSingleResult(); } catch (NoResultException e) { if (LOG.isDebugEnabled()) { LOG.debug("Failed to lookup agent - none exist with token [" + token + "] : " + e); } agent = null; } return agent; } @ExcludeDefaultInterceptors public Agent getAgentByName(String agentName) { Agent agent; try { Query query = entityManager.createNamedQuery(Agent.QUERY_FIND_BY_NAME); query.setParameter("name", agentName); agent = (Agent) query.getSingleResult(); } catch (NoResultException e) { if (LOG.isDebugEnabled()) { LOG.debug("Failed to lookup agent - none exist with name [" + agentName + "] : " + e); } agent = null; } return agent; } @ExcludeDefaultInterceptors public Agent getAgentByID(int agentId) { Agent agent = entityManager.find(Agent.class, agentId); return agent; } @ExcludeDefaultInterceptors public Agent getAgentByAddressAndPort(String address, int port) { Agent agent; try { Query query = entityManager.createNamedQuery(Agent.QUERY_FIND_BY_ADDRESS_AND_PORT); query.setParameter("address", address); query.setParameter("port", port); agent = (Agent) query.getSingleResult(); } catch (NoResultException e) { if (LOG.isDebugEnabled()) { LOG.debug("Agent not found with address/port: " + address + "/" + port); } agent = null; } return agent; } /* * Removed ExcludeDefaultInterceptors annotation to enable permission and session check by the container. */ public Agent getAgentByResourceId(Subject subject, int resourceId) { Agent agent; try { //insert logged in check and view resources perm check as method called from GWT*Service if ((subject != null) && (!authorizationManager.hasGlobalPermission(subject, Permission.MANAGE_SETTINGS))) { throw new PermissionException("Can not get agent details - " + subject + " lacks " + Permission.MANAGE_SETTINGS + " for resource[id=" + resourceId + "]"); } Query query = entityManager.createNamedQuery(Agent.QUERY_FIND_BY_RESOURCE_ID); query.setParameter("resourceId", resourceId); agent = (Agent) query.getSingleResult(); } catch (NoResultException e) { if (LOG.isDebugEnabled()) { LOG.debug("Failed to lookup agent for resource with ID of [" + resourceId + "] : " + e); } agent = null; } return agent; } @ExcludeDefaultInterceptors public Integer getAgentIdByResourceId(int resourceId) { Integer agentId; try { Query query = entityManager.createNamedQuery(Agent.QUERY_FIND_AGENT_ID_BY_RESOURCE_ID); query.setParameter("resourceId", resourceId); agentId = (Integer) query.getSingleResult(); } catch (NoResultException e) { if (LOG.isDebugEnabled()) { LOG.debug("Failed to lookup agent for resource with ID of [" + resourceId + "] : " + e); } agentId = null; } return agentId; } @ExcludeDefaultInterceptors public Integer getAgentIdByName(String agentName) { Integer agentId; try { Query query = entityManager.createNamedQuery(Agent.QUERY_FIND_AGENT_ID_BY_NAME); query.setParameter("name", agentName); agentId = (Integer) query.getSingleResult(); } catch (NoResultException e) { if (LOG.isDebugEnabled()) { LOG.debug("Failed to lookup agent for name of [" + agentName + "] : " + e); } agentId = null; } return agentId; } @ExcludeDefaultInterceptors public Integer getAgentIdByScheduleId(int scheduleId) { Integer agentId; try { Query query = entityManager.createNamedQuery(Agent.QUERY_FIND_AGENT_ID_BY_SCHEDULE_ID); query.setParameter("scheduleId", scheduleId); agentId = (Integer) query.getSingleResult(); } catch (NoResultException e) { if (LOG.isDebugEnabled()) { LOG.debug("Failed to lookup agent for resource with ID of [" + scheduleId + "] : " + e); } agentId = null; } return agentId; } @ExcludeDefaultInterceptors public AgentVersionCheckResults isAgentVersionSupported(AgentVersion agentVersionInfo) { try { Properties properties = getAgentUpdateVersionFileContent(); String supportedAgentBuilds = properties.getProperty(RHQ_AGENT_SUPPORTED_BUILDS); // this is optional String latestAgentVersion = properties.getProperty(RHQ_AGENT_LATEST_VERSION); String latestAgentBuild = properties.getProperty(RHQ_AGENT_LATEST_BUILD_NUMBER); if (latestAgentVersion == null) { throw new NullPointerException("no agent version in file"); } boolean isSupported; //if no list of supportedBuilds provident then, if (supportedAgentBuilds == null || supportedAgentBuilds.isEmpty()) { // we weren't given a regex of supported versions, make a simple string equality test on latest agent version ComparableVersion agent = new ComparableVersion(agentVersionInfo.getVersion()); ComparableVersion server = new ComparableVersion(latestAgentVersion); isSupported = agent.equals(server); } else { // we were given a regex of supported builds, check the agent build to see if it matches the regex isSupported = agentVersionInfo.getBuild().matches(supportedAgentBuilds); } return new AgentVersionCheckResults(isSupported, new AgentVersion(latestAgentVersion, latestAgentBuild)); } catch (Exception e) { LOG.warn("Cannot determine if agent version [" + agentVersionInfo + "] is supported. Cause: " + e); return new AgentVersionCheckResults(false, null); // assume we can't talk to it } } @ExcludeDefaultInterceptors public File getAgentUpdateVersionFile() throws Exception { File agentDownloadDir = getAgentDataDownloadDir(); File versionFile = new File(agentDownloadDir, "rhq-server-agent-versions.properties"); File binaryFile = getAgentUpdateBinaryFile(); Boolean needVersionFile = FileUtil.isNewer(binaryFile, versionFile); if (needVersionFile == null || needVersionFile.booleanValue()) { // we do not have the version properties file yet or it must be regenerated; let's extract some info and create it StringBuilder serverVersionInfo = new StringBuilder(); // first, get the server version info (by asking our server for the info) CoreServerMBean coreServer = LookupUtil.getCoreServer(); serverVersionInfo.append(RHQ_SERVER_VERSION + '=').append(coreServer.getVersion()).append('\n'); serverVersionInfo.append(RHQ_SERVER_BUILD_NUMBER + '=').append(coreServer.getBuildNumber()).append('\n'); // if there are supported agent versions, get it (this is a regex that is to match agent versions that are supported) String supportedAgentBuilds = coreServer.getProductInfo().getSupportedAgentBuilds(); if (supportedAgentBuilds != null && supportedAgentBuilds.length() > 0) { serverVersionInfo.append(RHQ_AGENT_SUPPORTED_BUILDS + '=').append(supportedAgentBuilds) .append('\n'); } // calculate the MD5 of the agent update binary file String md5Property = RHQ_AGENT_LATEST_MD5 + '=' + MessageDigestGenerator.getDigestString(binaryFile) + '\n'; // second, get the agent version info (by peeking into the agent update binary jar) JarFile binaryJarFile = new JarFile(binaryFile); try { JarEntry binaryJarFileEntry = binaryJarFile.getJarEntry("rhq-agent-update-version.properties"); InputStream binaryJarFileEntryStream = binaryJarFile.getInputStream(binaryJarFileEntry); // now write the server and agent version info in our internal version file our servlet will use FileOutputStream versionFileOutputStream = new FileOutputStream(versionFile); try { versionFileOutputStream.write(serverVersionInfo.toString().getBytes()); versionFileOutputStream.write(md5Property.getBytes()); StreamUtil.copy(binaryJarFileEntryStream, versionFileOutputStream, false); } finally { try { versionFileOutputStream.close(); } catch (Exception e) { } try { binaryJarFileEntryStream.close(); } catch (Exception e) { } } } finally { binaryJarFile.close(); } } return versionFile; } @ExcludeDefaultInterceptors public Properties getAgentUpdateVersionFileContent() throws Exception { FileInputStream stream = new FileInputStream(getAgentUpdateVersionFile()); try { Properties props = new Properties(); props.load(stream); return props; } finally { try { stream.close(); } catch (Exception e) { } } } @ExcludeDefaultInterceptors public File getAgentUpdateBinaryFile() throws Exception { File agentDownloadDir = getAgentDownloadDir(); for (File file : agentDownloadDir.listFiles()) { if (file.getName().endsWith(".jar")) { return file; } } throw new FileNotFoundException("Missing agent update binary in [" + agentDownloadDir + "]"); } /** * The directory on the server's file system where the agent update binary file is found. * * @return directory where the agent binary downloads are found * * @throws Exception if could not determine the location or it does not exist */ // SHOULD BE PRIVATE AND REMOVED FROM LOCAL INTERFACE - NOTHING (OTHER THAN THIS CLASS ITSELF) SHOULD USE THIS public File getAgentDownloadDir() throws Exception { File earDir = LookupUtil.getCoreServer().getEarDeploymentDir(); File agentDownloadDir = new File(earDir, "rhq-downloads/rhq-agent"); if (!agentDownloadDir.exists()) { throw new FileNotFoundException("Missing agent downloads directory at [" + agentDownloadDir + "]"); } return agentDownloadDir; } /** * The directory on the server's file system where the agent update version file is found. * * @return directory where the agent version file is found * * @throws Exception if could not determine the location or it does not exist */ private File getAgentDataDownloadDir() throws Exception { File earDir = LookupUtil.getCoreServer().getJBossServerDataDir(); File agentDownloadDir = new File(earDir, "rhq-downloads/rhq-agent"); if (!agentDownloadDir.exists()) { agentDownloadDir.mkdirs(); if (!agentDownloadDir.exists()) { throw new FileNotFoundException("Missing agent data downloads directory at [" + agentDownloadDir + "]"); } } return agentDownloadDir; } @Override public PingRequest handlePingRequest(PingRequest request) { // set the ping time without delay, so the return time is as close to the request time as possible, // for clock sync reasons. long now = System.currentTimeMillis(); if (request.isRequestUpdateAvailability()) { request.setReplyAgentIsBackfilled(updateLastAvailabilityPing(request.getAgentName(), now)); request.setReplyUpdateAvailability(true); } if (request.isRequestServerTimestamp()) { request.setReplyServerTimestamp(now); } return request; } /** * @return true if the agent is currently backfilled, false otherwise */ private boolean updateLastAvailabilityPing(String agentName, long now) { /* * There are two cases here: The agent is backfilled (rare) or not backfilled (typical). * In both cases update the ping time. If it is backfilled then reflect that fact * in the response so the agent can correct the situation. Note that this takes care of * the unusual case of BZ 1094540. */ Query query = entityManager.createNamedQuery(Agent.QUERY_UPDATE_LAST_AVAIL_PING); query.setParameter("now", now); query.setParameter("agentName", agentName); int numUpdates = query.executeUpdate(); if (0 == numUpdates) { query = entityManager.createNamedQuery(Agent.QUERY_UPDATE_LAST_AVAIL_PING_FORCE); query.setParameter("now", now); query.setParameter("agentName", agentName); query.executeUpdate(); return true; } return false; } @ExcludeDefaultInterceptors public Boolean pingAgentByResourceId(Subject subject, int resourceId) { Boolean pingResults = Boolean.FALSE; Agent agent; try { //insert call to method doing logged in check and view resources perm check as method calld from GWT*Service agent = getAgentByResourceId(subject, resourceId); //now ping AgentClient client = getAgentClient(agent); pingResults = client.pingService(5000L); } catch (NoResultException e) { if (LOG.isDebugEnabled()) { LOG.debug("Failed to lookup agent for resource with ID of [" + resourceId + "] : " + e); } agent = null; } return pingResults; } @RequiredPermission(Permission.MANAGE_SETTINGS) public PageList<Agent> findAgentsByCriteria(Subject subject, AgentCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); CriteriaQueryRunner<Agent> runner = new CriteriaQueryRunner<Agent>(criteria, generator, entityManager); return runner.execute(); } @RequiredPermission(Permission.MANAGE_SETTINGS) public void deleteAgent(Subject subject, Agent agent) { deleteAgent(agent); } }