/*
* RHQ Management Platform
* Copyright (C) 2005-2008 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, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* 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 and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser 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.core.domain.resource;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.jetbrains.annotations.NotNull;
import org.rhq.core.domain.cloud.AffinityGroup;
import org.rhq.core.domain.cloud.Server;
/**
* A JON agent.
*/
@Entity(name = "Agent")
@NamedQueries({
@NamedQuery(name = Agent.QUERY_FIND_BY_NAME, query = "SELECT a FROM Agent a WHERE a.name = :name"),
@NamedQuery(name = Agent.QUERY_FIND_BY_ADDRESS_AND_PORT, query = "SELECT a FROM Agent a WHERE a.address = :address AND a.port = :port"),
@NamedQuery(name = Agent.QUERY_FIND_BY_AGENT_TOKEN, query = "SELECT a FROM Agent a WHERE a.agentToken = :agentToken"),
@NamedQuery(name = Agent.QUERY_FIND_BY_RESOURCE_ID, query = "SELECT r.agent FROM Resource r WHERE r.id = :resourceId"),
@NamedQuery(name = Agent.QUERY_FIND_AGENT_ID_BY_RESOURCE_ID, query = "SELECT r.agent.id FROM Resource r WHERE r.id = :resourceId"),
@NamedQuery(name = Agent.QUERY_FIND_AGENT_ID_BY_NAME, query = "SELECT a.id FROM Agent a WHERE a.name = :name"),
@NamedQuery(name = Agent.QUERY_FIND_AGENT_ID_BY_SCHEDULE_ID, query = "SELECT r.agent.id FROM MeasurementSchedule sched JOIN sched.resource r WHERE sched.id = :scheduleId"),
@NamedQuery(name = Agent.QUERY_FIND_ALL, query = "SELECT a FROM Agent a"),
@NamedQuery(name = Agent.QUERY_FIND_BY_SERVER, query = "SELECT a FROM Agent a WHERE (a.server.id = :serverId OR :serverId IS NULL)"),
@NamedQuery(name = Agent.QUERY_REMOVE_SERVER_REFERENCE, query = "UPDATE Agent a SET a.server.id = NULL WHERE a.server.id = :serverId "),
@NamedQuery(name = Agent.QUERY_COUNT_ALL, query = "SELECT count(a.id) FROM Agent a"),
@NamedQuery(name = Agent.QUERY_FIND_RESOURCE_IDS_FOR_AGENT, query = "SELECT r.id FROM Resource r WHERE r.agent.id = :agentId"),
@NamedQuery(name = Agent.QUERY_FIND_AGENT_RESOURCE_ID_AGENT_ID, query = "" //
+ "SELECT r.id " //
+ "FROM Resource r " //
+ "WHERE r.resourceType.name = 'RHQ Agent' " //
+ "AND r.inventoryStatus = 'COMMITTED'" //
+ "AND r.agent.id = :agentId"),
@NamedQuery(name = Agent.QUERY_FIND_RESOURCE_IDS_WITH_AGENTS_BY_RESOURCE_IDS, query = "" //
+ "SELECT new org.rhq.core.domain.resource.composite.ResourceIdWithAgentComposite(r.id, r.agent) " //
+ " FROM Resource r " //
+ " WHERE r.id IN (:resourceIds)"),
@NamedQuery(name = Agent.QUERY_FIND_ALL_SUSPECT_AGENTS, query = "SELECT new org.rhq.core.domain.resource.composite.AgentLastAvailabilityPingComposite "
+ " ( "
+ " a.id,a.name,a.remoteEndpoint,a.lastAvailabilityPing,a.backFilled "
+ " ) "
+ " FROM Agent a " + " WHERE a.lastAvailabilityPing < :dateThreshold "),
@NamedQuery(name = Agent.QUERY_FIND_ALL_WITH_STATUS_BY_SERVER, query = "" //
+ "SELECT a.id " //
+ " FROM Agent a " //
+ " WHERE a.server.name = :serverName " //
+ " AND a.status <> 0 "), //
@NamedQuery(name = Agent.QUERY_FIND_ALL_WITH_STATUS, query = "" //
+ "SELECT a.id " //
+ " FROM Agent a " //
+ " WHERE a.status <> 0 "), //
@NamedQuery(name = Agent.QUERY_UPDATE_CLEAR_STATUS_BY_IDS, query = "" //
+ "UPDATE Agent a " //
+ " SET a.status = 0 " //
+ " WHERE a.id IN ( :agentIds ) "), //
@NamedQuery(name = Agent.QUERY_FIND_BY_AFFINITY_GROUP, query = "" //
+ "SELECT a " //
+ " FROM Agent a " //
+ " WHERE a.affinityGroup.id = :affinityGroupId "),
@NamedQuery(name = Agent.QUERY_FIND_WITHOUT_AFFINITY_GROUP, query = "" //
+ "SELECT a " //
+ " FROM Agent a " //
+ " WHERE a.affinityGroup IS NULL"), //
@NamedQuery(name = Agent.QUERY_SET_AGENT_BACKFILLED, query = "" //
+ "UPDATE Agent a " //
+ " SET a.backFilled = :backfilled " //
+ " WHERE a.id = :agentId " //
+ " AND a.backFilled <> :backfilled "), //
@NamedQuery(name = Agent.QUERY_IS_AGENT_BACKFILLED, query = "" //
+ "SELECT COUNT(a.id) " //
+ " FROM Agent a " //
+ " WHERE a.id = :agentId " //
+ " AND a.backFilled = true "), //
@NamedQuery(name = Agent.QUERY_UPDATE_STATUS_BY_RESOURCE, query = "" //
+ " UPDATE Agent a " //
+ " SET a.status = -1 " // negative numbers so that bitmask strategy does not conflict with this one
+ " WHERE a.status = 0 " // we only need the first guy to set it
+ " AND a.id = ( SELECT resA.id " // only update ourselves;
+ " FROM Resource res " //
+ " JOIN res.agent resA " //
+ " WHERE res.id = :resourceId ) "), //
@NamedQuery(name = Agent.QUERY_UPDATE_STATUS_BY_ALERT_DEFINITION, query = "" //
+ " UPDATE Agent a " //
+ " SET a.status = -1 " // negative numbers so that bitmask strategy does not conflict with this one
+ " WHERE a.status = 0 " // we only need the first guy to set it
+ " AND a.id = ( SELECT resA.id " // only update ourselves;
+ " FROM AlertDefinition ad " //
+ " JOIN ad.resource res " //
+ " JOIN res.agent resA " //
+ " WHERE ad.id = :alertDefinitionId ) "), //
@NamedQuery(name = Agent.QUERY_UPDATE_STATUS_BY_MEASUREMENT_BASELINE, query = "" //
+ " UPDATE Agent a " //
+ " SET a.status = -1 " // negative numbers so that bitmask strategy does not conflict with this one
+ " WHERE a.status = 0 " // we only need the first guy to set it
+ " AND a.id = ( SELECT resA.id " // only update ourselves;
+ " FROM MeasurementBaseline mb " //
+ " JOIN mb.schedule ms " //
+ " JOIN ms.resource res " //
+ " JOIN res.agent resA " //
+ " WHERE mb.id = :baselineId ) "), //
@NamedQuery(name = Agent.QUERY_UPDATE_STATUS_BY_AGENT, query = "" //
+ " UPDATE Agent a " //
+ " SET a.status = -1 " // negative numbers so that bitmask strategy does not conflict with this one
+ " WHERE a.status = 0 " // we only need the first guy to set it
+ " AND a.id = :agentId "), //
@NamedQuery(name = Agent.QUERY_UPDATE_STATUS_FOR_ALL, query = "" //
+ " UPDATE Agent a " //
+ " SET a.status = -1 " // negative numbers so that bitmask strategy does not conflict with this one
+ " WHERE a.status = 0 "), //
@NamedQuery(name = Agent.QUERY_UPDATE_LAST_AVAIL_REPORT, query = "" //
+ " UPDATE Agent a " //
+ " SET lastAvailabilityReport = :reportTime, backFilled = FALSE " //
+ " WHERE id = :agentId "), //
@NamedQuery(name = Agent.QUERY_UPDATE_LAST_AVAIL_PING, query = "" //
+ " UPDATE Agent a " //
+ " SET lastAvailabilityPing = :now " //
+ " WHERE name = :agentName AND backfilled = FALSE "), //
@NamedQuery(name = Agent.QUERY_UPDATE_LAST_AVAIL_PING_FORCE, query = "" //
+ " UPDATE Agent a " //
+ " SET lastAvailabilityPing = :now " //
+ " WHERE name = :agentName ") //
})
@SequenceGenerator(allocationSize = org.rhq.core.domain.util.Constants.ALLOCATION_SIZE, name = "RHQ_AGENT_ID_SEQ", sequenceName = "RHQ_AGENT_ID_SEQ")
@Table(name = "RHQ_AGENT")
public class Agent implements Serializable {
public static final long serialVersionUID = 1L;
public static final String QUERY_FIND_BY_NAME = "Agent.findByName";
public static final String QUERY_FIND_BY_ADDRESS_AND_PORT = "Agent.findByAddressAndPort";
public static final String QUERY_FIND_BY_AGENT_TOKEN = "Agent.findByAgentToken";
public static final String QUERY_FIND_BY_RESOURCE_ID = "Agent.findByResourceId";
public static final String QUERY_FIND_AGENT_ID_BY_RESOURCE_ID = "Agent.findAgentIdByResourceId";
public static final String QUERY_FIND_AGENT_ID_BY_NAME = "Agent.findAgentIdByName";
public static final String QUERY_FIND_AGENT_ID_BY_SCHEDULE_ID = "Agent.findAgentIdByScheduleId";
public static final String QUERY_FIND_ALL = "Agent.findAll";
public static final String QUERY_FIND_BY_SERVER = "Agent.findByServer";
public static final String QUERY_COUNT_ALL = "Agent.countAll";
public static final String QUERY_FIND_RESOURCE_IDS_FOR_AGENT = "Agent.findResourceIdsForAgent";
public static final String QUERY_FIND_AGENT_RESOURCE_ID_AGENT_ID = "Agent.findAgentResourceIdByAgentId";
public static final String QUERY_FIND_RESOURCE_IDS_WITH_AGENTS_BY_RESOURCE_IDS = "Agent.findResourceIdsWithAgentsByResourceIds";
public static final String QUERY_FIND_ALL_SUSPECT_AGENTS = "Agent.findAllSuspectAgents";
public static final String QUERY_FIND_BY_AFFINITY_GROUP = "Agent.findByAffinityGroup";
public static final String QUERY_FIND_WITHOUT_AFFINITY_GROUP = "Agent.findWithoutAffinityGroup";
public static final String QUERY_SET_AGENT_BACKFILLED = "Agent.setAgentBackfilled";
public static final String QUERY_IS_AGENT_BACKFILLED = "Agent.isAgentBackfilled";
// HA queries
public static final String QUERY_FIND_ALL_WITH_STATUS_BY_SERVER = "Agent.findAllWithStatusByServer";
public static final String QUERY_FIND_ALL_WITH_STATUS = "Agent.findAllWithStatus";
public static final String QUERY_UPDATE_CLEAR_STATUS_BY_IDS = "Agent.updateClearStatusByIds";
public static final String QUERY_REMOVE_SERVER_REFERENCE = "Agent.removeServerReference";
public static final String QUERY_UPDATE_STATUS_BY_RESOURCE = "Agent.updateStatusByResource";
public static final String QUERY_UPDATE_STATUS_BY_ALERT_DEFINITION = "Agent.updateStatusByAlertDefinition";
public static final String QUERY_UPDATE_STATUS_BY_MEASUREMENT_BASELINE = "Agent.updateStatusByMeasurementBasleine";
public static final String QUERY_UPDATE_STATUS_BY_AGENT = "Agent.updateStatusByAgent";
public static final String QUERY_UPDATE_STATUS_FOR_ALL = "Agent.updateStatusForAll";
public static final String QUERY_UPDATE_LAST_AVAIL_REPORT = "Agent.updateLastAvailReport";
public static final String QUERY_UPDATE_LAST_AVAIL_PING = "Agent.updateLastAvailPing";
public static final String QUERY_UPDATE_LAST_AVAIL_PING_FORCE = "Agent.updateLastAvailPingForce";
// this value is set, when authorized user wants to reset the token
public static final String SECURITY_TOKEN_RESET = "@#$reset$#@";
@Column(name = "ID", nullable = false)
@GeneratedValue(strategy = GenerationType.AUTO, generator = "RHQ_AGENT_ID_SEQ")
@Id
private int id;
@Column(name = "NAME", nullable = false)
private String name;
@Column(name = "ADDRESS", nullable = false)
private String address;
@Column(name = "PORT", nullable = false)
private int port;
@Column(name = "AGENTTOKEN", nullable = false)
private String agentToken;
@Column(name = "REMOTE_ENDPOINT")
private String remoteEndpoint;
@Column(name = "CTIME", nullable = false)
private long ctime = System.currentTimeMillis();
@Column(name = "MTIME", nullable = false)
private long mtime = System.currentTimeMillis();
@Column(name = "LAST_AVAILABILITY_REPORT")
private Long lastAvailabilityReport;
@Column(name = "LAST_AVAILABILITY_PING")
private Long lastAvailabilityPing;
@JoinColumn(name = "AFFINITY_GROUP_ID", referencedColumnName = "ID", nullable = true)
@ManyToOne
private AffinityGroup affinityGroup;
@JoinColumn(name = "SERVER_ID", referencedColumnName = "ID", nullable = true)
@ManyToOne
private Server server;
@Column(name = "STATUS", nullable = false)
private int status;
@Column(name = "BACKFILLED", nullable = false)
private boolean backFilled;
/**
* Creates a new instance of Agent
*/
public Agent() {
}
/**
* Constructor for {@link Agent}.
*
* @param name
* @param address
* @param port
* @param remoteEndpoint
* @param agentToken
*/
public Agent(@NotNull
String name, String address, int port, String remoteEndpoint, String agentToken) {
this.name = name;
this.address = address;
this.port = port;
this.remoteEndpoint = remoteEndpoint;
this.agentToken = agentToken;
}
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
/**
* The agent's name will usually, but is not required to, be the fully qualified domain name of the machine where
* the agent is running. In other words, the {@link #getAddress() address}. However, there is no technical reason
* why you cannot have an agent with a name such as "foo". In fact, there are use-cases where the agent name is not
* the same as the address (e.g. perhaps you expect the machine to periodically change hostnames due to DNS name
* changes and so you want to name it something more permanent).
*
* <p>Agent names must be unique - no two agents can have the same name.</p>
*
* @return the agent's unique name
*/
@NotNull
public String getName() {
return this.name;
}
public void setName(@NotNull
String name) {
this.name = name;
}
/**
* Returns the machine address that is used to connect to the agent. This can be either an IP address or a fully
* qualified host name.
*
* @return the name of the host where the agent is located
*/
@NotNull
public String getAddress() {
return this.address;
}
public void setAddress(@NotNull
String address) {
this.address = address;
}
/**
* Returns the port number that the agent is listening to when accepting messages from the server.
*
* @return server socket port being listened to by the agent
*/
public int getPort() {
return this.port;
}
public void setPort(int port) {
this.port = port;
}
/**
* Returns the token string that allows the agent to talk back to the server. If the agent provides an agent token
* different that this, the server will ignore the agent's messages. The agent gets a new token whenever it
* registers with the server.
*
* @return agent token string
*/
@NotNull
public String getAgentToken() {
return agentToken;
}
public void setAgentToken(@NotNull
String agentToken) {
this.agentToken = agentToken;
}
/**
* The remote endpoint is the full connection string that is to be used to connect to the agent. If <code>
* null</code>, a default protocol mechanism will be used. The remote endpoint will usually have both the
* {@link #getAddress() address} and {@link #getPort() port} encoded in it.
*
* @return the endpoint describing where and how to communicate with the agent
*/
public String getRemoteEndpoint() {
return remoteEndpoint;
}
public void setRemoteEndpoint(String locatorUri) {
this.remoteEndpoint = locatorUri;
}
/**
* Returns when this agent object was initially created.
*
* @return creation time
*/
public long getCreatedTime() {
return this.ctime;
}
/**
* Returns when this agent object was modified - usually when it receives a new security token. Other times an agent
* will be modified is when it registers a new remote endpoint (in the case when the agent changes the port it is
* listening to, for example).
*
* @return modified time
*/
public long getModifiedTime() {
return this.mtime;
}
/**
* Returns the timestamp when this agent last returned an availability report. If <code>null</code>, then this agent
* has yet to send its very first availability report.
*
* @return timestamp when the last availability report was received from this agent
*/
public Long getLastAvailabilityReport() {
return lastAvailabilityReport;
}
/**
* Sets the timestamp when this agent last returned an availability report.
*
* @param lastAvailabilityReport when the last availability report was received from this agent
*/
public void setLastAvailabilityReport(Long lastAvailabilityReport) {
this.lastAvailabilityReport = lastAvailabilityReport;
}
/**
* Sets the timestamp when this agent last performed an availability ping.
*
* @param lastAvailabilityPing when the last availability ping was received by this agent
*/
public void setLastAvailabilityPing(Long lastAvailabilityPing) {
this.lastAvailabilityPing = lastAvailabilityPing;
}
/**
* Returns the timestamp when this agent last performed an availability ping. If <code>null</code>, then this agent
* has yet to perform its very first availability ping.
*
* @return timestamp when the last availability ping was received from this agent
*/
public Long getLastAvailabilityPing() {
return lastAvailabilityPing;
}
/**
* Returns the {@link AffinityGroup} this agent currently belongs to.
*
* @return the {@link AffinityGroup} this agent currently belongs to
*/
public AffinityGroup getAffinityGroup() {
return affinityGroup;
}
/**
* Sets the {@link AffinityGroup} this agent should belong to.
*
* @param affinityGroup the {@link AffinityGroup} this agent should belong to
*/
public void setAffinityGroup(AffinityGroup affinityGroup) {
this.affinityGroup = affinityGroup;
}
/**
* Returns the {@link Server} this agent is currently communicating to.
*
* @return the {@link Server} this agent is currently communicating to
*/
public Server getServer() {
return server;
}
/**
* Sets the {@link Server} this agent should communicate with.
*
* @param server the {@link Server} this agent should communicate with
*/
public void setServer(Server server) {
this.server = server;
}
/**
* Returns 0 if this agent is current. Otherwise, returns a mask of {@link Agent.Status}
* elements corresponding to the updates that have occurred that are related to this agent.
*
* @return 0 if this agent is current. Otherwise, returns a mask of {@link Agent.Status}
* elements corresponding to the updates that have occurred that are related to this agent.
*/
public int getStatus() {
return status;
}
/**
* If this status was non-zero, some scheduled job would have had to come along to perform
* some work on behalf of this agent. After that work is complete, the status can be reset
* (set to 0) signifying that no further work needs to be done on this agent (as long as the
* status remains 0).
*/
public void clearStatus() {
status = 0;
}
/**
* If some subsystem makes a change to some data that this agent cares about (as summarized
* by the various {@link Status} elements), then that change should be added via this method.
* Periodically, a background job will come along, check the status, and possibly perform
* work on behalf of this agent based on the type of change.
*/
public void addStatus(Status newStatus) {
this.status |= newStatus.mask;
}
public List<String> getStatusMessages() {
return Status.getMessages(status);
}
public enum Status {
RESOURCE_HIERARCHY_UPDATED(1, "This agent's managed resource hierarchy has been updated"), //
BASELINES_CALCULATED(2, "This agent's baselines have been recalculated"), //
ALERT_DEFINITION(4, "Some alert definition with an agent-specific condition category was updated");
public final int mask;
public final String message;
private Status(int mask, String message) {
this.mask = mask;
this.message = message;
}
public static List<String> getMessages(int mask) {
List<String> results = new ArrayList<String>();
for (Status next : Status.values()) {
if (next.mask == (next.mask & mask)) {
results.add(next.message);
}
}
return results;
}
}
public boolean isBackFilled() {
return backFilled;
}
public void setBackFilled(boolean backFilled) {
this.backFilled = backFilled;
}
@PrePersist
void onPersist() {
this.mtime = this.ctime = System.currentTimeMillis();
}
@PreUpdate
void onUpdate() {
if (this.server == null) {
printWithTrace("Agent getting it's server reference set to null");
}
this.mtime = System.currentTimeMillis();
}
private void printWithTrace(String message) {
try {
new IllegalArgumentException(message);
} catch (IllegalArgumentException iae) {
iae.printStackTrace(System.out);
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Agent)) {
return false;
}
Agent agent = (Agent) obj;
return name.equals(agent.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return "Agent[id=" + id + ",name=" + this.name + ",address=" + this.address + ",port=" + this.port
+ ",remote-endpoint=" + this.remoteEndpoint + ",last-availability-ping=" + this.lastAvailabilityPing
+ ",last-availability-report=" + this.lastAvailabilityReport + "]";
}
}