/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ngrinder.perftest.service;
import net.grinder.AgentControllerServerDaemon;
import net.grinder.SingleConsole;
import net.grinder.common.GrinderProperties;
import net.grinder.common.processidentity.AgentIdentity;
import net.grinder.console.communication.AgentDownloadRequestListener;
import net.grinder.console.communication.AgentProcessControlImplementation;
import net.grinder.console.communication.AgentProcessControlImplementation.AgentStatus;
import net.grinder.console.communication.LogArrivedListener;
import net.grinder.console.model.ConsoleCommunicationSetting;
import net.grinder.engine.communication.AgentUpdateGrinderMessage;
import net.grinder.engine.controller.AgentControllerIdentityImplementation;
import net.grinder.message.console.AgentControllerState;
import net.grinder.messages.console.AgentAddress;
import net.grinder.util.thread.ExecutorFactory;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.ngrinder.agent.service.AgentPackageService;
import org.ngrinder.agent.service.LocalAgentService;
import org.ngrinder.common.constant.ControllerConstants;
import org.ngrinder.common.util.CRC32ChecksumUtils;
import org.ngrinder.infra.config.Config;
import org.ngrinder.model.AgentInfo;
import org.ngrinder.model.User;
import org.ngrinder.monitor.controller.model.SystemDataModel;
import org.python.google.common.base.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
/**
* Agent manager.
* <p/>
* This class has {@link AgentControllerServerDaemon} internally and manage to the agent connection.
*
* @author JunHo Yoon
* @since 3.0
*/
@SuppressWarnings("UnusedDeclaration")
@Component
public class AgentManager implements ControllerConstants, AgentDownloadRequestListener {
public static final Logger LOGGER = LoggerFactory.getLogger(AgentManager.class);
private AgentControllerServerDaemon agentControllerServerDaemon;
private static final int NUMBER_OF_THREAD = 3;
@Autowired
private Config config;
@Autowired
private AgentPackageService agentPackageService;
@Autowired
private LocalAgentService cachedLocalAgentService;
/**
* Initialize agent manager.
*/
@PostConstruct
public void init() {
int port = config.getControllerPort();
ConsoleCommunicationSetting consoleCommunicationSetting = ConsoleCommunicationSetting.asDefault();
if (config.getInactiveClientTimeOut() > 0) {
consoleCommunicationSetting.setInactiveClientTimeOut(config.getInactiveClientTimeOut());
}
agentControllerServerDaemon = new AgentControllerServerDaemon(config.getCurrentIP(), port, consoleCommunicationSetting);
agentControllerServerDaemon.start();
agentControllerServerDaemon.setAgentDownloadRequestListener(this);
agentControllerServerDaemon.addLogArrivedListener(new LogArrivedListener() {
@Override
public void logArrived(String testId, AgentAddress agentAddress, byte[] logs) {
AgentControllerIdentityImplementation agentIdentity = convert(agentAddress.getIdentity());
if (ArrayUtils.isEmpty(logs)) {
LOGGER.error("Log is arrived from {} but no log content", agentIdentity.getIp());
}
File logFile = null;
try {
logFile = new File(config.getHome().getPerfTestLogDirectory(testId.replace("test_", "")),
agentIdentity.getName() + "-" + agentIdentity.getRegion() + "-log.zip");
FileUtils.writeByteArrayToFile(logFile, logs);
} catch (IOException e) {
LOGGER.error("Error while write logs from {} to {}", agentAddress.getIdentity().getName(),
logFile.getAbsolutePath());
LOGGER.error("Error is following", e);
}
}
});
}
/**
* Shutdown agent controller server.
*/
@PreDestroy
public void destroy() {
agentControllerServerDaemon.shutdown();
}
/**
* Get the port which given agent is connecting to.
*
* @param agentIdentity agent identity
* @return port
*/
public int getAgentConnectingPort(AgentIdentity agentIdentity) {
return agentControllerServerDaemon.getAgentConnectingPort(agentIdentity);
}
/**
* Get all agent status.
*
* @return {@link AgentStatus} set
* @since 3.1.2
*/
public Set<AgentStatus> getAllAgentStatusSet() {
return agentControllerServerDaemon.getAgentStatusSet(new Predicate<AgentStatus>() {
@Override
public boolean apply(AgentStatus arg0) {
return true;
}
});
}
/**
* Get agent status set matching the given predicate.
*
* @param predicate predicate
* @return {@link AgentStatus} set
* @since 3.1.2
*/
public Set<AgentStatus> getAgentStatusSet(Predicate<AgentStatus> predicate) {
return agentControllerServerDaemon.getAgentStatusSet(predicate);
}
/**
* Get the agent status of the given agent.
*
* @param agentIdentity agentIdentity of one agent
* @return status agent controller status of one agent
*/
public AgentControllerState getAgentState(AgentIdentity agentIdentity) {
return agentControllerServerDaemon.getAgentState(agentIdentity);
}
/**
* Get all agents which is connected to agent controller.
*
* @return agents set
*/
public Set<AgentIdentity> getAllAttachedAgents() {
return agentControllerServerDaemon.getAllAvailableAgents();
}
/**
* Get the max agent size per console.
*
* @return max agent size per console
*/
public int getMaxAgentSizePerConsole() {
return config.getControllerProperties().getPropertyInt(PROP_CONTROLLER_MAX_AGENT_PER_TEST);
}
/**
* Get the max vuser per agent.
*
* @return max vuser per agent
*/
public int getMaxVuserPerAgent() {
return config.getControllerProperties().getPropertyInt(PROP_CONTROLLER_MAX_VUSER_PER_AGENT);
}
/**
* Get the max run count per thread.
*
* @return max run count per thread
*/
public int getMaxRunCount() {
return config.getControllerProperties().getPropertyInt(PROP_CONTROLLER_MAX_RUN_COUNT);
}
/**
* Get the max run hour.
*
* @return max run hour
*/
public int getMaxRunHour() {
return config.getControllerProperties().getPropertyInt(PROP_CONTROLLER_MAX_RUN_HOUR);
}
/**
* Get the {@link AgentIdentity} which has the given ip.
*
* @param agentIP agent ip
* @return {@link AgentControllerIdentityImplementation}
*/
public AgentControllerIdentityImplementation getAgentIdentityByIp(String agentIP) {
for (AgentIdentity agentIdentity : getAllAttachedAgents()) {
if (StringUtils.equals(convert(agentIdentity).getIp(), agentIP)) {
return convert(agentIdentity);
}
}
return null;
}
/**
* Convert {@link AgentIdentity} to {@link AgentControllerIdentityImplementation} type.
*
* @param identity identity
* @return converted identity.
*/
AgentControllerIdentityImplementation convert(AgentIdentity identity) {
return (AgentControllerIdentityImplementation) identity;
}
/**
* Get all agents which are not used now.
*
* @return AgentIdentity set
*/
public Set<AgentIdentity> getAllFreeAgents() {
return agentControllerServerDaemon.getAllFreeAgents();
}
/**
* Get all approved agents for given user which are not used now.
*
* @param user user
* @return AgentIdentity set
*/
public Set<AgentIdentity> getAllFreeApprovedAgentsForUser(User user) {
if (user == null) {
return Collections.emptySet();
}
return filterUserAgents(getAllFreeApprovedAgents(), user.getUserId());
}
/**
* Get all approved agents which are not used now.
*
* @return AgentIdentity set
*/
public Set<AgentIdentity> getAllFreeApprovedAgents() {
Set<AgentIdentity> allFreeAgents = agentControllerServerDaemon.getAllFreeAgents();
return filterApprovedAgents(allFreeAgents);
}
/**
* Get all approved agents for given user.
*
* @param user user
* @return AgentIdentity set
*/
public Set<AgentIdentity> getAllApprovedAgents(User user) {
if (user == null) {
return Collections.emptySet();
}
return filterUserAgents(getAllApprovedAgents(), user.getUserId());
}
/**
* Get all shared approved agents.
*
* @return AgentIdentity set
*/
public Set<AgentIdentity> getAllSharedAgents() {
return filterSharedAgents(getAllApprovedAgents());
}
/**
* Get all approved agents.
*
* @return AgentIdentity set
*/
public Set<AgentIdentity> getAllApprovedAgents() {
Set<AgentIdentity> allAgents = agentControllerServerDaemon.getAllAvailableAgents();
return filterApprovedAgents(allAgents);
}
/**
* Filter the approved agents from given agents.
*
* @param agents all agents
* @return approved agents.
*/
public Set<AgentIdentity> filterApprovedAgents(Set<AgentIdentity> agents) {
if (agents.size() == 0) {
return agents;
}
Set<String> ips = new HashSet<String>();
for (AgentInfo each : cachedLocalAgentService.getLocalAgents()) {
if (each.isApproved()) {
ips.add(each.getIp() + each.getName());
}
}
Set<AgentIdentity> approvedAgent = new HashSet<AgentIdentity>();
for (AgentIdentity each : agents) {
if (ips.contains(((AgentControllerIdentityImplementation) each).getIp() + each.getName())) {
approvedAgent.add(each);
}
}
return approvedAgent;
}
/**
* Filter the shared agents from given agents.
*
* @param agents all agents
* @return userOwned agents.
*/
public Set<AgentIdentity> filterSharedAgents(Set<AgentIdentity> agents) {
Set<AgentIdentity> userAgent = new HashSet<AgentIdentity>();
for (AgentIdentity each : agents) {
String region = ((AgentControllerIdentityImplementation) each).getRegion();
if (StringUtils.containsNone(region, "owned_")) {
userAgent.add(each);
}
}
return userAgent;
}
/**
* Filter the user owned agents from given agents.
*
* @param agents all agents
* @param userId userId
* @return userOwned agents.
*/
public Set<AgentIdentity> filterUserAgents(Set<AgentIdentity> agents, String userId) {
Set<AgentIdentity> userAgent = new HashSet<AgentIdentity>();
for (AgentIdentity each : agents) {
String region = ((AgentControllerIdentityImplementation) each).getRegion();
if (StringUtils.endsWith(region, "owned_" + userId) || !StringUtils.contains(region, "owned_")) {
userAgent.add(each);
}
}
return userAgent;
}
/**
* Get the current system performance of the given agent.
*
* @param agentIdentity {@link AgentIdentity}
* @return {@link SystemDataModel} instance.
*/
public SystemDataModel getSystemDataModel(AgentIdentity agentIdentity) {
return agentControllerServerDaemon.getSystemDataModel(agentIdentity);
}
/**
* Get the agent version.
*
* @param agentIdentity {@link AgentIdentity}
* @return version.
*/
public String getAgentVersion(AgentControllerIdentityImplementation agentIdentity) {
return agentControllerServerDaemon.getAgentVersion(agentIdentity);
}
/**
* Assign the agents on the given console.
*
* @param user user
* @param singleConsole {@link SingleConsole} to which agents will be assigned
* @param grinderProperties {@link GrinderProperties} to be distributed.
* @param agentCount the count of agents.
*/
public synchronized void runAgent(User user, final SingleConsole singleConsole,
final GrinderProperties grinderProperties, final Integer agentCount) {
final Set<AgentIdentity> allFreeAgents = getAllFreeApprovedAgentsForUser(user);
final Set<AgentIdentity> necessaryAgents = selectAgent(user, allFreeAgents, agentCount);
LOGGER.info("{} agents are starting for user {}", agentCount, user.getUserId());
for (AgentIdentity each : necessaryAgents) {
LOGGER.info("- Agent {}", each.getName());
}
ExecutorService execService = null;
try {
// Make the agents connect to console.
grinderProperties.setInt(GrinderProperties.CONSOLE_PORT, singleConsole.getConsolePort());
execService = ExecutorFactory.createThreadPool("agentStarter", NUMBER_OF_THREAD);
for (final AgentIdentity eachAgentIdentity : necessaryAgents) {
execService.submit(new Runnable() {
@Override
public void run() {
agentControllerServerDaemon.startAgent(grinderProperties, eachAgentIdentity);
}
});
}
} finally {
if (execService != null) {
execService.shutdown();
}
}
}
/**
* Select agent. This method return agent set which is belong to the given user first and then share agent set.
*
* @param user user
* @param allFreeAgents agents
* @param agentCount number of agent
* @return selected agent.
*/
public Set<AgentIdentity> selectAgent(User user, Set<AgentIdentity> allFreeAgents, int agentCount) {
Set<AgentIdentity> userAgent = new HashSet<AgentIdentity>();
for (AgentIdentity each : allFreeAgents) {
String region = ((AgentControllerIdentityImplementation) each).getRegion();
if (StringUtils.endsWith(region, "owned_" + user.getUserId())) {
userAgent.add(each);
if (userAgent.size() == agentCount) {
return userAgent;
}
}
}
for (AgentIdentity each : allFreeAgents) {
String region = ((AgentControllerIdentityImplementation) each).getRegion();
if (!StringUtils.contains(region, "owned_")) {
userAgent.add(each);
if (userAgent.size() == agentCount) {
return userAgent;
}
}
}
return userAgent;
}
/**
* Stop agent by force.
*
* @param agentIdentity agent identity
*/
public void stopAgent(AgentIdentity agentIdentity) {
agentControllerServerDaemon.stopAgent(agentIdentity);
}
/**
* Stop agents which uses the given console port by force.
*
* @param consolePort console port.
*/
public void stopAgent(int consolePort) {
Set<AgentStatus> agentStatusSetConnectingToPort = getAgentStatusSetConnectingToPort(consolePort);
for (AgentStatus each : agentStatusSetConnectingToPort) {
if (each.getAgentControllerState() == AgentControllerState.BUSY) {
agentControllerServerDaemon.stopAgent(each.getAgentIdentity());
}
}
}
/**
* Update the given agent.
*
* @param agentIdentity agent identity
*/
public void updateAgent(AgentIdentity agentIdentity, String version) {
agentControllerServerDaemon.updateAgent(agentIdentity, version);
}
/**
* Get the set of {@link AgentStatus} from agents belong to the given single console port.
*
* @param singleConsolePort port
* @return {@link AgentStatus} set
*/
public Set<AgentStatus> getAgentStatusSetConnectingToPort(final int singleConsolePort) {
return getAgentStatusSet(new Predicate<AgentProcessControlImplementation.AgentStatus>() {
@Override
public boolean apply(AgentStatus status) {
return status.getConnectingPort() == singleConsolePort;
}
});
}
@Override
public synchronized AgentUpdateGrinderMessage onAgentDownloadRequested(String version, int offset) {
final int updateChunkSize = getUpdateChunkSize();
byte[] buffer = new byte[updateChunkSize];
RandomAccessFile agentPackageReader = null;
try {
agentPackageReader = new RandomAccessFile(agentPackageService.createAgentPackage(), "r");
agentPackageReader.seek(offset);
int count = agentPackageReader.read(buffer, 0, updateChunkSize);
byte[] bytes = buffer;
int next = offset + count;
if (count != updateChunkSize) {
bytes = Arrays.copyOf(buffer, count);
next = 0;
}
return new AgentUpdateGrinderMessage(version, bytes, offset, next,
CRC32ChecksumUtils.getCRC32Checksum(bytes));
} catch (Exception e) {
LOGGER.error("Error while reading agent package, its offset is {} and details {}:", offset, e);
} finally {
IOUtils.closeQuietly(agentPackageReader);
}
return AgentUpdateGrinderMessage.getNullAgentUpdateGrinderMessage(version);
}
private int getUpdateChunkSize() {
return config.getControllerProperties().getPropertyInt(ControllerConstants.PROP_CONTROLLER_UPDATE_CHUNK_SIZE);
}
}