package io.cattle.platform.process.instance;
import static io.cattle.platform.core.model.tables.VolumeTable.*;
import io.cattle.platform.core.constants.InstanceConstants;
import io.cattle.platform.core.constants.VolumeConstants;
import io.cattle.platform.core.dao.StoragePoolDao;
import io.cattle.platform.core.dao.VolumeDao;
import io.cattle.platform.core.model.Instance;
import io.cattle.platform.core.model.Volume;
import io.cattle.platform.engine.handler.HandlerResult;
import io.cattle.platform.engine.handler.ProcessPreListener;
import io.cattle.platform.engine.process.ProcessInstance;
import io.cattle.platform.engine.process.ProcessState;
import io.cattle.platform.object.ObjectManager;
import io.cattle.platform.object.process.ObjectProcessManager;
import io.cattle.platform.object.util.DataAccessor;
import io.cattle.platform.process.common.handler.AbstractObjectProcessLogic;
import io.cattle.platform.util.exception.ExecutionException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.commons.lang3.StringUtils;
@Named
public class InstanceVolumeLookupPreCreate extends AbstractObjectProcessLogic implements ProcessPreListener {
private static final String LABEL_VOLUME_AFFINITY = "io.rancher.scheduler.affinity:volumes";
@Inject
ObjectManager objectManager;
@Inject
ObjectProcessManager processManager;
@Inject
StoragePoolDao storagePoolDao;
@Inject
VolumeDao volumeDao;
@Override
public String[] getProcessNames() {
return new String[] { "instance.create" };
}
/**
* Looks for volumes in the dataVolumes field and converts them to dataVolumeMounts.
* Will convert if the volume name matches a shared volume or an unmapped volume (one with no VolumeStoragePoolMap entry).
* Additionally, will map volumes found in "local" pools if the name appears in the io.rancher.scheduler.affinity:volumes label.
*/
@Override
public HandlerResult handle(ProcessState state, ProcessInstance process) {
Instance instance = (Instance)state.getResource();
if (!InstanceConstants.CONTAINER_LIKE.contains(instance.getKind())) {
return null;
}
Object va = DataAccessor.fieldMap(instance, InstanceConstants.FIELD_LABELS).get(LABEL_VOLUME_AFFINITY);
Set<String> affinities = new HashSet<String>();
if (va != null) {
affinities.addAll(Arrays.asList(va.toString().split(",")));
}
String volumeDriver = DataAccessor.fieldString(instance, InstanceConstants.FIELD_VOLUME_DRIVER);
Map<Object, Object> data = new HashMap<>();
List<String> dataVolumes = DataAccessor.fieldStringList(instance, InstanceConstants.FIELD_DATA_VOLUMES);
Map<String, Object> dataVolumeMounts = DataAccessor.fieldMap(instance, InstanceConstants.FIELD_DATA_VOLUME_MOUNTS);
List<String> newDataVolumes = new ArrayList<String>();
data.put(InstanceConstants.FIELD_DATA_VOLUMES, newDataVolumes);
data.put(InstanceConstants.FIELD_DATA_VOLUME_MOUNTS, dataVolumeMounts);
for (String v : dataVolumes) {
String volName = null;
String volPath = null;
String[] parts = v.split(":", 2);
if (parts.length == 2 && !parts[0].startsWith("/") && parts[1].startsWith("/")) {
// named volume
volName = parts[0];
volPath = parts[1];
} else if (isNonlocalDriver(volumeDriver) && parts.length == 2 && parts[0].startsWith("/") && !parts[1].startsWith("/")) {
// anonymous volume with mount permissions
volName = UUID.randomUUID().toString();
volPath = parts[0] + ":" + parts[1];
v = volName + ":" + volPath;
} else if (isNonlocalDriver(volumeDriver) && parts.length == 1 && parts[0].startsWith("/")) {
// anonymous volume
volName = UUID.randomUUID().toString();
volPath = parts[0];
v = volName + ":" + volPath;
} else {
newDataVolumes.add(v);
continue;
}
boolean createVol = true;
if (volName != null) {
List<? extends Volume> volumes = volumeDao.findSharedOrUnmappedVolumes(instance.getAccountId(), volName);
if (volumes.isEmpty() && affinities.contains(volName)) {
// Any volumes found here will be mapped to local (docker, sim) pools
volumes = objectManager.find(Volume.class, VOLUME.ACCOUNT_ID, instance.getAccountId(), VOLUME.NAME, volName, VOLUME.REMOVED, null);
}
if (volumes.size() == 1) {
dataVolumeMounts.put(volPath, volumes.get(0).getId());
createVol = false;
} else if (volumes.size() > 1) {
objectProcessManager.scheduleProcessInstance(InstanceConstants.PROCESS_REMOVE, instance, null);
ExecutionException ex =
new ExecutionException(
String.format("Could not process named volume %s. More than one volume " + "with that name exists.", volName));
ex.setResources(state.getResource());
throw ex;
}
}
if (createVol && isNonlocalDriver(volumeDriver)) {
Volume newVol = volumeDao.createVolumeForDriver(instance.getAccountId(), volName, volumeDriver);
dataVolumeMounts.put(volPath, newVol.getId());
}
newDataVolumes.add(v);
}
return new HandlerResult(data);
}
boolean isNonlocalDriver(String volumeDriver) {
return StringUtils.isNotEmpty(volumeDriver) && !VolumeConstants.LOCAL_DRIVER.equals(volumeDriver);
}
}