package io.cattle.platform.docker.process.instancehostmap;
import static io.cattle.platform.core.model.tables.IpAddressTable.*;
import static io.cattle.platform.core.model.tables.MountTable.*;
import static io.cattle.platform.docker.constants.DockerInstanceConstants.*;
import io.cattle.iaas.labels.service.LabelsService;
import io.cattle.platform.core.constants.CommonStatesConstants;
import io.cattle.platform.core.constants.InstanceConstants;
import io.cattle.platform.core.constants.IpAddressConstants;
import io.cattle.platform.core.constants.PortConstants;
import io.cattle.platform.core.constants.VolumeConstants;
import io.cattle.platform.core.dao.HostDao;
import io.cattle.platform.core.dao.InstanceDao;
import io.cattle.platform.core.dao.IpAddressDao;
import io.cattle.platform.core.dao.NicDao;
import io.cattle.platform.core.model.Host;
import io.cattle.platform.core.model.Instance;
import io.cattle.platform.core.model.InstanceHostMap;
import io.cattle.platform.core.model.IpAddress;
import io.cattle.platform.core.model.Mount;
import io.cattle.platform.core.model.Nic;
import io.cattle.platform.core.model.Port;
import io.cattle.platform.core.model.StoragePool;
import io.cattle.platform.core.model.Volume;
import io.cattle.platform.core.util.PortSpec;
import io.cattle.platform.docker.constants.DockerInstanceConstants;
import io.cattle.platform.docker.constants.DockerIpAddressConstants;
import io.cattle.platform.docker.process.dao.DockerComputeDao;
import io.cattle.platform.docker.process.lock.DockerStoragePoolVolumeCreateLock;
import io.cattle.platform.docker.process.util.DockerProcessUtils;
import io.cattle.platform.docker.storage.DockerStoragePoolDriver;
import io.cattle.platform.docker.transform.DockerInspectTransformVolume;
import io.cattle.platform.docker.transform.DockerTransformer;
import io.cattle.platform.engine.handler.HandlerResult;
import io.cattle.platform.engine.handler.ProcessPostListener;
import io.cattle.platform.engine.process.ProcessInstance;
import io.cattle.platform.engine.process.ProcessState;
import io.cattle.platform.json.JsonMapper;
import io.cattle.platform.lock.LockCallback;
import io.cattle.platform.lock.LockManager;
import io.cattle.platform.object.process.StandardProcess;
import io.cattle.platform.object.util.DataAccessor;
import io.cattle.platform.process.common.handler.AbstractObjectProcessLogic;
import io.cattle.platform.process.common.lock.MountVolumeLock;
import io.cattle.platform.util.type.CollectionUtils;
import io.cattle.platform.util.type.Priority;
import io.github.ibuildthecloud.gdapi.condition.Condition;
import io.github.ibuildthecloud.gdapi.condition.ConditionType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DockerPostInstanceHostMapActivate extends AbstractObjectProcessLogic implements ProcessPostListener,
Priority {
private static final Logger log = LoggerFactory.getLogger(DockerPostInstanceHostMapActivate.class);
@Inject
JsonMapper jsonMapper;
@Inject
IpAddressDao ipAddressDao;
@Inject
DockerComputeDao dockerDao;
@Inject
NicDao nicDao;
@Inject
LockManager lockManager;
@Inject
HostDao hostDao;
@Inject
DockerTransformer transformer;
@Inject
LabelsService labelsService;
@Inject
InstanceDao instanceDao;
@Override
public String[] getProcessNames() {
return new String[] { "instancehostmap.activate" };
}
@SuppressWarnings("unchecked")
@Override
public HandlerResult handle(ProcessState state, ProcessInstance process) {
InstanceHostMap map = (InstanceHostMap)state.getResource();
Instance instance = getObjectManager().loadResource(Instance.class, map.getInstanceId());
Host host = getObjectManager().loadResource(Host.class, map.getHostId());
String dockerIp = DockerProcessUtils.getDockerIp(instance);
Nic nic = nicDao.getPrimaryNic(instance);
IpAddress hostIpAddress = hostDao.getIpAddressForHost(host.getId());
IpAddress primaryIp = ipAddressDao.getInstancePrimaryIp(instance);
List<String> ports = DataAccessor.fields(instance).withKey(DockerInstanceConstants.FIELD_DOCKER_PORTS).as(jsonMapper, List.class);
if (dockerIp != null) {
primaryIp = processDockerIp(instance, nic, primaryIp, dockerIp);
}
if (hostIpAddress != null && ports != null) {
processPorts(primaryIp, hostIpAddress, ports, instance, nic, host);
}
processVolumes(instance, host, state);
processLabels(instance);
nativeDockerBackPopulate(instance);
instanceDao.clearCacheInstanceData(instance.getId());
return null;
}
@SuppressWarnings("unchecked")
void processLabels(Instance instance) {
Map<String, String> labels = CollectionUtils.toMap(CollectionUtils.getNestedValue(instance.getData(), FIELD_DOCKER_INSPECT, "Config",
"Labels"));
for (Map.Entry<String, String>label : labels.entrySet()) {
labelsService.createContainerLabel(instance.getAccountId(), instance.getId(), label.getKey(), label.getValue());
}
Map<String, Object> inspect = (Map<String, Object>)instance.getData().get(FIELD_DOCKER_INSPECT);
if (inspect == null) {
return;
}
transformer.setLabels(instance, inspect);
objectManager.persist(instance);
}
@SuppressWarnings({ "unchecked" })
protected void nativeDockerBackPopulate(Instance instance) {
Map<String, Object> inspect = (Map<String, Object>)instance.getData().get(FIELD_DOCKER_INSPECT);
if (inspect == null || instance.getNativeContainer() == null || !instance.getNativeContainer().booleanValue()) {
return;
}
transformer.transform(inspect, instance);
objectManager.persist(instance);
}
@SuppressWarnings({ "unchecked" })
protected void processVolumes(Instance instance, Host host, ProcessState state) {
Map<String, Object> inspect = (Map<String, Object>) instance.getData().get(FIELD_DOCKER_INSPECT);
List<Object> mounts = (List<Object>) instance.getData().get(FIELD_DOCKER_MOUNTS);
if (inspect == null && mounts == null) {
return;
}
List<DockerInspectTransformVolume> dockerVolumes = transformer.transformVolumes(inspect, mounts);
if (dockerVolumes.size() == 0) {
/* If there are no volumes avoid looking for a pool because one may not exists
* for this host and we don't want to warn about that
*/
return;
}
StoragePool dockerLocalStoragePool = null;
Map<String, StoragePool> pools = new HashMap<String, StoragePool>();
for (StoragePool pool : objectManager.mappedChildren(host, StoragePool.class)) {
if (DockerStoragePoolDriver.isDockerPool(pool) &&
(VolumeConstants.LOCAL_DRIVER.equals(pool.getDriverName()) || StringUtils.isEmpty(pool.getDriverName()))) {
dockerLocalStoragePool = pool;
}
if (StringUtils.isNotEmpty(pool.getDriverName())) {
pools.put(pool.getDriverName(), pool);
}
}
for (DockerInspectTransformVolume dVol : dockerVolumes) {
String driver = dVol.getDriver();
StoragePool pool = null;
if (driver != null) {
pool = pools.get(driver);
}
if (pool == null) {
pool = dockerLocalStoragePool;
}
Volume volume = createVolumeInStoragePool(pool, instance, dVol);
String action;
if (CommonStatesConstants.REQUESTED.equals(volume.getState())) {
action = "Created";
objectProcessManager.scheduleStandardProcess(StandardProcess.CREATE, volume, state.getData());
} else {
action = "Using existing";
}
log.debug("{} volume [{}] in storage pool [{}].", action, volume.getId(), pool.getId());
Mount mount = mountVolume(volume, instance, dVol.getContainerPath(), dVol.getAccessMode());
log.info("Volme mount created. Volume id [{}], instance id [{}], mount id [{}]", volume.getId(), instance.getId(), mount.getId());
objectProcessManager.scheduleStandardProcess(StandardProcess.CREATE, mount, null);
}
}
protected Volume createVolumeInStoragePool(final StoragePool storagePool, final Instance instance, final DockerInspectTransformVolume dVol) {
Volume volume = dockerDao.getDockerVolumeInPool(dVol.getUri(), dVol.getExternalId(), storagePool);
if (volume != null)
return volume;
return lockManager.lock(new DockerStoragePoolVolumeCreateLock(storagePool, dVol.getExternalId()), new LockCallback<Volume>() {
@Override
public Volume doWithLock() {
Volume volume = dockerDao.createDockerVolumeInPool(instance.getAccountId(), dVol.getName(), dVol.getUri(), dVol.getExternalId(),
dVol.getDriver(), storagePool, dVol.isBindMount(), instance.getNativeContainer());
return volume;
}
});
}
protected Mount mountVolume(final Volume volume, final Instance instance, final String path, final String permissions) {
return lockManager.lock(new MountVolumeLock(volume.getId()), new LockCallback<Mount>() {
@Override
public Mount doWithLock() {
Map<Object, Object> criteria = new HashMap<Object, Object>();
criteria.put(MOUNT.VOLUME_ID, volume.getId());
criteria.put(MOUNT.INSTANCE_ID, instance.getId());
criteria.put(MOUNT.PATH, path);
criteria.put(MOUNT.REMOVED, null);
criteria.put(MOUNT.STATE, new Condition(ConditionType.NE, CommonStatesConstants.INACTIVE));
Mount mount = objectManager.findAny(Mount.class, criteria);
if (mount != null) {
if (!mount.getPath().equalsIgnoreCase(permissions))
objectManager.setFields(mount, MOUNT.PERMISSIONS, permissions);
return mount;
}
return objectManager.create(Mount.class, MOUNT.ACCOUNT_ID, instance.getAccountId(), MOUNT.INSTANCE_ID, instance.getId(), MOUNT.VOLUME_ID,
volume.getId(), MOUNT.PATH, path, MOUNT.PERMISSIONS, permissions);
}
});
}
protected IpAddress processDockerIp(Instance instance, Nic nic, IpAddress primaryIp, String dockerIp) {
if (primaryIp != null && !DockerIpAddressConstants.KIND_DOCKER.equals(primaryIp.getKind())) {
return primaryIp;
}
if (nic == null || StringUtils.isBlank(dockerIp)) {
return null;
}
if (primaryIp == null) {
primaryIp = ipAddressDao.mapNewIpAddress(nic,
IP_ADDRESS.KIND, DockerIpAddressConstants.KIND_DOCKER,
IP_ADDRESS.ROLE, IpAddressConstants.ROLE_PRIMARY,
IP_ADDRESS.ADDRESS, dockerIp);
} else if (!dockerIp.equals(primaryIp.getAddress())) {
getObjectManager().setFields(primaryIp, IP_ADDRESS.ADDRESS, dockerIp);
}
createThenActivate(primaryIp, null);
return primaryIp;
}
protected void processPorts(IpAddress primaryIp, IpAddress ipAddress, List<String> ports, Instance instance, Nic nic, final Host host) {
Long privateIpAddressId = primaryIp == null ? null : primaryIp.getId();
Map<Integer, Port> existing = new HashMap<Integer, Port>();
for (Port port : getObjectManager().children(instance, Port.class)) {
existing.put(port.getPrivatePort(), port);
}
Long publicIpAddressId = ipAddress == null ? null : ipAddress.getId();
for (String entry : ports) {
PortSpec spec = new PortSpec(entry);
Port port = existing.get(spec.getPrivatePort());
if (port == null) {
Port portObj = objectManager.newRecord(Port.class);
portObj.setAccountId(instance.getAccountId());
portObj.setKind(PortConstants.KIND_IMAGE);
portObj.setInstanceId(instance.getId());
portObj.setPublicPort(spec.getPublicPort());
portObj.setPrivatePort(spec.getPrivatePort());
portObj.setProtocol(spec.getProtocol());
if (StringUtils.isNotEmpty(spec.getIpAddress()) && !"0.0.0.0".equals(spec.getIpAddress())) {
DataAccessor.fields(portObj).withKey(PortConstants.FIELD_BIND_ADDR).set(spec.getIpAddress());
} else {
portObj.setPublicIpAddressId(publicIpAddressId);
}
portObj.setPrivateIpAddressId(privateIpAddressId);
portObj = objectManager.create(portObj);
} else {
String bindAddress = DataAccessor.fields(port).withKey(PortConstants.FIELD_BIND_ADDR).as(String.class);
boolean bindAddressNull = bindAddress == null;
if (!ObjectUtils.equals(port.getPublicPort(), spec.getPublicPort())
|| !ObjectUtils.equals(port.getPrivateIpAddressId(), privateIpAddressId)
|| (bindAddressNull && !ObjectUtils.equals(port.getPublicIpAddressId(), publicIpAddressId))
|| (!bindAddressNull && !bindAddress.equals(spec.getIpAddress()))){
if (spec.getPublicPort() != null) {
port.setPublicPort(spec.getPublicPort());
}
port.setPrivateIpAddressId(privateIpAddressId);
if (StringUtils.isNotEmpty(spec.getIpAddress()) && !"0.0.0.0".equals(spec.getIpAddress())) {
DataAccessor.fields(port).withKey(PortConstants.FIELD_BIND_ADDR).set(spec.getIpAddress());
} else {
port.setPublicIpAddressId(publicIpAddressId);
}
objectManager.persist(port);
}
}
}
List<String> publishedPorts = new ArrayList<>();
for (Port port : getObjectManager().children(instance, Port.class)) {
if (port.getRemoved() != null) {
continue;
}
createIgnoreCancel(port, null);
if (port.getPublicPort() != null) {
publishedPorts.add(new PortSpec(port).toSpec());
}
}
List<String> userPorts = DataAccessor.fieldStringList(instance, InstanceConstants.FIELD_PORTS);
if (DataAccessor.fieldStringList(instance, InstanceConstants.FIELD_USER_PORTS).isEmpty()) {
DataAccessor.fields(instance).withKey(InstanceConstants.FIELD_USER_PORTS).set(userPorts);
}
DataAccessor.fields(instance).withKey(InstanceConstants.FIELD_PORTS).set(publishedPorts);
objectManager.persist(instance);
}
@Override
public int getPriority() {
return Priority.PRE;
}
}