package io.cattle.platform.process.containerevent;
import static io.cattle.platform.core.constants.CommonStatesConstants.*;
import static io.cattle.platform.core.constants.ContainerEventConstants.*;
import static io.cattle.platform.core.constants.InstanceConstants.*;
import static io.cattle.platform.core.model.tables.HostTable.*;
import static io.cattle.platform.core.util.SystemLabels.*;
import static io.cattle.platform.docker.constants.DockerInstanceConstants.*;
import io.cattle.platform.agent.AgentLocator;
import io.cattle.platform.agent.RemoteAgent;
import io.cattle.platform.archaius.util.ArchaiusUtil;
import io.cattle.platform.async.utils.TimeoutException;
import io.cattle.platform.core.constants.CommonStatesConstants;
import io.cattle.platform.core.constants.InstanceConstants;
import io.cattle.platform.core.constants.NetworkConstants;
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.dao.NetworkDao;
import io.cattle.platform.core.model.ContainerEvent;
import io.cattle.platform.core.model.Host;
import io.cattle.platform.core.model.Instance;
import io.cattle.platform.core.util.SystemLabels;
import io.cattle.platform.engine.handler.HandlerResult;
import io.cattle.platform.engine.process.ProcessInstance;
import io.cattle.platform.engine.process.ProcessState;
import io.cattle.platform.engine.process.impl.ProcessCancelException;
import io.cattle.platform.eventing.exception.EventExecutionException;
import io.cattle.platform.eventing.model.Event;
import io.cattle.platform.eventing.model.EventVO;
import io.cattle.platform.lock.LockCallback;
import io.cattle.platform.lock.LockManager;
import io.cattle.platform.object.process.StandardProcess;
import io.cattle.platform.object.resource.ResourceMonitor;
import io.cattle.platform.object.util.DataAccessor;
import io.cattle.platform.object.util.DataUtils;
import io.cattle.platform.process.base.AbstractDefaultProcessHandler;
import io.cattle.platform.storage.service.StorageService;
import io.cattle.platform.util.type.CollectionUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.netflix.config.DynamicBooleanProperty;
public class ContainerEventCreate extends AbstractDefaultProcessHandler {
public static final String AGENT_ID = "agentId";
public static final String INSTANCE_INSPECT_EVENT_NAME = "compute.instance.inspect";
private static final String INSTANCE_INSPECT_DATA_NAME = "instanceInspect";
private static final DynamicBooleanProperty MANAGE_NONRANCHER_CONTAINERS = ArchaiusUtil.getBoolean("manage.nonrancher.containers");
private static final String INSPECT_ENV = "Env";
private static final String INSPECT_LABELS = "Labels";
private static final String INSPECT_NAME = "Name";
private static final String FIELD_DOCKER_INSPECT = "dockerInspect";
private static final String INSPECT_CONFIG = "Config";
private static final String IMAGE_PREFIX = "docker:";
private static final String IMAGE_KIND_PATTERN = "^(sim|docker):.*";
private static final String RANCHER_UUID_ENV_VAR = "RANCHER_UUID=";
private static final Logger log = LoggerFactory.getLogger(ContainerEventCreate.class);
@Inject
StorageService storageService;
@Inject
NetworkDao networkDao;
@Inject
AccountDao accountDao;
@Inject
InstanceDao instanceDao;
@Inject
LockManager lockManager;
@Inject
ResourceMonitor resourceMonitor;
@Inject
AgentLocator agentLocator;
@Inject
GenericResourceDao resourceDao;
Cache<String, String> scheduled = CacheBuilder.newBuilder()
.expireAfterWrite(15, TimeUnit.MINUTES)
.build();
public boolean checkOrRecordScheduled(String id, String event) {
if (event.equals(scheduled.getIfPresent(id))) {
// Create container events only so often.
return true;
}
scheduled.put(id, event);
return false;
}
public void invalidate(String id) {
scheduled.invalidate(id);
}
@Override
public HandlerResult handle(ProcessState state, ProcessInstance process) {
HandlerResult result = null;
try {
result = handleInternal(state, process);
} catch (TimeoutException e) {
// ignore;
}
String externalId = ((ContainerEvent)state.getResource()).getExternalId();
if (StringUtils.isNotBlank(externalId)) {
scheduled.invalidate(((ContainerEvent)state.getResource()).getExternalId());
}
return result;
}
protected HandlerResult handleInternal(ProcessState state, ProcessInstance process) {
if (!MANAGE_NONRANCHER_CONTAINERS.get()) {
return null;
}
final ContainerEvent event = (ContainerEvent)state.getResource();
Host host = objectManager.findOne(Host.class, HOST.ID, event.getHostId());
if (host == null || host.getRemoved() != null
|| host.getState().equalsIgnoreCase(CommonStatesConstants.REMOVING)) {
log.info("Host [{}] is unavailable. Not processing container event [{}].", event.getHostId(), event.getId());
return null;
}
final Map<String, Object> data = state.getData();
HandlerResult result = lockManager.lock(new ContainerEventInstanceLock(event.getAccountId(), event.getExternalId()), new LockCallback<HandlerResult>() {
@Override
public HandlerResult doWithLock() {
Map<String, Object> inspect = getInspect(event, data, false);
String rancherUuid = getRancherUuidLabel(inspect, data);
Instance instance = instanceDao.getInstanceByUuidOrExternalId(event.getAccountId(), rancherUuid, event.getExternalId());
try {
String status = event.getExternalStatus();
if (status.equals(EVENT_START) && instance == null) {
scheduleInstance(event, instance, data);
return null;
}
if (instance == null || instance.getRemoved() != null) {
return null;
}
String state = instance.getState();
if (EVENT_START.equals(status)) {
if (STATE_CREATING.equals(state) || STATE_RUNNING.equals(state) || STATE_STARTING.equals(state) || STATE_RESTARTING.equals(status))
return null;
if (STATE_STOPPING.equals(state)) {
// handle docker restarts
instance = resourceMonitor.waitForNotTransitioning(instance, 3000L);
}
objectProcessManager.scheduleProcessInstance(PROCESS_START, instance, makeData());
} else if (EVENT_STOP.equals(status) || EVENT_DIE.equals(status)) {
if (STATE_STOPPED.equals(state) || STATE_STOPPING.equals(state))
return null;
objectProcessManager.scheduleProcessInstance(PROCESS_STOP, instance, makeData());
} else if (EVENT_DESTROY.equals(status)) {
if (REMOVED.equals(state) || REMOVING.equals(state) || PURGED.equals(state) || PURGING.equals(state))
return null;
Map<String, Object> data = makeData();
try {
objectProcessManager.scheduleStandardProcess(StandardProcess.REMOVE, instance, data);
} catch (ProcessCancelException e) {
if (STATE_STOPPING.equals(state)) {
// handle docker forced stop and remove
instance = resourceMonitor.waitForNotTransitioning(instance, 3000L);
objectProcessManager.scheduleStandardProcess(StandardProcess.REMOVE, instance, data);
} else {
data.put(REMOVE_OPTION, true);
objectProcessManager.scheduleProcessInstance(PROCESS_STOP, instance, data);
}
}
}
} catch (ProcessCancelException e) {
// ignore
}
return null;
}
});
return result;
}
void scheduleInstance(ContainerEvent event, Instance instance, Map<String, Object> data) {
Map<String, Object> inspect = getInspect(event, data, true);
final Long accountId = event.getAccountId();
final String externalId = event.getExternalId();
instance = objectManager.newRecord(Instance.class);
instance.setKind(KIND_CONTAINER);
instance.setAccountId(accountId);
instance.setExternalId(externalId);
instance.setNativeContainer(true);
setName(inspect, data, instance);
setNetwork(inspect, data, instance);
setImage(event, instance);
setHost(event, instance);
setLabels(inspect, data, instance);
resourceDao.createAndSchedule(instance, makeData());
}
private void setLabels(Map<String, Object> inspect, Map<String, Object> data, Instance instance) {
Map<String, Object> labels = DataAccessor.fieldMap(instance, InstanceConstants.FIELD_LABELS);
if ("true".equals(labels.get(SystemLabels.LABEL_RANCHER_NETWORK))) {
labels.put(SystemLabels.LABEL_CNI_NETWORK, NetworkConstants.NETWORK_MODE_MANAGED);
}
labels.putAll(getLabels(inspect, data));
DataAccessor.setField(instance, InstanceConstants.FIELD_LABELS, labels);
}
private Map<String, Object> getInspect(ContainerEvent event, Map<String, Object> data, boolean remote) {
Object inspectObj = DataUtils.getFields(event).get(FIELD_DOCKER_INSPECT);
if (inspectObj == null && remote) {
Event inspectEvent = newInspectEvent(null, event.getExternalId());
inspectObj = callAgentForInspect(data, inspectEvent);
}
return CollectionUtils.toMap(inspectObj);
}
private Object callAgentForInspect(Map<String, Object> data, Event inspectEvent) {
Long agentId = DataAccessor.fromMap(data).withScope(ContainerEventCreate.class).withKey(AGENT_ID).as(Long.class);
Object inspect = null;
if (agentId != null) {
RemoteAgent agent = agentLocator.lookupAgent(agentId);
try {
Event result = agent.callSync(inspectEvent);
inspect = CollectionUtils.getNestedValue(result.getData(), INSTANCE_INSPECT_DATA_NAME);
} catch (EventExecutionException e) {
log.warn("Unable to retrieve inspect for event [" + inspectEvent + "]", e);
}
}
return inspect;
}
private Event newInspectEvent(String containerName, String containerId) {
Map<String, Object> inspectEventData = new HashMap<String, Object>();
Map<String, Object> reqData = CollectionUtils.asMap("kind", "docker");
if (StringUtils.isNotEmpty(containerId))
reqData.put("id", containerId);
if (StringUtils.isNotEmpty(containerName))
reqData.put("name", containerName);
inspectEventData.put(INSTANCE_INSPECT_DATA_NAME, reqData);
EventVO<Object> inspectEvent = EventVO.newEvent(INSTANCE_INSPECT_EVENT_NAME).withData(inspectEventData);
inspectEvent.setResourceType(INSTANCE_INSPECT_DATA_NAME);
return inspectEvent;
}
public static boolean isNativeDockerStart(ProcessState state) {
return DataAccessor.fromMap(state.getData()).withKey(PROCESS_DATA_NO_OP).withDefault(false).as(Boolean.class);
}
protected Map<String, Object> makeData() {
Map<String, Object> data = new HashMap<String, Object>();
DataAccessor.fromMap(data).withKey(PROCESS_DATA_NO_OP).set(true);
return data;
}
void setHost(ContainerEvent event, Instance instance) {
DataAccessor.fields(instance).withKey(FIELD_REQUESTED_HOST_ID).set(event.getHostId());
}
void setName(Map<String, Object> inspect, Map<String, Object> data, Instance instance) {
String name = DataAccessor.fromMap(data).withKey(CONTAINER_EVENT_SYNC_NAME).as(String.class);
if (StringUtils.isBlank(name))
name = DataAccessor.fromMap(inspect).withKey(INSPECT_NAME).as(String.class);
if (name != null)
name = name.replaceFirst("/", "");
instance.setName(name);
}
void setNetwork(Map<String, Object> inspect, Map<String, Object> data, Instance instance) {
String inspectNetMode = getInspectNetworkMode(inspect);
String networkMode = checkNoneNetwork(inspect);
if (networkMode == null) {
networkMode = checkBlankNetwork(inspect);
}
if (networkMode == null) {
networkMode = checkContainerNetwork(inspectNetMode, instance, data);
}
if (networkMode == null) {
networkMode = inspectNetMode;
}
DataAccessor.fields(instance).withKey(FIELD_NETWORK_MODE).set(networkMode);
String ip = getDockerIp(inspect);
if (StringUtils.isNotEmpty(ip)) {
DataAccessor.fields(instance).withKey(FIELD_REQUESTED_IP_ADDRESS).set(ip);
}
}
private String getInspectNetworkMode(Map<String, Object> inspect) {
Object tempNM = CollectionUtils.getNestedValue(inspect, "HostConfig", "NetworkMode");
String inspectNetMode = tempNM == null ? "" : tempNM.toString();
return inspectNetMode;
}
private String checkNoneNetwork(Map<String, Object> inspect) {
Object netDisabledObj = CollectionUtils.getNestedValue(inspect, "Config", "NetworkDisabled");
if (Boolean.TRUE.equals(netDisabledObj)) {
return NetworkConstants.NETWORK_MODE_NONE;
}
return null;
}
private String checkBlankNetwork(Map<String, Object> inspect) {
String inspectNm = getInspectNetworkMode(inspect);
if (StringUtils.isBlank(inspectNm)) {
return NetworkConstants.NETWORK_MODE_BRIDGE;
}
return null;
}
/*
* If mode is container, will attempt to look up the corresponding container in rancher. If it is found, will also have the side effect of setting
* networkContainerId on the instance.
*/
private String checkContainerNetwork(String inspectNetMode, Instance instance, Map<String, Object> data) {
if (!StringUtils.startsWith(inspectNetMode, NetworkConstants.NETWORK_MODE_CONTAINER))
return null;
String[] parts = StringUtils.split(inspectNetMode, ":", 2);
String targetContainer = null;
if (parts.length == 2) {
targetContainer = parts[1];
Instance netFromInstance = instanceDao.getInstanceByUuidOrExternalId(instance.getAccountId(), targetContainer, targetContainer);
if (netFromInstance == null) {
Event inspectEvent = newInspectEvent(targetContainer, targetContainer);
Object inspectObj = callAgentForInspect(data, inspectEvent);
Map<String, Object> inspect = CollectionUtils.toMap(inspectObj);
String uuid = getRancherUuidLabel(inspect, null);
String externalId = inspect.get("Id") != null ? inspect.get("Id").toString() : null;
netFromInstance = instanceDao.getInstanceByUuidOrExternalId(instance.getAccountId(), uuid, externalId);
}
if (netFromInstance != null) {
DataAccessor.fields(instance).withKey(FIELD_NETWORK_CONTAINER_ID).set(netFromInstance.getId());
return NetworkConstants.NETWORK_MODE_CONTAINER;
}
}
log.warn("Problem configuring container networking for container [externalId: {}]. Could not find target container: [{}].", instance.getExternalId(),
targetContainer);
return NetworkConstants.NETWORK_MODE_NONE;
}
String getDockerIp(Map<String, Object> inspect) {
Object ip = CollectionUtils.getNestedValue(inspect, "NetworkSettings", "IPAddress");
if (ip != null)
return ip.toString();
return null;
}
void setImage(ContainerEvent event, Instance instance) {
String imageUuid = event.getExternalFrom();
// Somewhat of a hack, but needed for testing against sim contexts
if (!imageUuid.matches(IMAGE_KIND_PATTERN)) {
imageUuid = IMAGE_PREFIX + imageUuid;
}
DataAccessor.fields(instance).withKey(FIELD_IMAGE_UUID).set(imageUuid);
}
String getRancherUuidLabel(Map<String, Object> inspect, Map<String, Object> data) {
return getLabel(LABEL_RANCHER_UUID, RANCHER_UUID_ENV_VAR, inspect, data);
}
String getLabel(String labelKey, Map<String, Object> inspect, Map<String, Object> data) {
return getLabel(labelKey, null, inspect, data);
}
Map<String, Object> getLabels(Map<String, Object> inspect, Map<String, Object> data) {
Map<String, Object> labels = new HashMap<>();
Map<String, Object> labelsFromData = CollectionUtils.toMap(DataAccessor.fromMap(data).withKey(CONTAINER_EVENT_SYNC_LABELS).get());
Map<String, Object> config = CollectionUtils.toMap(inspect.get(INSPECT_CONFIG));
Map<String, Object> inspectLabels = CollectionUtils.toMap(config.get(INSPECT_LABELS));
labels.putAll(inspectLabels);
labels.putAll(labelsFromData);
return labels;
}
@SuppressWarnings("unchecked")
String getLabel(String labelKey, String envVarPrefix, Map<String, Object> inspect, Map<String, Object> data) {
Map<String, Object> labelsFromData = CollectionUtils.toMap(DataAccessor.fromMap(data).withKey(CONTAINER_EVENT_SYNC_LABELS).get());
String label = labelsFromData.containsKey(labelKey) ? labelsFromData.get(labelKey).toString() : null;
if (StringUtils.isNotEmpty(label))
return label;
Map<String, Object> config = CollectionUtils.toMap(inspect.get(INSPECT_CONFIG));
if (config != null) {
Map<String, String> labels = CollectionUtils.toMap(config.get(INSPECT_LABELS));
label = labels.get(labelKey);
if (StringUtils.isNotEmpty(label))
return label;
}
if (envVarPrefix == null)
return null;
if (config != null) {
List<String> envVars = (List<String>)CollectionUtils.toList(config.get(INSPECT_ENV));
for (String envVar : envVars) {
if (envVar.startsWith(envVarPrefix))
return envVar.substring(envVarPrefix.length());
}
}
return null;
}
}