package io.cattle.platform.agent.instance.factory.impl;
import static io.cattle.platform.core.model.tables.AgentTable.*;
import static io.cattle.platform.core.model.tables.InstanceTable.*;
import io.cattle.platform.agent.AgentLocator;
import io.cattle.platform.agent.instance.dao.AgentInstanceDao;
import io.cattle.platform.agent.instance.factory.AgentInstanceFactory;
import io.cattle.platform.agent.instance.factory.lock.AgentInstanceAgentCreateLock;
import io.cattle.platform.archaius.util.ArchaiusUtil;
import io.cattle.platform.core.constants.AgentConstants;
import io.cattle.platform.core.constants.CommonStatesConstants;
import io.cattle.platform.core.constants.InstanceConstants;
import io.cattle.platform.core.constants.ServiceConstants;
import io.cattle.platform.core.dao.AccountDao;
import io.cattle.platform.core.dao.GenericResourceDao;
import io.cattle.platform.core.dao.InstanceDao;
import io.cattle.platform.core.model.Agent;
import io.cattle.platform.core.model.Instance;
import io.cattle.platform.core.model.Service;
import io.cattle.platform.core.model.Stack;
import io.cattle.platform.core.util.SystemLabels;
import io.cattle.platform.deferred.util.DeferredUtils;
import io.cattle.platform.docker.client.DockerImage;
import io.cattle.platform.engine.process.impl.ProcessCancelException;
import io.cattle.platform.lock.LockCallback;
import io.cattle.platform.lock.LockManager;
import io.cattle.platform.object.ObjectManager;
import io.cattle.platform.object.process.ObjectProcessManager;
import io.cattle.platform.object.process.StandardProcess;
import io.cattle.platform.object.resource.ResourceMonitor;
import io.cattle.platform.object.resource.ResourcePredicate;
import io.cattle.platform.object.util.DataAccessor;
import io.cattle.platform.process.common.util.ProcessUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.tuple.Pair;
import com.netflix.config.DynamicStringProperty;
public class AgentInstanceFactoryImpl implements AgentInstanceFactory {
private static final DynamicStringProperty LB_IMAGE_UUID = ArchaiusUtil.getString("lb.instance.image.uuid");
@Inject
AccountDao accountDao;
@Inject
ObjectManager objectManager;
@Inject
AgentInstanceDao factoryDao;
@Inject
LockManager lockManager;
@Inject
GenericResourceDao resourceDao;
@Inject
AgentLocator agentLocator;
@Inject
ResourceMonitor resourceMonitor;
@Inject
ObjectProcessManager processManager;
@Inject
InstanceDao instanceDao;
protected Instance build(AgentInstanceBuilderImpl builder) {
Agent agent = getAgent(builder);
return getInstance(agent, builder);
}
private Instance getInstance(Agent agent, AgentInstanceBuilderImpl builder) {
Instance instance = factoryDao.getInstanceByAgent(agent.getId());
if (instance != null) {
return instance;
}
Map<String, Object> properties = getInstanceProperties(agent, builder);
return createInstance(agent, properties, builder);
}
private Map<String, Object> getInstanceProperties(Agent agent, AgentInstanceBuilderImpl builder) {
Map<Object, Object> properties = new HashMap<Object, Object>();
properties.put(INSTANCE.ACCOUNT_ID, getAccountId(builder));
properties.put(INSTANCE.AGENT_ID, agent.getId());
properties.put(InstanceConstants.FIELD_IMAGE_UUID, builder.getImageUuid());
properties.put(INSTANCE.NAME, builder.getName());
properties.put(INSTANCE.ZONE_ID, agent.getZoneId());
properties.put(INSTANCE.KIND, builder.getInstanceKind());
properties.put(InstanceConstants.FIELD_PRIVILEGED, builder.isPrivileged());
properties.put(InstanceConstants.FIELD_NETWORK_IDS, getNetworkIds(agent, builder));
properties.putAll(builder.getParams());
addAdditionalProperties(properties, agent, builder);
return objectManager.convertToPropertiesFor(Instance.class, properties);
}
protected void addAdditionalProperties(Map<Object, Object> properties, Agent agent, AgentInstanceBuilderImpl builder) {
}
@Override
public Agent createAgent(Instance instance) {
if (shouldCreateAgent(instance)) {
Map<String, Object> labels = DataAccessor.fieldMap(instance, InstanceConstants.FIELD_LABELS);
Set<String> filteredRoles = new HashSet<>();
String rolesVal = labels.get(SystemLabels.LABEL_AGENT_ROLE) != null ? labels.get(SystemLabels.LABEL_AGENT_ROLE).toString() : null;
if (rolesVal != null) {
String[] roles = rolesVal.split(",");
for (String r : roles) {
if ("environment".equals(r) || "agent".equals(r)) {
filteredRoles.add(r);
} else if ("environmentAdmin".equals(r) && isSystem(instance)) {
filteredRoles.add(r);
}
}
}
return getAgent(new AgentInstanceBuilderImpl(this, instance, filteredRoles));
}
return null;
}
@SuppressWarnings("unchecked")
protected boolean isLBSystemService(Service service) {
if (!service.getKind().equalsIgnoreCase(ServiceConstants.KIND_LOAD_BALANCER_SERVICE)) {
return false;
}
Map<String, Object> data = DataAccessor.fields(service)
.withKey("launchConfig").withDefault(Collections.EMPTY_MAP)
.as(Map.class);
Object imageObj = data.get(InstanceConstants.FIELD_IMAGE_UUID);
if (imageObj == null) {
return false;
}
Pair<String, String> defaultImage = getImageAndRepo(LB_IMAGE_UUID.get().toLowerCase());
Pair<String, String> instanceImage = getImageAndRepo(imageObj.toString().toLowerCase());
return defaultImage.getRight().equalsIgnoreCase(instanceImage.getRight())
&& defaultImage.getLeft().equalsIgnoreCase(instanceImage.getLeft());
}
private Pair<String, String> getImageAndRepo(String imageUUID) {
DockerImage dockerImage = DockerImage.parse(imageUUID);
String[] splitted = dockerImage.getFullName().split("/");
if (splitted.length < 2) {
return Pair.of("", "");
}
String repo = splitted[0];
// split the version
String image = splitted[1].split(":")[0];
return Pair.of(repo, image);
}
private boolean isSystem(Instance instance) {
List<? extends Service> services = instanceDao.findServicesNonRemovedLinksOnly(instance);
for (Service service : services) {
Stack stack = objectManager.loadResource(Stack.class, service.getStackId());
if (ServiceConstants.isSystem(stack) || isLBSystemService(service)) {
return true;
}
}
return false;
}
@Override
public void deleteAgent(Instance instance) {
if (!shouldCreateAgent(instance) || instance.getAgentId() == null) {
return;
}
Agent agent = objectManager.loadResource(Agent.class, instance.getAgentId());
if (agent == null) {
return;
}
if (CommonStatesConstants.DEACTIVATING.equals(agent.getState())) {
return;
}
try {
processManager.scheduleStandardProcess(StandardProcess.DEACTIVATE, agent,
ProcessUtils.chainInData(new HashMap<String, Object>(), AgentConstants.PROCESS_DEACTIVATE, AgentConstants.PROCESS_REMOVE));
} catch (ProcessCancelException e) {
try {
processManager.scheduleStandardProcess(StandardProcess.REMOVE, agent, null);
} catch (ProcessCancelException e1) {
}
}
}
protected boolean shouldCreateAgent(Instance instance) {
Map<String, Object> labels = DataAccessor.fieldMap(instance, InstanceConstants.FIELD_LABELS);
return BooleanUtils.toBoolean(ObjectUtils.toString(labels.get(SystemLabels.LABEL_AGENT_CREATE), null));
}
protected Agent getAgent(AgentInstanceBuilderImpl builder) {
String uri = getUri(builder);
Agent agent = factoryDao.getAgentByUri(uri);
if (agent == null) {
agent = createAgent(uri, builder);
}
agent = resourceMonitor.waitFor(agent, new ResourcePredicate<Agent>() {
@Override
public boolean evaluate(Agent agent) {
return factoryDao.areAllCredentialsActive(agent);
}
@Override
public String getMessage() {
return "active credentials";
}
});
return agent;
}
protected Instance createInstance(final Agent agent, final Map<String, Object> properties, final AgentInstanceBuilderImpl builder) {
return lockManager.lock(new AgentInstanceAgentCreateLock(agent.getUri()), new LockCallback<Instance>() {
@Override
public Instance doWithLock() {
Instance instance = factoryDao.getInstanceByAgent(agent.getId());
if (instance == null) {
instance = DeferredUtils.nest(new Callable<Instance>() {
@Override
public Instance call() throws Exception {
return resourceDao.createAndSchedule(Instance.class, properties);
}
});
}
return instance;
}
});
}
protected Agent createAgent(final String uri, final AgentInstanceBuilderImpl builder) {
return lockManager.lock(new AgentInstanceAgentCreateLock(uri), new LockCallback<Agent>() {
@Override
public Agent doWithLock() {
Agent agent = factoryDao.getAgentByUri(uri);
final Map<String, Object> data = new HashMap<>();
if (builder.getResourceAccountId() != null) {
data.put(AgentConstants.DATA_AGENT_RESOURCES_ACCOUNT_ID, builder.getResourceAccountId());
}
if (builder.getRequestedRoles() != null) {
data.put(AgentConstants.DATA_REQUESTED_ROLES, new ArrayList<String>(builder.getRequestedRoles()));
}
if (agent == null) {
agent = DeferredUtils.nest(new Callable<Agent>() {
@Override
public Agent call() throws Exception {
return resourceDao.createAndSchedule(Agent.class,
AGENT.DATA, data,
AGENT.URI, uri,
AGENT.MANAGED_CONFIG, builder.isManagedConfig(),
AGENT.ZONE_ID, builder.getZoneId());
}
});
}
return agent;
}
});
}
protected Long getAccountId(AgentInstanceBuilderImpl builder) {
if (builder.isAccountOwned() && builder.getAccountId() != null) {
return builder.getAccountId();
}
return accountDao.getSystemAccount().getId();
}
protected String getUri(AgentInstanceBuilderImpl builder) {
return builder.getUri();
}
@SuppressWarnings("unchecked")
protected List<Long> getNetworkIds(Agent agent, AgentInstanceBuilderImpl builder) {
List<Long> networkIds = new ArrayList<>();
if (builder.getParams().get(InstanceConstants.FIELD_NETWORK_IDS) != null) {
networkIds = (List<Long>) builder.getParams().get(InstanceConstants.FIELD_NETWORK_IDS);
}
return networkIds;
}
}