blob: 79d43ba2735540e0e05e668fcbb45b83dab5d64c [file] [log] [blame]
/*
* Licensed 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.kvm.resource.wrapper;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.ScaleVmAnswer;
import com.cloud.agent.api.ScaleVmCommand;
import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.resource.LibvirtVmMemoryDeviceDef;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
import org.libvirt.Connect;
import org.libvirt.Domain;
import org.libvirt.LibvirtException;
@ResourceWrapper(handles = ScaleVmCommand.class)
public class LibvirtScaleVmCommandWrapper extends CommandWrapper<ScaleVmCommand, Answer, LibvirtComputingResource> {
@Override
public Answer execute(ScaleVmCommand command, LibvirtComputingResource libvirtComputingResource) {
VirtualMachineTO vmSpec = command.getVirtualMachine();
String vmName = vmSpec.getName();
Connect conn = null;
long newMemory = ByteScaleUtils.bytesToKibibytes(vmSpec.getMaxRam());
int newVcpus = vmSpec.getCpus();
int newCpuShares = libvirtComputingResource.calculateCpuShares(vmSpec);
String vmDefinition = vmSpec.toString();
String scalingDetails = String.format("%s memory to [%s KiB], CPU cores to [%s] and cpu_shares to [%s]", vmDefinition, newMemory, newVcpus, newCpuShares);
try {
LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
conn = libvirtUtilitiesHelper.getConnectionByVmName(vmName);
Domain dm = conn.domainLookupByName(vmName);
logger.debug(String.format("Scaling %s.", scalingDetails));
scaleMemory(dm, newMemory, vmDefinition);
scaleVcpus(dm, newVcpus, vmDefinition);
updateCpuShares(dm, newCpuShares);
return new ScaleVmAnswer(command, true, String.format("Successfully scaled %s.", scalingDetails));
} catch (LibvirtException | CloudRuntimeException e) {
String message = String.format("Unable to scale %s due to [%s].", scalingDetails, e.getMessage());
logger.error(message, e);
return new ScaleVmAnswer(command, false, message);
} finally {
if (conn != null) {
try {
conn.close();
} catch (LibvirtException ex) {
logger.warn(String.format("Error trying to close libvirt connection [%s]", ex.getMessage()), ex);
}
}
}
}
/**
* Sets the cpu_shares (priority) of the running VM. This is necessary because the priority is only calculated when deploying the VM.
* To prevent the cpu_shares to be manually updated by using the command virsh schedinfo or restarting the VM. This method updates the cpu_shares of a running VM on the fly.
* @param dm domain of the VM.
* @param newCpuShares new priority of the running VM.
* @throws org.libvirt.LibvirtException
**/
protected void updateCpuShares(Domain dm, int newCpuShares) throws LibvirtException {
int oldCpuShares = LibvirtComputingResource.getCpuShares(dm);
if (oldCpuShares < newCpuShares) {
LibvirtComputingResource.setCpuShares(dm, newCpuShares);
logger.info(String.format("Successfully increased cpu_shares of VM [%s] from [%s] to [%s].", dm.getName(), oldCpuShares, newCpuShares));
}
}
protected void scaleVcpus(Domain dm, int newVcpus, String vmDefinition) throws LibvirtException {
long runningVcpus = LibvirtComputingResource.countDomainRunningVcpus(dm);
if (runningVcpus < newVcpus) {
dm.setVcpus(newVcpus);
return;
}
logger.info(String.format("Not scaling the CPU cores. To scale the CPU cores of the %s, the new CPU count [%s] must be higher than the current CPU count [%s].",
vmDefinition, newVcpus, runningVcpus));
}
protected void scaleMemory(Domain dm, long newMemory, String vmDefinition) throws LibvirtException, CloudRuntimeException {
long currentMemory = LibvirtComputingResource.getDomainMemory(dm);
long memoryToAttach = newMemory - currentMemory;
if (memoryToAttach <= 0) {
logger.info(String.format("Not scaling the memory. To scale the memory of the %s, the new memory [%s] must be higher than the current memory [%s]. The current "
+ "difference is [%s].", vmDefinition, newMemory, currentMemory, memoryToAttach));
return;
}
if (!dm.getXMLDesc(0).contains("<maxMemory slots='16' unit='KiB'>")) {
throw new CloudRuntimeException(String.format("The %s is not prepared for dynamic scaling. To be prepared, the VM must be deployed with a dynamic service offering,"
+ " VM dynamic scale enabled and global setting \"enable.dynamic.scale.vm\" as \"true\". If you changed one of these settings after deploying the VM,"
+ " consider stopping and starting it again to prepared it to dynamic scaling.", vmDefinition));
}
String memoryDevice = new LibvirtVmMemoryDeviceDef(memoryToAttach).toString();
logger.debug(String.format("Attaching memory device [%s] to %s.", memoryDevice, vmDefinition));
dm.attachDevice(memoryDevice);
}
}