/*
* 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.agent.service;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import net.grinder.common.processidentity.AgentIdentity;
import net.grinder.engine.controller.AgentControllerIdentityImplementation;
import net.grinder.message.console.AgentControllerState;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.mutable.MutableInt;
import org.ngrinder.agent.repository.AgentManagerRepository;
import org.ngrinder.common.constant.ControllerConstants;
import org.ngrinder.infra.config.Config;
import org.ngrinder.infra.schedule.ScheduledTaskService;
import org.ngrinder.model.AgentInfo;
import org.ngrinder.model.User;
import org.ngrinder.monitor.controller.model.SystemDataModel;
import org.ngrinder.perftest.service.AgentManager;
import org.ngrinder.service.AbstractAgentManagerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.*;
import static org.ngrinder.common.util.CollectionUtils.newArrayList;
import static org.ngrinder.common.util.CollectionUtils.newHashMap;
import static org.ngrinder.common.util.NoOp.noOp;
import static org.ngrinder.common.util.TypeConvertUtils.cast;
/**
* Agent manager service.
*
* @author JunHo Yoon
* @since 3.0
*/
public class AgentManagerService extends AbstractAgentManagerService {
protected static final Logger LOGGER = LoggerFactory.getLogger(AgentManagerService.class);
@Autowired
private AgentManager agentManager;
@Autowired
protected AgentManagerRepository agentManagerRepository;
@Autowired
protected LocalAgentService cachedLocalAgentService;
@Autowired
private Config config;
@Autowired
protected ScheduledTaskService scheduledTaskService;
private Runnable runnable;
@PostConstruct
public void init() {
runnable = new Runnable() {
@Override
public void run() {
checkAgentStatePeriodically();
}
};
scheduledTaskService.addFixedDelayedScheduledTaskInTransactionContext(runnable, 1000);
}
@PreDestroy
public void destroy() {
scheduledTaskService.removeScheduledJob(runnable);
}
public void checkAgentStatePeriodically() {
checkAgentState();
}
public void checkAgentState() {
List<AgentInfo> newAgents = newArrayList(0);
List<AgentInfo> updatedAgents = newArrayList(0);
List<AgentInfo> stateUpdatedAgents = newArrayList(0);
List<AgentInfo> removedAgents = newArrayList(0);
Set<AgentIdentity> allAttachedAgents = getAgentManager().getAllAttachedAgents();
Map<String, AgentControllerIdentityImplementation> attachedAgentMap = Maps.newHashMap();
for (AgentIdentity agentIdentity : allAttachedAgents) {
AgentControllerIdentityImplementation agentControllerIdentity = cast(agentIdentity);
attachedAgentMap.put(createKey(agentControllerIdentity), agentControllerIdentity);
}
// If region is not specified retrieved all
Map<String, AgentInfo> agentInDBMap = newHashMap();
// step1. check all agents in DB, whether they are attached to
// controller.
for (AgentInfo each : getAllLocal()) {
final String agentKey = createKey(each);
if (!agentInDBMap.containsKey(agentKey)) {
agentInDBMap.put(agentKey, each);
} else {
removedAgents.add(each);
}
}
for (Map.Entry<String, AgentInfo> each : agentInDBMap.entrySet()) {
String agentKey = each.getKey();
AgentInfo agentInfo = each.getValue();
AgentControllerIdentityImplementation agentIdentity = attachedAgentMap.remove(agentKey);
if (agentIdentity == null) {
// this agent is not attached to controller
agentInfo.setState(AgentControllerState.INACTIVE);
stateUpdatedAgents.add(agentInfo);
} else if (!hasSameInfo(agentInfo, agentIdentity)) {
agentInfo.setRegion(agentIdentity.getRegion());
agentInfo.setPort(agentManager.getAgentConnectingPort(agentIdentity));
agentInfo.setVersion(agentManager.getAgentVersion(agentIdentity));
updatedAgents.add(agentInfo);
} else if (!hasSameState(agentInfo, agentIdentity)) {
agentInfo.setState(agentManager.getAgentState(agentIdentity));
stateUpdatedAgents.add(agentInfo);
}
}
// step2. check all attached agents, whether they are new, and not saved
// in DB.
for (AgentControllerIdentityImplementation agentIdentity : attachedAgentMap.values()) {
final AgentInfo agentInfo = fillUp(new AgentInfo(), agentIdentity);
newAgents.add(agentInfo);
}
cachedLocalAgentService.updateAgents(newAgents, updatedAgents, stateUpdatedAgents, removedAgents);
if (!newAgents.isEmpty() || !removedAgents.isEmpty()) {
expireLocalCache();
}
}
public String extractRegionFromAgentRegion(String agentRegion) {
if (agentRegion != null && agentRegion.contains("_owned_")) {
return agentRegion.substring(0, agentRegion.indexOf("_owned_"));
}
if (agentRegion != null && agentRegion.contains("owned_")) {
return agentRegion.substring(0, agentRegion.indexOf("owned_"));
}
if (StringUtils.isEmpty(agentRegion)) {
return Config.NONE_REGION;
}
return agentRegion;
}
protected boolean hasSameInfo(AgentInfo agentInfo, AgentControllerIdentityImplementation agentIdentity) {
return agentInfo != null &&
agentInfo.getPort() == agentManager.getAgentConnectingPort(agentIdentity) &&
StringUtils.equals(agentInfo.getRegion(), agentIdentity.getRegion()) &&
StringUtils.equals(StringUtils.trimToNull(agentInfo.getVersion()),
StringUtils.trimToNull(agentManager.getAgentVersion(agentIdentity)));
}
protected boolean hasSameState(AgentInfo agentInfo, AgentControllerIdentityImplementation agentIdentity) {
return agentInfo != null &&
agentInfo.getState() == agentManager.getAgentState(agentIdentity);
}
/*
* (non-Javadoc)
*
* @see
* org.ngrinder.service.IAgentManagerService#getAvailableAgentCountMap
* (org.ngrinder .model.User)
*/
@Override
public Map<String, MutableInt> getAvailableAgentCountMap(User user) {
int availableShareAgents = 0;
int availableUserOwnAgent = 0;
String myAgentSuffix = "owned_" + user.getUserId();
for (AgentInfo agentInfo : getAllActive()) {
// Skip all agents which are disapproved, inactive or
// have no region prefix.
if (!agentInfo.isApproved()) {
continue;
}
String fullRegion = agentInfo.getRegion();
// It's this controller's agent
if (StringUtils.endsWithIgnoreCase(fullRegion, myAgentSuffix)) {
availableUserOwnAgent++;
} else if (!StringUtils.containsIgnoreCase(fullRegion, "owned_")) {
availableShareAgents++;
}
}
int maxAgentSizePerConsole = getMaxAgentSizePerConsole();
availableShareAgents = (Math.min(availableShareAgents, maxAgentSizePerConsole));
Map<String, MutableInt> result = new HashMap<String, MutableInt>(1);
result.put(Config.NONE_REGION, new MutableInt(availableShareAgents + availableUserOwnAgent));
return result;
}
int getMaxAgentSizePerConsole() {
return getAgentManager().getMaxAgentSizePerConsole();
}
/*
* (non-Javadoc)
*
* @see org.ngrinder.service.IAgentManagerService#getAllLocalWithFullInfo()
*/
@Override
@Transactional
public List<AgentInfo> getAllLocalWithFullInfo() {
Map<String, AgentInfo> agentInfoMap = createLocalAgentMap();
Set<AgentIdentity> allAttachedAgents = getAgentManager().getAllAttachedAgents();
List<AgentInfo> agentList = new ArrayList<AgentInfo>(allAttachedAgents.size());
for (AgentIdentity eachAgentIdentity : allAttachedAgents) {
AgentControllerIdentityImplementation agentControllerIdentity = cast(eachAgentIdentity);
agentList.add(createAgentInfo(agentControllerIdentity, agentInfoMap));
}
return agentList;
}
private Map<String, AgentInfo> createLocalAgentMap() {
Map<String, AgentInfo> agentInfoMap = Maps.newHashMap();
for (AgentInfo each : getAllLocal()) {
agentInfoMap.put(createKey(each), each);
}
return agentInfoMap;
}
/*
* (non-Javadoc)
*
* @see
* org.ngrinder.service.IAgentManagerService#createKey(org.ngrinder
* .agent.model.AgentInfo )
*/
@Override
public String createKey(AgentInfo agentInfo) {
return createAgentKey(agentInfo.getIp(), agentInfo.getName());
}
/*
* (non-Javadoc)
*
* @see
* org.ngrinder.service.IAgentManagerService#createKey(net.grinder
* .engine.controller .AgentControllerIdentityImplementation)
*/
@Override
public String createKey(AgentControllerIdentityImplementation agentIdentity) {
return createAgentKey(agentIdentity.getIp(), agentIdentity.getName());
}
protected String createAgentKey(String ip, String name) {
return ip + "_" + name;
}
/*
* (non-Javadoc)
*
* @see org.ngrinder.service.IAgentManagerService#
* getAgentIdentityByIpAndName(java.lang .String, java.lang.String)
*/
@Override
public AgentControllerIdentityImplementation getAgentIdentityByIpAndName(String ip, String name) {
Set<AgentIdentity> allAttachedAgents = getAgentManager().getAllAttachedAgents();
for (AgentIdentity eachAgentIdentity : allAttachedAgents) {
AgentControllerIdentityImplementation agentIdentity = cast(eachAgentIdentity);
if (StringUtils.equals(ip, agentIdentity.getIp()) && StringUtils.equals(name, agentIdentity.getName())) {
return agentIdentity;
}
}
return null;
}
public List<AgentInfo> getAllLocal() {
return Collections.unmodifiableList(cachedLocalAgentService.getLocalAgents());
}
/*
* (non-Javadoc)
*
* @see
* org.ngrinder.service.IAgentManagerService#getAllActive
* ()
*
*/
@Override
public List<AgentInfo> getAllActive() {
List<AgentInfo> agents = Lists.newArrayList();
for (AgentInfo agentInfo : getAllLocal()) {
final AgentControllerState state = agentInfo.getState();
if (state != null && state.isActive()) {
agents.add(agentInfo);
}
}
return agents;
}
/*
* (non-Javadoc)
*
* @see
* org.ngrinder.service.IAgentManagerService#getAllVisible
* ()
*/
@Override
public List<AgentInfo> getAllVisible() {
List<AgentInfo> agents = Lists.newArrayList();
for (AgentInfo agentInfo : getAllLocal()) {
final AgentControllerState state = agentInfo.getState();
if (state != null && state.isActive()) {
agents.add(agentInfo);
}
}
return agents;
}
private AgentInfo createAgentInfo(AgentControllerIdentityImplementation agentIdentity,
Map<String, AgentInfo> agentInfoMap) {
AgentInfo agentInfo = agentInfoMap.get(createKey(agentIdentity));
if (agentInfo == null) {
agentInfo = new AgentInfo();
}
return fillUp(agentInfo, agentIdentity);
}
protected AgentInfo fillUp(AgentInfo agentInfo, AgentControllerIdentityImplementation agentIdentity) {
fillUpApproval(agentInfo);
if (agentIdentity != null) {
agentInfo.setAgentIdentity(agentIdentity);
agentInfo.setName(agentIdentity.getName());
agentInfo.setRegion(agentIdentity.getRegion());
agentInfo.setIp(agentIdentity.getIp());
AgentManager agentManager = getAgentManager();
agentInfo.setPort(agentManager.getAgentConnectingPort(agentIdentity));
agentInfo.setState(agentManager.getAgentState(agentIdentity));
agentInfo.setVersion(agentManager.getAgentVersion(agentIdentity));
}
return agentInfo;
}
protected AgentInfo fillUpApproval(AgentInfo agentInfo) {
if (agentInfo.getApproved() == null) {
final boolean approved = config.getControllerProperties().getPropertyBoolean(ControllerConstants
.PROP_CONTROLLER_ENABLE_AGENT_AUTO_APPROVAL);
agentInfo.setApproved(approved);
}
return agentInfo;
}
/*
* (non-Javadoc)
*
* @see org.ngrinder.service.IAgentManagerService#getAll(long,
* boolean)
*/
@Override
public AgentInfo getOne(Long id) {
return getOne(id, false);
}
/*
* (non-Javadoc)
*
* @see org.ngrinder.service.IAgentManagerService#getAll(long,
* boolean)
*/
@Override
public AgentInfo getOne(Long id, boolean includeAgentIdentity) {
AgentInfo findOne = agentManagerRepository.findOne(id);
if (findOne == null) {
return null;
}
if (includeAgentIdentity) {
AgentControllerIdentityImplementation agentIdentityByIp = getAgentIdentityByIpAndName(findOne.getIp(),
findOne.getName());
return fillUp(findOne, agentIdentityByIp);
} else {
return findOne;
}
}
/**
* Approve/disapprove the agent on given id.
*
* @param id id
* @param approve true/false
*/
@Transactional
public AgentInfo approve(Long id, boolean approve) {
AgentInfo found = agentManagerRepository.findOne(id);
if (found != null) {
found.setApproved(approve);
agentManagerRepository.save(found);
expireLocalCache();
}
return found;
}
/**
* Stop agent. If it's in cluster mode, it queue to agentRequestCache.
* otherwise, it send stop message to the agent.
*
* @param id identity of agent to stop.
*/
@Transactional
public void stopAgent(Long id) {
AgentInfo agent = getOne(id, true);
if (agent == null) {
return;
}
getAgentManager().stopAgent(agent.getAgentIdentity());
}
/**
* Add the agent system data model share request on cache.
*
* @param id agent id.
*/
public void requestShareAgentSystemDataModel(Long id) {
noOp();
}
/*
* (non-Javadoc)
*
* @see
* org.ngrinder.service.IAgentManagerService#getSystemDataModel
* (java.lang.String, java.lang.String)
*/
@Override
public SystemDataModel getSystemDataModel(String ip, String name) {
AgentControllerIdentityImplementation agentIdentity = getAgentIdentityByIpAndName(ip, name);
return agentIdentity != null ? getAgentManager().getSystemDataModel(agentIdentity) : new SystemDataModel();
}
AgentManager getAgentManager() {
return agentManager;
}
void setAgentManager(AgentManager agentManager) {
this.agentManager = agentManager;
}
public void setAgentManagerRepository(AgentManagerRepository agentManagerRepository) {
this.agentManagerRepository = agentManagerRepository;
}
public Config getConfig() {
return config;
}
public void setConfig(Config config) {
this.config = config;
}
/*
* (non-Javadoc)
*
* @see
* org.ngrinder.service.IAgentManagerService#updateAgentLib
* (java.lang.String)
*/
@Override
public void update(Long id) {
AgentInfo agent = getOne(id, true);
if (agent == null) {
return;
}
updateAgent(agent.getAgentIdentity());
}
/**
* Update the agent
*
* @param agentIdentity agent identity to be updated.
*/
public void updateAgent(AgentIdentity agentIdentity) {
agentManager.updateAgent(agentIdentity, shouldUpdateAgentAlways() ? "99.99" : config.getVersion());
}
protected boolean shouldUpdateAgentAlways() {
return config.getControllerProperties().getPropertyBoolean(ControllerConstants.PROP_CONTROLLER_AGENT_FORCE_UPDATE);
}
public void expireLocalCache() {
cachedLocalAgentService.expireCache();
}
/**
* Clean up the agents from db which belongs to the inactive regions.
* Do nothing in not cluster mode.
*/
@Transactional
public void cleanup() {
for (AgentInfo each : agentManagerRepository.findAll()) {
if (!each.getState().isActive()) {
agentManagerRepository.delete(each);
}
}
}
/**
* All ready state agent return
*/
List<AgentInfo> getAllReady() {
List<AgentInfo> agents = Lists.newArrayList();
for (AgentInfo agentInfo : getAllLocal()) {
final AgentControllerState state = agentInfo.getState();
if (state != null && state.isReady()) {
agents.add(agentInfo);
}
}
return agents;
}
/**
* Ready agent state count return
*
* @param user The login user
* @param String targetRegion The name of target region
* @return ready Agent count
*/
@Override
public int getReadyAgentCount(User user, String targetRegion) {
int readyAgentCnt = 0;
String myOwnAgent = targetRegion + "_owned_" + user.getUserId();
for (AgentInfo agentInfo : getAllReady()) {
if (!agentInfo.isApproved()) {
continue;
}
String fullRegion = agentInfo.getRegion();
if (fullRegion.equals(targetRegion) || fullRegion.equals(myOwnAgent)) {
readyAgentCnt++;
}
}
return readyAgentCnt;
}
}