/*
* Copyright 2016 ThoughtWorks, Inc.
*
* 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 com.thoughtworks.go.server.service;
import com.thoughtworks.go.config.AgentConfig;
import com.thoughtworks.go.config.Agents;
import com.thoughtworks.go.domain.AgentInstance;
import com.thoughtworks.go.domain.AgentRuntimeStatus;
import com.thoughtworks.go.listener.AgentChangeListener;
import com.thoughtworks.go.presentation.TriStateSelection;
import com.thoughtworks.go.remote.AgentIdentifier;
import com.thoughtworks.go.security.Registration;
import com.thoughtworks.go.server.domain.AgentInstances;
import com.thoughtworks.go.server.domain.ElasticAgentMetadata;
import com.thoughtworks.go.server.domain.Username;
import com.thoughtworks.go.server.persistence.AgentDao;
import com.thoughtworks.go.server.service.result.HttpOperationResult;
import com.thoughtworks.go.server.service.result.LocalizedOperationResult;
import com.thoughtworks.go.server.service.result.OperationResult;
import com.thoughtworks.go.server.ui.AgentViewModel;
import com.thoughtworks.go.server.ui.AgentsViewModel;
import com.thoughtworks.go.server.util.UuidGenerator;
import com.thoughtworks.go.serverhealth.HealthStateScope;
import com.thoughtworks.go.serverhealth.HealthStateType;
import com.thoughtworks.go.serverhealth.ServerHealthService;
import com.thoughtworks.go.serverhealth.ServerHealthState;
import com.thoughtworks.go.util.SystemEnvironment;
import com.thoughtworks.go.util.TriState;
import com.thoughtworks.go.utils.Timeout;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import java.util.*;
import static java.lang.String.format;
@Service
public class AgentService {
private final SystemEnvironment systemEnvironment;
private final AgentConfigService agentConfigService;
private final SecurityService securityService;
private final EnvironmentConfigService environmentConfigService;
private final GoConfigService goConfigService;
private final UuidGenerator uuidGenerator;
private final ServerHealthService serverHealthService;
private final AgentDao agentDao;
private AgentInstances agentInstances;
private static final Logger LOGGER = Logger.getLogger(AgentService.class);
@Autowired
public AgentService(AgentConfigService agentConfigService, SystemEnvironment systemEnvironment, final EnvironmentConfigService environmentConfigService,
final GoConfigService goConfigService, SecurityService securityService, AgentDao agentDao, UuidGenerator uuidGenerator, ServerHealthService serverHealthService,
final EmailSender emailSender) {
this(agentConfigService, systemEnvironment, null, environmentConfigService, goConfigService, securityService, agentDao, uuidGenerator, serverHealthService);
this.agentInstances = new AgentInstances(new AgentRuntimeStatus.ChangeListener() {
public void statusUpdateRequested(AgentRuntimeInfo runtimeInfo, AgentRuntimeStatus newStatus) {
}
});
}
AgentService(AgentConfigService agentConfigService, SystemEnvironment systemEnvironment, AgentInstances agentInstances,
EnvironmentConfigService environmentConfigService, GoConfigService goConfigService, SecurityService securityService, AgentDao agentDao, UuidGenerator uuidGenerator,
ServerHealthService serverHealthService) {
this.systemEnvironment = systemEnvironment;
this.agentConfigService = agentConfigService;
this.environmentConfigService = environmentConfigService;
this.goConfigService = goConfigService;
this.securityService = securityService;
this.agentInstances = agentInstances;
this.agentDao = agentDao;
this.uuidGenerator = uuidGenerator;
this.serverHealthService = serverHealthService;
}
public void initialize() {
this.sync(this.agentConfigService.agents());
agentConfigService.register(new AgentChangeListener(this));
}
public void sync(Agents agents) {
agentInstances.sync(agents);
}
public List<String> getUniqueAgentNames() {
return new ArrayList<>(agentInstances.getAllHostNames());
}
public List<String> getUniqueIPAddresses() {
return new ArrayList<>(agentInstances.getAllIpAddresses());
}
public List<String> getUniqueAgentOperatingSystems() {
return new ArrayList<>(agentInstances.getAllOperatingSystems());
}
AgentInstances agents() {
return agentInstances;
}
public Map<AgentInstance, Collection<String>> agentEnvironmentMap() {
Map<AgentInstance, Collection<String>> allAgents = new LinkedHashMap<>();
for (AgentInstance agentInstance : agentInstances.allAgents()) {
allAgents.put(agentInstance, environmentConfigService.environmentsFor(agentInstance.getUuid()));
}
return allAgents;
}
public AgentsViewModel registeredAgents() {
return toAgentViewModels(agentInstances.findRegisteredAgents());
}
private AgentsViewModel toAgentViewModels(AgentInstances instances) {
AgentsViewModel agents = new AgentsViewModel();
for (AgentInstance instance : instances) {
agents.add(toAgentViewModel(instance));
}
return agents;
}
private AgentViewModel toAgentViewModel(AgentInstance instance) {
return new AgentViewModel(instance, environmentConfigService.environmentsFor(instance.getUuid()));
}
public AgentInstances findRegisteredAgents() {
return agentInstances.findRegisteredAgents();
}
private boolean isUnknownAgent(AgentInstance agentInstance, OperationResult operationResult) {
if (agentInstance.isNullAgent()) {
String agentNotFoundMessage = String.format("Agent '%s' not found", agentInstance.getUuid());
operationResult.notFound("Agent not found.", agentNotFoundMessage, HealthStateType.general(HealthStateScope.GLOBAL));
return true;
}
return false;
}
private boolean hasOperatePermission(Username username, OperationResult operationResult) {
if (!securityService.hasOperatePermissionForAgents(username)) {
String message = "Unauthorized to operate on agent";
operationResult.unauthorized(message, message, HealthStateType.general(HealthStateScope.GLOBAL));
return false;
}
return true;
}
public AgentInstance updateAgentAttributes(Username username, HttpOperationResult result, String uuid, String newHostname, String resources, String environments, TriState enable) {
if (!hasOperatePermission(username, result)) {
return null;
}
AgentInstance agentInstance = findAgent(uuid);
if (isUnknownAgent(agentInstance, result)) {
return null;
}
AgentConfig agentConfig = agentConfigService.updateAgentAttributes(uuid, username, newHostname, resources, environments, enable, agentInstances, result);
if (agentConfig != null) {
return AgentInstance.createFromConfig(agentConfig, systemEnvironment);
}
return null;
}
public void bulkUpdateAgentAttributes(Username username, LocalizedOperationResult result, List<String> uuids, List<String> resourcesToAdd, List<String> resourcesToRemove, List<String> environmentsToAdd, List<String> environmentsToRemove, TriState enable) {
agentConfigService.bulkUpdateAgentAttributes(agentInstances, username, result, uuids, resourcesToAdd, resourcesToRemove, environmentsToAdd, environmentsToRemove, enable);
}
public void enableAgents(Username username, OperationResult operationResult, List<String> uuids) {
if (!hasOperatePermission(username, operationResult)) {
return;
}
List<AgentInstance> agents = new ArrayList<>();
if (!populateAgentInstancesForUUIDs(operationResult, uuids, agents)) {
return;
}
try {
agentConfigService.enableAgents(username, agents.toArray((new AgentInstance[0])));
operationResult.ok(String.format("Enabled %s agent(s)", uuids.size()));
} catch (Exception e) {
operationResult.internalServerError("Enabling agents failed:" + e.getMessage(), HealthStateType.general(HealthStateScope.GLOBAL));
}
}
public void disableAgents(Username username, OperationResult operationResult, List<String> uuids) {
if (!hasOperatePermission(username, operationResult)) {
return;
}
List<AgentInstance> agents = new ArrayList<>();
if (!populateAgentInstancesForUUIDs(operationResult, uuids, agents)) {
return;
}
try {
agentConfigService.disableAgents(username, agents.toArray(new AgentInstance[0]));
operationResult.ok(String.format("Disabled %s agent(s)", uuids.size()));
} catch (Exception e) {
operationResult.internalServerError("Disabling agents failed:" + e.getMessage(), HealthStateType.general(HealthStateScope.GLOBAL));
}
}
private boolean populateAgentInstancesForUUIDs(OperationResult operationResult, List<String> uuids, List<AgentInstance> agents) {
for (String uuid : uuids) {
AgentInstance agentInstance = findAgentAndRefreshStatus(uuid);
if (isUnknownAgent(agentInstance, operationResult)) {
return false;
}
agents.add(agentInstance);
}
return true;
}
public void deleteAgents(Username username, HttpOperationResult operationResult, List<String> uuids) {
if (!hasOperatePermission(username, operationResult)) {
return;
}
List<AgentInstance> agents = new ArrayList<>();
if (!populateAgentInstancesForUUIDs(operationResult, uuids, agents)) {
return;
}
for (AgentInstance agentInstance : agents) {
if (!agentInstance.canBeDeleted()) {
operationResult.notAcceptable(String.format("Failed to delete %s agent(s), as agent(s) might not be disabled or are still building.", agents.size()),
HealthStateType.general(HealthStateScope.GLOBAL));
return;
}
}
try {
agentConfigService.deleteAgents(username, agents.toArray(new AgentInstance[0]));
operationResult.ok(String.format("Deleted %s agent(s).", agents.size()));
} catch (Exception e) {
operationResult.internalServerError("Deleting agents failed:" + e.getMessage(), HealthStateType.general(HealthStateScope.GLOBAL));
}
}
public void modifyResources(Username username, HttpOperationResult operationResult, List<String> uuids, List<TriStateSelection> selections) {
if (!hasOperatePermission(username, operationResult)) {
return;
}
List<AgentInstance> agents = new ArrayList<>();
if (!populateAgentInstancesForUUIDs(operationResult, uuids, agents)) {
return;
}
try {
agentConfigService.modifyResources(agents.toArray(new AgentInstance[0]), selections, username);
operationResult.ok(String.format("Resource(s) modified on %s agent(s)", uuids.size()));
} catch (Exception e) {
operationResult.notAcceptable("Could not modify resources:" + e.getMessage(), HealthStateType.general(HealthStateScope.GLOBAL));
}
}
public void modifyEnvironments(Username username, HttpOperationResult operationResult, List<String> uuids, List<TriStateSelection> selections) {
if (!hasOperatePermission(username, operationResult)) {
return;
}
List<AgentInstance> agents = new ArrayList<>();
if (!populateAgentInstancesForUUIDs(operationResult, uuids, agents)) {
return;
}
try {
environmentConfigService.modifyEnvironments(agents, selections);
operationResult.ok(String.format("Environment(s) modified on %s agent(s)", uuids.size()));
} catch (Exception e) {
operationResult.notAcceptable("Could not modify environments:" + e.getMessage(), HealthStateType.general(HealthStateScope.GLOBAL));
}
}
public void updateRuntimeInfo(AgentRuntimeInfo info) {
if (!info.hasCookie()) {
LOGGER.warn(format("Agent [%s] has no cookie set", info.agentInfoDebugString()));
throw new AgentNoCookieSetException(format("Agent [%s] has no cookie set", info.agentInfoDebugString()));
}
if (info.hasDuplicateCookie(agentDao.cookieFor(info.getIdentifier()))) {
LOGGER.warn(format("Found agent [%s] with duplicate uuid. Please check the agent installation.", info.agentInfoDebugString()));
serverHealthService.update(
ServerHealthState.warning(format("[%s] has duplicate unique identifier which conflicts with [%s]", info.agentInfoForDisplay(), findAgentAndRefreshStatus(info.getUUId()).agentInfoForDisplay()),
"Please check the agent installation. Click <a href='https://docs.gocd.io/current/faq/agent_guid_issue.html' target='_blank'>here</a> for more info.",
HealthStateType.duplicateAgent(HealthStateScope.forAgent(info.getCookie())), Timeout.THIRTY_SECONDS));
throw new AgentWithDuplicateUUIDException(format("Agent [%s] has invalid cookie", info.agentInfoDebugString()));
}
AgentInstance agentInstance = findAgentAndRefreshStatus(info.getUUId());
if (agentInstance.isIpChangeRequired(info.getIpAdress())) {
AgentConfig agentConfig = agentInstance.agentConfig();
LOGGER.warn(
String.format("Agent with UUID [%s] changed IP Address from [%s] to [%s]",
info.getUUId(), agentConfig.getIpAddress(), info.getIpAdress()));
Username userName = agentUsername(info.getUUId(), info.getIpAdress(), agentConfig.getHostNameForDispaly());
agentConfigService.updateAgentIpByUuid(agentConfig.getUuid(), info.getIpAdress(), userName);
}
agentInstances.updateAgentRuntimeInfo(info);
}
public Username agentUsername(String uuId, String ipAddress, String hostNameForDisplay) {
return new Username(String.format("agent_%s_%s_%s", uuId, ipAddress, hostNameForDisplay));
}
public Registration requestRegistration(Username username, AgentRuntimeInfo agentRuntimeInfo) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Agent is requesting registration " + agentRuntimeInfo);
}
AgentInstance agentInstance = agentInstances.register(agentRuntimeInfo);
Registration registration = agentInstance.assignCertification();
if (agentInstance.isRegistered()) {
agentConfigService.saveOrUpdateAgent(agentInstance, username);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("New Agent approved " + agentRuntimeInfo);
}
}
return registration;
}
@Deprecated
public void approve(String uuid) {
AgentInstance agentInstance = findAgentAndRefreshStatus(uuid);
agentConfigService.approvePendingAgent(agentInstance);
}
public void notifyJobCancelledEvent(String agentId) {
agentInstances.updateAgentAboutCancelledBuild(agentId, true);
}
public AgentInstance findAgentAndRefreshStatus(String uuid) {
return agentInstances.findAgentAndRefreshStatus(uuid);
}
public AgentInstance findAgent(String uuid) {
return agentInstances.findAgent(uuid);
}
public void clearAll() {
agentInstances.clearAll();
}
/**
* called from spring timer
*/
public void refresh() {
agentInstances.refresh();
}
public void building(String uuid, AgentBuildingInfo agentBuildingInfo) {
agentInstances.building(uuid, agentBuildingInfo);
}
public String assignCookie(AgentIdentifier identifier) {
String cookie = uuidGenerator.randomUuid();
agentDao.associateCookie(identifier, cookie);
return cookie;
}
public AgentsViewModel filter(List<String> uuids) {
AgentsViewModel viewModels = new AgentsViewModel();
for (AgentInstance agentInstance : agentInstances.filter(uuids)) {
viewModels.add(new AgentViewModel(agentInstance));
}
return viewModels;
}
public AgentViewModel findAgentViewModel(String uuid) {
return toAgentViewModel(findAgentAndRefreshStatus(uuid));
}
public LinkedMultiValueMap<String, ElasticAgentMetadata> allElasticAgents() {
return agentInstances.allElasticAgentsGroupedByPluginId();
}
public AgentInstance findElasticAgent(String elasticAgentId, String elasticPluginId) {
return agentInstances.findElasticAgent(elasticAgentId, elasticPluginId);
}
public AgentInstances findEnabledAgents() {
return agentInstances.findEnabledAgents();
}
public AgentInstances findDisabledAgents() {
return agentInstances.findDisabledAgents();
}
}