blob: 6c1c3424b1b9064b91ec2a67b3b3a90e0aefc186 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package com.cloud.hypervisor;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.NicTO;
import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.capacity.CapacityManager;
import com.cloud.event.EventTypes;
import com.cloud.event.UsageEventUtils;
import com.cloud.host.HostVO;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
import com.cloud.hypervisor.kvm.dpdk.DpdkHelper;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.GuestOSHypervisorVO;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.storage.dao.GuestOSHypervisorDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineProfile;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.storage.command.CopyCommand;
import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.collections.MapUtils;
import javax.inject.Inject;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru {
@Inject
GuestOSDao _guestOsDao;
@Inject
GuestOSHypervisorDao _guestOsHypervisorDao;
@Inject
DpdkHelper dpdkHelper;
@Inject
VMInstanceDao _instanceDao;
@Inject
VolumeDao _volumeDao;
@Inject
HypervisorCapabilitiesDao _hypervisorCapabilitiesDao;
@Override
public HypervisorType getHypervisorType() {
return HypervisorType.KVM;
}
protected KVMGuru() {
super();
}
/**
* Get next free DeviceId for a KVM Guest
*/
protected Long getNextAvailableDeviceId(List<VolumeVO> vmVolumes) {
int maxDataVolumesSupported;
int maxDeviceId;
List<String> devIds = new ArrayList<>();
try {
maxDataVolumesSupported = _hypervisorCapabilitiesDao.getMaxDataVolumesLimit(HypervisorType.KVM,"default");
int maxDevices = maxDataVolumesSupported + 2; // add 2 to consider devices root volume and cdrom
maxDeviceId = maxDevices - 1;
} catch (Exception e) {
throw new RuntimeException("Cannot find maximum number of disk devices that can be attached to the KVM Hypervisor");
}
for (int i = 1; i <= maxDeviceId; i++) {
devIds.add(String.valueOf(i));
}
devIds.remove("3");
for (VolumeVO vmVolume : vmVolumes) {
devIds.remove(vmVolume.getDeviceId().toString().trim());
}
if (devIds.isEmpty()) {
throw new RuntimeException("All device Ids are in use.");
}
return Long.parseLong(devIds.iterator().next());
}
/**
* Retrieve host max CPU speed
*/
protected double getHostCPUSpeed(HostVO host) {
return host.getSpeed();
}
protected double getVmSpeed(VirtualMachineTO to) {
return to.getMaxSpeed() != null ? to.getMaxSpeed() : to.getSpeed();
}
/**
* Set VM CPU quota percentage with respect to host CPU on 'to' if CPU limit option is set
* @param to vm to
* @param vmProfile vm profile
*/
protected void setVmQuotaPercentage(VirtualMachineTO to, VirtualMachineProfile vmProfile) {
if (!to.isLimitCpuUse()) {
return;
}
VirtualMachine vm = vmProfile.getVirtualMachine();
HostVO host = hostDao.findById(vm.getHostId());
if (host == null) {
logger.warn("Host is not available. Skipping setting CPU quota percentage for VM: [{}].", vm);
return;
}
logger.debug("Limiting CPU usage for VM: [{}] on host: [{}].", vm, host);
double maxSpeed = getVmSpeed(to);
double hostMaxSpeed = getHostCPUSpeed(host);
Double cpuQuotaPercentage = getCpuQuotaPercentage(maxSpeed, hostMaxSpeed);
if (cpuQuotaPercentage != null) {
to.setCpuQuotaPercentage(cpuQuotaPercentage);
}
}
/**
* Calculates the VM quota percentage based on the VM and host CPU speeds.
* @param vmSpeeed Speed of the VM.
* @param hostSpeed Speed of the host.
* @return The VM quota percentage.
*/
public Double getCpuQuotaPercentage(double vmSpeeed, double hostSpeed) {
logger.debug("Calculating CPU quota percentage for VM with speed [{}] on host with speed [{}].", vmSpeeed, hostSpeed);
try {
BigDecimal percent = new BigDecimal(vmSpeeed / hostSpeed);
percent = percent.setScale(2, RoundingMode.HALF_DOWN);
if (percent.compareTo(new BigDecimal(1)) > 0) {
logger.debug("VM CPU speed exceeded host CPU speed and, therefore, limiting VM CPU quota to the host maximum.");
percent = new BigDecimal(1);
}
double quotaPercentage = percent.doubleValue();
logger.info("Calculated CPU quota percentage for VM with speed [{}] on host with speed [{}] is [{}].", vmSpeeed, hostSpeed, quotaPercentage);
return quotaPercentage;
} catch (NumberFormatException e) {
logger.info("Could not calculate CPU quota percentage for VM with speed [{}] on host with speed [{}]. Therefore, CPU limitation will not be set for the domain.", vmSpeeed, hostSpeed);
return null;
}
}
@Override
public VirtualMachineTO implement(VirtualMachineProfile vm) {
VirtualMachineTO to = toVirtualMachineTO(vm);
setVmQuotaPercentage(to, vm);
enableDpdkIfNeeded(vm, to);
VirtualMachine virtualMachine = vm.getVirtualMachine();
Long hostId = virtualMachine.getHostId();
HostVO host = hostId == null ? null : hostDao.findById(hostId);
// Determine the VM's OS description
configureVmOsDescription(virtualMachine, to, host);
configureVmMemoryAndCpuCores(to, host, virtualMachine, vm);
to.setMetadata(makeVirtualMachineMetadata(vm));
return to;
}
protected void configureVmOsDescription(VirtualMachine virtualMachine, VirtualMachineTO virtualMachineTo, HostVO hostVo) {
GuestOSVO guestOS = _guestOsDao.findByIdIncludingRemoved(virtualMachine.getGuestOSId());
String guestOsDisplayName = guestOS.getDisplayName();
virtualMachineTo.setOs(guestOsDisplayName);
GuestOSHypervisorVO guestOsMapping = null;
if (hostVo != null) {
guestOsMapping = _guestOsHypervisorDao.findByOsIdAndHypervisor(guestOS.getId(), getHypervisorType().toString(), hostVo.getHypervisorVersion());
}
if (guestOsMapping == null || hostVo == null) {
virtualMachineTo.setPlatformEmulator(guestOsDisplayName == null ? "Other" : guestOsDisplayName);
} else {
virtualMachineTo.setPlatformEmulator(guestOsMapping.getGuestOsName());
}
}
protected void enableDpdkIfNeeded(VirtualMachineProfile virtualMachineProfile, VirtualMachineTO virtualMachineTo) {
if (dpdkHelper.isDpdkvHostUserModeSettingOnServiceOffering(virtualMachineProfile)) {
dpdkHelper.setDpdkVhostUserMode(virtualMachineTo, virtualMachineProfile);
}
if (virtualMachineTo.getType() == VirtualMachine.Type.User && MapUtils.isNotEmpty(virtualMachineTo.getExtraConfig()) &&
virtualMachineTo.getExtraConfig().containsKey(DpdkHelper.DPDK_NUMA) && virtualMachineTo.getExtraConfig().containsKey(DpdkHelper.DPDK_HUGE_PAGES)) {
for (final NicTO nic : virtualMachineTo.getNics()) {
nic.setDpdkEnabled(true);
}
}
}
protected void configureVmMemoryAndCpuCores(VirtualMachineTO virtualMachineTo, HostVO hostVo, VirtualMachine virtualMachine, VirtualMachineProfile virtualMachineProfile) {
String vmDescription = virtualMachineTo.toString();
Pair<Long, Integer> max = getHostMaxMemoryAndCpuCores(hostVo, virtualMachine, vmDescription);
Long maxHostMemory = max.first();
Integer maxHostCpuCores = max.second();
long minMemory = virtualMachineTo.getMinRam();
Long maxMemory = virtualMachineTo.getMaxRam();
long requestedMemory = maxMemory;
int minCpuCores = virtualMachineTo.getCpus();
int maxCpuCores = minCpuCores;
if (isVmDynamicScalable(virtualMachineTo, virtualMachine)) {
ServiceOfferingVO serviceOfferingVO = serviceOfferingDao.findById(virtualMachineProfile.getId(), virtualMachineProfile.getServiceOfferingId());
serviceOfferingDao.loadDetails(serviceOfferingVO);
Long clusterId = hostVo != null ? hostVo.getClusterId() : null;
maxMemory = getVmMaxMemory(serviceOfferingVO, vmDescription, maxHostMemory, clusterId);
maxCpuCores = getVmMaxCpuCores(serviceOfferingVO, vmDescription, maxHostCpuCores, clusterId);
}
virtualMachineTo.setRam(minMemory, maxMemory, requestedMemory);
virtualMachineTo.setCpus(minCpuCores);
virtualMachineTo.setVcpuMaxLimit(maxCpuCores);
}
protected boolean isVmDynamicScalable(VirtualMachineTO virtualMachineTo, VirtualMachine virtualMachine) {
return virtualMachineTo.isEnableDynamicallyScaleVm() && UserVmManager.EnableDynamicallyScaleVm.valueIn(virtualMachine.getDataCenterId());
}
protected Pair<Long, Integer> getHostMaxMemoryAndCpuCores(HostVO host, VirtualMachine virtualMachine, String vmDescription){
Long maxHostMemory = Long.MAX_VALUE;
Integer maxHostCpuCore = Integer.MAX_VALUE;
if (host != null) {
return new Pair<>(host.getTotalMemory(), host.getCpus());
}
Long lastHostId = virtualMachine.getLastHostId();
HostVO lastHost = lastHostId == null ? null : hostDao.findById(lastHostId);
logger.info("{} is not running; therefore, we use the last host [{}] with id {} that the " +
"VM was running on to derive the unconstrained service offering max CPU " +
"and memory.", vmDescription, lastHost, lastHostId);
if (lastHost != null) {
maxHostMemory = lastHost.getTotalMemory();
maxHostCpuCore = lastHost.getCpus();
logger.debug(String.format("Retrieved memory and cpu max values {\"memory\": %s, \"cpu\": %s} from %s last %s.", maxHostMemory, maxHostCpuCore, vmDescription, lastHost));
} else {
logger.warn(String.format("%s host [%s] and last host [%s] are null. Using 'Long.MAX_VALUE' [%s] and 'Integer.MAX_VALUE' [%s] as max memory and cpu cores.", vmDescription, virtualMachine.getHostId(), lastHostId, maxHostMemory, maxHostCpuCore));
}
return new Pair<>(maxHostMemory, maxHostCpuCore);
}
protected Long getVmMaxMemory(ServiceOfferingVO serviceOfferingVO, String vmDescription, Long maxHostMemory, Long clusterId) {
Long maxMemory;
ConfigKey<Integer> maxMemoryConfig = CapacityManager.KvmMemoryDynamicScalingCapacity;
Integer maxMemoryConfigValue = maxMemoryConfig.valueIn(clusterId);
logger.info("[{}] is a dynamically scalable service offering. Using config [{}] value [{}] in cluster [ID: {}] as max [{}] memory.",
serviceOfferingVO.toString(), maxMemoryConfig.key(), maxMemoryConfigValue, clusterId, vmDescription);
if (maxMemoryConfigValue > 0) {
maxMemory = ByteScaleUtils.mebibytesToBytes(maxMemoryConfigValue);
} else {
logger.info("Config [{}] in cluster [ID: {}] has value less or equal '0'. Using [{}] host or last host max memory [{}] as VM max memory in the hypervisor.",
maxMemoryConfig.key(), clusterId, vmDescription, maxHostMemory);
maxMemory = maxHostMemory;
}
return maxMemory;
}
protected Integer getVmMaxCpuCores(ServiceOfferingVO serviceOfferingVO, String vmDescription, Integer maxHostCpuCores, Long clusterId) {
Integer maxCpuCores;
ConfigKey<Integer> maxCpuCoresConfig = CapacityManager.KvmCpuDynamicScalingCapacity;
Integer maxCpuCoresConfigValue = maxCpuCoresConfig.valueIn(clusterId);
logger.info("[{}] is a dynamically scalable service offering. Using config [{}] value [{}] in cluster [ID: {}] as max [{}] CPU cores.",
serviceOfferingVO.toString(), maxCpuCoresConfig.key(), maxCpuCoresConfigValue, clusterId, vmDescription);
if (maxCpuCoresConfigValue > 0) {
maxCpuCores = maxCpuCoresConfigValue;
} else {
logger.info("Config [{}] in cluster [ID: {}] has value less or equal '0'. Using [{}] host or last host max CPU cores [{}] as VM CPU cores in the hypervisor.",
maxCpuCoresConfig.key(), clusterId, vmDescription, maxHostCpuCores);
maxCpuCores = maxHostCpuCores;
}
return maxCpuCores;
}
@Override
public Pair<Boolean, Long> getCommandHostDelegation(long hostId, Command cmd) {
if (cmd instanceof StorageSubSystemCommand) {
StorageSubSystemCommand c = (StorageSubSystemCommand)cmd;
c.setExecuteInSequence(false);
}
if (cmd instanceof CopyCommand) {
CopyCommand c = (CopyCommand) cmd;
boolean inSeq = true;
if (c.getSrcTO().getObjectType() == DataObjectType.SNAPSHOT ||
c.getDestTO().getObjectType() == DataObjectType.SNAPSHOT) {
inSeq = false;
} else if (c.getDestTO().getDataStore().getRole() == DataStoreRole.Image ||
c.getDestTO().getDataStore().getRole() == DataStoreRole.ImageCache) {
inSeq = false;
}
c.setExecuteInSequence(inSeq);
if (c.getSrcTO().getHypervisorType() == HypervisorType.KVM) {
return new Pair<>(true, hostId);
}
}
return new Pair<>(false, hostId);
}
@Override
public boolean trackVmHostChange() {
return false;
}
@Override
public Map<String, String> getClusterSettings(long vmId) {
return null;
}
@Override
public VirtualMachine importVirtualMachineFromBackup(long zoneId, long domainId, long accountId, long userId, String vmInternalName, Backup backup) {
logger.debug(String.format("Trying to import VM [vmInternalName: %s] from Backup [%s].", vmInternalName,
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(backup, "id", "uuid", "vmId", "externalId", "backupType")));
VMInstanceVO vm = _instanceDao.findVMByInstanceNameIncludingRemoved(vmInternalName);
if (vm == null) {
throw new CloudRuntimeException("Cannot find VM: " + vmInternalName);
}
try {
if (vm.getRemoved() == null) {
vm.setState(VirtualMachine.State.Stopped);
vm.setPowerState(VirtualMachine.PowerState.PowerOff);
_instanceDao.update(vm.getId(), vm);
}
for (Backup.VolumeInfo VMVolToRestore : vm.getBackupVolumeList()) {
VolumeVO volume = _volumeDao.findByUuidIncludingRemoved(VMVolToRestore.getUuid());
volume.setState(Volume.State.Ready);
_volumeDao.update(volume.getId(), volume);
if (VMVolToRestore.getType() == Volume.Type.ROOT) {
_volumeDao.update(volume.getId(), volume);
_volumeDao.attachVolume(volume.getId(), vm.getId(), 0L);
} else if (VMVolToRestore.getType() == Volume.Type.DATADISK) {
List<VolumeVO> vmVolumes = _volumeDao.findByInstance(vm.getId());
_volumeDao.update(volume.getId(), volume);
_volumeDao.attachVolume(volume.getId(), vm.getId(), getNextAvailableDeviceId(vmVolumes));
}
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_ATTACH, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(),
volume.getDiskOfferingId(), volume.getTemplateId(), volume.getSize(), Volume.class.getName(), volume.getUuid(), vm.getId(), volume.isDisplay());
}
} catch (Exception e) {
throw new RuntimeException("Could not restore VM " + vm.getName() + " due to : " + e.getMessage());
}
return vm;
}
@Override public boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location, Backup.VolumeInfo volumeInfo, VirtualMachine vm, long poolId, Backup backup) {
VMInstanceVO targetVM = _instanceDao.findVMByInstanceNameIncludingRemoved(vm.getName());
List<VolumeVO> vmVolumes = _volumeDao.findByInstance(targetVM.getId());
VolumeVO restoredVolume = _volumeDao.findByUuid(location);
if (restoredVolume != null) {
try {
_volumeDao.attachVolume(restoredVolume.getId(), vm.getId(), getNextAvailableDeviceId(vmVolumes));
restoredVolume.setState(Volume.State.Ready);
_volumeDao.update(restoredVolume.getId(), restoredVolume);
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_ATTACH, restoredVolume.getAccountId(), restoredVolume.getDataCenterId(), restoredVolume.getId(), restoredVolume.getName(),
restoredVolume.getDiskOfferingId(), restoredVolume.getTemplateId(), restoredVolume.getSize(), Volume.class.getName(), restoredVolume.getUuid(), vm.getId(), restoredVolume.isDisplay());
return true;
} catch (Exception e) {
restoredVolume.setDisplay(false);
restoredVolume.setDisplayVolume(false);
restoredVolume.setState(Volume.State.Destroy);
_volumeDao.update(restoredVolume.getId(), restoredVolume);
throw new RuntimeException("Unable to attach volume " + restoredVolume.getName() + " to VM" + vm.getName() + " due to : " + e.getMessage());
}
}
return false;
}
}