package io.cattle.platform.vm.process; import static io.cattle.platform.core.model.tables.InstanceTable.*; import static io.cattle.platform.core.model.tables.VolumeTable.*; import io.cattle.platform.core.addon.VirtualMachineDisk; import io.cattle.platform.core.constants.InstanceConstants; import io.cattle.platform.core.constants.StoragePoolConstants; import io.cattle.platform.core.constants.VolumeConstants; import io.cattle.platform.core.dao.ServiceDao; 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.Service; import io.cattle.platform.core.model.StoragePool; import io.cattle.platform.core.model.Volume; import io.cattle.platform.core.util.SystemLabels; import io.cattle.platform.docker.constants.DockerInstanceConstants; 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.json.JsonMapper; import io.cattle.platform.object.meta.ObjectMetaDataManager; import io.cattle.platform.object.util.DataAccessor; import io.cattle.platform.process.common.handler.AbstractObjectProcessLogic; import io.cattle.platform.util.type.Priority; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import javax.inject.Inject; import org.apache.commons.lang3.StringUtils; public class VirtualMachinePreCreate extends AbstractObjectProcessLogic implements ProcessPreListener, Priority { private static final String[] CAPS = new String[] { "NET_ADMIN" }; private static final String[] VOLUMES = new String[] { "/var/lib/rancher/vm:/vm", "/var/run/rancher:/var/run/rancher" }; private static final String[] DEVICES = new String[] { "/dev/kvm:/dev/kvm", "/dev/net/tun:/dev/net/tun" }; private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9][a-zA-Z0-9_.-]+"); @Inject VolumeDao volumeDao; @Inject StoragePoolDao storagePoolDao; @Inject ServiceDao serviceDao; @Inject JsonMapper jsonMapper; @Override public String[] getProcessNames() { return new String[] { "instance.create" }; } @Override public HandlerResult handle(ProcessState state, ProcessInstance process) { Instance instance = (Instance)state.getResource(); if (!InstanceConstants.KIND_VIRTUAL_MACHINE.equals(instance.getKind())) { return null; } Map<Object, Object> fields = new HashMap<>(); Map<String, Object> labels = DataAccessor.fieldMap(instance, InstanceConstants.FIELD_LABELS); labels.put("io.rancher.scheduler.affinity:host_label_soft", "kvm=true"); labels.put(SystemLabels.LABEL_RANCHER_NETWORK, "true"); labels.put(SystemLabels.LABEL_VM, "true"); long mem = 512L; if (instance.getMemoryMb() == null) { labels.put(SystemLabels.LABEL_VM_MEMORY, "512"); } else { mem = instance.getMemoryMb(); labels.put(SystemLabels.LABEL_VM_MEMORY, instance.getMemoryMb().toString()); } labels.put(SystemLabels.LABEL_VM_USERDATA, instance.getUserdata()); labels.put(SystemLabels.LABEL_VM_VCPU, DataAccessor.fieldString(instance, InstanceConstants.FIELD_VCPU)); fields.put(InstanceConstants.FIELD_LABELS, labels); fields.put(ObjectMetaDataManager.CAPABILITIES_FIELD, Arrays.asList("console")); Map<String, Object> env = DataAccessor.fieldMap(instance, InstanceConstants.FIELD_ENVIRONMENT); env.put("RANCHER_NETWORK", "true"); List<String> dataVolumes = DataAccessor.fieldStringList(instance, InstanceConstants.FIELD_DATA_VOLUMES); List<String> devices = DataAccessor.fieldStringList(instance, DockerInstanceConstants.FIELD_DEVICES); List<String> caps = DataAccessor.fieldStringList(instance, DockerInstanceConstants.FIELD_CAP_ADD); for (String volume : VOLUMES) { addToList(dataVolumes, volume); } for (String device : DEVICES) { addToList(devices, device); } for (String cap : CAPS) { addToList(caps, cap); } String volumeDriver = DataAccessor.fieldString(instance, InstanceConstants.FIELD_VOLUME_DRIVER); Object objectDisks = DataAccessor.field(instance, InstanceConstants.FIELD_DISKS, Object.class); if (objectDisks instanceof List<?>) { String namePrefix = instance.getName(); Long svcIndexId = instance.getServiceIndexId(); String uuidPart = null; if (svcIndexId != null) { Service svc = serviceDao.getServiceByServiceIndexId(svcIndexId); uuidPart = svc.getUuid().substring(0, 7); } else { uuidPart = instance.getUuid().substring(0, 7); } if (StringUtils.isBlank(namePrefix) || !NAME_PATTERN.matcher(namePrefix).matches()) { namePrefix = uuidPart; } else { namePrefix += "-" + uuidPart; } boolean rootFound = false; int index = 0; List<VirtualMachineDisk> disks = jsonMapper.convertCollectionValue(objectDisks, List.class, VirtualMachineDisk.class); for (int i = 0; i < disks.size(); i++) { VirtualMachineDisk disk = disks.get(i); if (disk.isRoot() && rootFound) { continue; } String name = disk.getName(); boolean assignedName = false; if (StringUtils.isBlank(name)) { assignedName = true; name = String.format("%s-%02d", namePrefix, index); } List<? extends Volume> volumes = volumeDao.findSharedOrUnmappedVolumes(instance.getAccountId(), name); if (volumes.size() == 0 && !assignedName) { name = String.format("%s-%s", namePrefix, name); volumes = volumeDao.findSharedOrUnmappedVolumes(instance.getAccountId(), name); } String localDriver = disk.getDriver(); if (localDriver == null) { localDriver = volumeDriver; } List<? extends StoragePool> pools = storagePoolDao.findStoragePoolByDriverName(instance.getAccountId(), localDriver); String blockDevPath = null; if (pools.size() > 0) { blockDevPath = DataAccessor.fieldString(pools.get(0), StoragePoolConstants.FIELD_BLOCK_DEVICE_PATH); } if (volumes.size() == 0) { Map<String, String> opts = disk.getOpts(); if (opts == null) { opts = new HashMap<>(); } opts.put("vm", "true"); if (disk.getSize() != null) { opts.put("size", disk.getSize()); } if (disk.getReadIops() != null) { opts.put("read-iops", disk.getReadIops().toString()); } if (disk.getWriteIops() != null) { opts.put("write-iops", disk.getWriteIops().toString()); } if (StringUtils.isNotEmpty(blockDevPath)) { opts.put("dont-format", "true"); if (disk.isRoot()) { String image = StringUtils.removeStart(DataAccessor.fieldString(instance, InstanceConstants.FIELD_IMAGE_UUID), "docker:"); if (StringUtils.isNotBlank(image)) { opts.put(VolumeConstants.DRIVER_OPT_BASE_IMAGE, image); } opts.remove("size"); } } objectManager.create(Volume.class, VOLUME.NAME, name, VOLUME.ACCOUNT_ID, instance.getAccountId(), VOLUME.ACCESS_MODE, VolumeConstants.ACCESS_MODE_SINGLE_INSTANCE_RW, VolumeConstants.FIELD_VOLUME_DRIVER, localDriver, VolumeConstants.FIELD_VOLUME_DRIVER_OPTS, opts); } else { /* Use name from DB because of case sensitivity */ Volume v = volumes.get(0); name = v.getName(); if (!VolumeConstants.ACCESS_MODE_SINGLE_INSTANCE_RW.equals(v.getAccessMode())) { objectManager.setFields(v, VOLUME.ACCESS_MODE, VolumeConstants.ACCESS_MODE_SINGLE_INSTANCE_RW); } } String diskNameInContainer = String.format("disk%02d", index); String dataVolumeString = String.format("%s:/volumes/%s", name, diskNameInContainer); if (disk.isRoot()) { rootFound = true; dataVolumeString = String.format("%s:/image", name); diskNameInContainer = "root"; } else { index++; } if (StringUtils.isNotEmpty(blockDevPath)) { String deviceOnHost = Paths.get(blockDevPath, name).toString(); String deviceInContainer = String.format("/dev/vm/%s", diskNameInContainer); String deviceParam = String.format("%s:%s", deviceOnHost, deviceInContainer); addToList(devices, deviceParam); } addToList(dataVolumes, dataVolumeString); } } // TODO: I'd really like to remove this List<Object> command = new ArrayList<Object>(DataAccessor.fieldStringList(instance, DockerInstanceConstants.FIELD_COMMAND)); if (!command.contains("-m")) { command.add("-m"); command.add(labels.get(SystemLabels.LABEL_VM_MEMORY)); } if (!command.contains("-smp")) { command.add("-smp"); command.add("cpus=" + labels.get(SystemLabels.LABEL_VM_VCPU)); } fields.put(DockerInstanceConstants.FIELD_COMMAND, command); fields.put(InstanceConstants.FIELD_ENVIRONMENT, env); fields.put(InstanceConstants.FIELD_DATA_VOLUMES, dataVolumes); fields.put(DockerInstanceConstants.FIELD_DEVICES, devices); fields.put(DockerInstanceConstants.FIELD_CAP_ADD, caps); fields.put(INSTANCE.MEMORY_MB, mem); return new HandlerResult(true, fields); } protected void addToList(List<String> list, String value) { if (!list.contains(value)) { list.add(value); } } @Override public int getPriority() { return Priority.PRE; } }