Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions api/src/main/java/com/cloud/storage/VolumeApiService.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@
import java.net.MalformedURLException;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.cloud.exception.ResourceAllocationException;
import com.cloud.offering.DiskOffering;
import com.cloud.user.Account;
import com.cloud.utils.Pair;
import com.cloud.utils.fsm.NoTransitionException;
import com.cloud.vm.VirtualMachine;

import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
Expand Down Expand Up @@ -180,6 +182,22 @@ Volume updateVolume(long volumeId, String path, String state, Long storageId,
*/
boolean doesStoragePoolSupportDiskOfferingTags(StoragePool destPool, String diskOfferingTags);

/**
* Returns the device ids of the Instance which are used by non-volume devices (the CD-ROM drives and the config drive ISO),
* and therefore must not be assigned to its volumes. Otherwise the hypervisor may end up with two devices using the same
* target name (e.g. sde), which is rejected.
*
* @param vm the Instance
* @return the reserved device ids
*/
Set<Long> getReservedDeviceIds(VirtualMachine vm);

/**
* Same as {@link #getReservedDeviceIds(VirtualMachine)}, with the specified usage of config drive instead of the one
* of the Instance. It is useful when the networks of the Instance are not known yet, e.g. when importing it.
*/
Set<Long> getReservedDeviceIds(VirtualMachine vm, boolean configDriveUsed);

boolean validateConditionsToReplaceDiskOfferingOfVolume(Volume volume, DiskOffering newDiskOffering, StoragePool destPool);

Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.cloud.template;

import java.util.List;
import java.util.Set;

import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
Expand All @@ -37,6 +38,7 @@
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VolumeVO;
import com.cloud.utils.Pair;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineProfile;

/**
Expand Down Expand Up @@ -67,6 +69,9 @@ public interface TemplateManager {
static final String VMWARE_TOOLS_ISO = "vmware-tools.iso";
static final String XS_TOOLS_ISO = "xs-tools.iso";

// KVM/libvirt maps deviceSeq=3 to hdc (hda/hdb are taken by the root volume on i440fx/IDE).
int CDROM_PRIMARY_DEVICE_SEQ = 3;

/**
* Prepares a template for vm creation for a certain storage pool.
*
Expand Down Expand Up @@ -148,6 +153,23 @@ public interface TemplateManager {
*/
void prepareIsoForVmProfile(VirtualMachineProfile profile, DeployDestination dest);

/**
* Returns the device ids of the CD-ROM devices of the Instance, starting from {@link #CDROM_PRIMARY_DEVICE_SEQ}.
* They cannot be used by the volumes of the Instance.
* An Instance has one CD-ROM device. If it uses config drive, the config drive ISO is one more CD-ROM, which has the
* device id 4.
*
* @param vm the Instance
* @return the device ids of the CD-ROM devices, including the one of the config drive ISO if the Instance uses config drive
*/
Set<Long> getCdromDeviceIds(VirtualMachine vm);

/**
* Same as {@link #getCdromDeviceIds(VirtualMachine)}, with the specified usage of config drive instead of the one of
* the Instance. It is useful when the networks of the Instance are not known yet, e.g. when importing it.
*/
Set<Long> getCdromDeviceIds(VirtualMachine vm, boolean configDriveUsed);

public static final String MESSAGE_REGISTER_PUBLIC_TEMPLATE_EVENT = "Message.RegisterPublicTemplate.Event";
public static final String MESSAGE_RESET_TEMPLATE_PERMISSION_EVENT = "Message.ResetTemplatePermission.Event";

Expand Down
29 changes: 17 additions & 12 deletions server/src/main/java/com/cloud/hypervisor/KVMGuru.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.cloud.storage.GuestOSHypervisorVO;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeApiService;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.storage.dao.GuestOSHypervisorDao;
Expand All @@ -54,6 +55,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
import org.apache.commons.lang3.math.NumberUtils;
Expand All @@ -71,6 +73,8 @@ public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru {
VolumeDao _volumeDao;
@Inject
HypervisorCapabilitiesDao _hypervisorCapabilitiesDao;
@Inject
VolumeApiService volumeApiService;


@Override
Expand All @@ -86,30 +90,29 @@ protected KVMGuru() {
* Get next free DeviceId for a KVM Guest
*/

protected Long getNextAvailableDeviceId(List<VolumeVO> vmVolumes) {
protected Long getNextAvailableDeviceId(Set<Long> reservedDeviceIds, List<VolumeVO> vmVolumes) {

int maxDataVolumesSupported;
int maxDeviceId;
List<String> devIds = new ArrayList<>();
List<Long> 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));
// the device ids reserved for the CD-ROM and config drive devices cannot be used by volumes
for (long id = 1; devIds.size() < maxDataVolumesSupported; id++) {
if (!reservedDeviceIds.contains(id)) {
devIds.add(id);
}
}
devIds.remove("3");
for (VolumeVO vmVolume : vmVolumes) {
devIds.remove(vmVolume.getDeviceId().toString().trim());
devIds.remove(vmVolume.getDeviceId());
}
if (devIds.isEmpty()) {
throw new RuntimeException("All device Ids are in use.");
}
return Long.parseLong(devIds.iterator().next());
return devIds.get(0);
}


Expand Down Expand Up @@ -361,6 +364,8 @@ public VirtualMachine importVirtualMachineFromBackup(long zoneId, long domainId,
vm.setPowerState(VirtualMachine.PowerState.PowerOff);
_instanceDao.update(vm.getId(), vm);
}
// the networks of the (possibly expunged) VM may not be available, so always keep the config drive device id free
Set<Long> reservedDeviceIds = volumeApiService.getReservedDeviceIds(vm, true);
for (Backup.VolumeInfo VMVolToRestore : vm.getBackupVolumeList()) {
VolumeVO volume = _volumeDao.findByUuidIncludingRemoved(VMVolToRestore.getUuid());
volume.setState(Volume.State.Ready);
Expand All @@ -371,7 +376,7 @@ public VirtualMachine importVirtualMachineFromBackup(long zoneId, long domainId,
} 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));
_volumeDao.attachVolume(volume.getId(), vm.getId(), getNextAvailableDeviceId(reservedDeviceIds, 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());
Expand All @@ -389,7 +394,7 @@ public VirtualMachine importVirtualMachineFromBackup(long zoneId, long domainId,
VolumeVO restoredVolume = _volumeDao.findByUuid(location);
if (restoredVolume != null) {
try {
_volumeDao.attachVolume(restoredVolume.getId(), vm.getId(), getNextAvailableDeviceId(vmVolumes));
_volumeDao.attachVolume(restoredVolume.getId(), vm.getId(), getNextAvailableDeviceId(volumeApiService.getReservedDeviceIds(vm), 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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle
@Inject
private HypervisorGuruManager _hvGuruMgr;

private final static Integer CONFIGDRIVEDISKSEQ = 4;
public static final Integer CONFIGDRIVEDISKSEQ = 4;
Comment thread
weizhouapache marked this conversation as resolved.

private boolean canHandle(TrafficType trafficType) {
return trafficType.equals(TrafficType.Guest);
Expand Down
44 changes: 32 additions & 12 deletions server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -5037,14 +5037,39 @@ protected String getMinimumHypervisorVersionInDatacenter(long datacenterId, Hype
return StringUtils.isBlank(minHwVersion) ? defaultHypervisorVersion : minHwVersion;
}

@Override
public Set<Long> getReservedDeviceIds(VirtualMachine vm) {
return _tmpltMgr.getCdromDeviceIds(vm);
}

@Override
public Set<Long> getReservedDeviceIds(VirtualMachine vm, boolean configDriveUsed) {
return _tmpltMgr.getCdromDeviceIds(vm, configDriveUsed);
}

/**
* Returns the first {@code maxDataVolumes} device ids (starting from 1) which are not reserved.
*/
private List<Long> getAvailableDeviceIds(int maxDataVolumes, Set<Long> reservedDeviceIds) {
List<Long> availableDeviceIds = new ArrayList<>();
for (long id = 1; availableDeviceIds.size() < maxDataVolumes; id++) {
if (!reservedDeviceIds.contains(id)) {
availableDeviceIds.add(id);
}
}
return availableDeviceIds;
}

private Long getDeviceId(UserVmVO vm, Long deviceId) {
// allocate deviceId
int maxDevices = getMaxDataVolumesSupported(vm) + 2; // add 2 to consider devices root volume and cdrom
int maxDeviceId = maxDevices - 1;
Set<Long> reservedDeviceIds = getReservedDeviceIds(vm);
List<Long> availableDeviceIds = getAvailableDeviceIds(getMaxDataVolumesSupported(vm), reservedDeviceIds);
long maxDeviceId = availableDeviceIds.get(availableDeviceIds.size() - 1);
List<VolumeVO> vols = _volsDao.findByInstance(vm.getId());
if (deviceId != null) {
if (deviceId.longValue() < 0 || deviceId.longValue() > maxDeviceId || deviceId.longValue() == 3) {
throw new RuntimeException("deviceId should be 0,1,2,4-" + maxDeviceId);
if (deviceId.longValue() < 0 || deviceId.longValue() > maxDeviceId || reservedDeviceIds.contains(deviceId)) {
throw new RuntimeException(String.format("deviceId should be 0 or between 1 and %d, except the ones reserved for CD-ROM/config drive devices: %s",
maxDeviceId, reservedDeviceIds));
}
for (VolumeVO vol : vols) {
if (vol.getDeviceId().equals(deviceId)) {
Expand All @@ -5053,18 +5078,13 @@ private Long getDeviceId(UserVmVO vm, Long deviceId) {
}
} else {
// allocate deviceId here
List<String> devIds = new ArrayList<String>();
for (int i = 1; i <= maxDeviceId; i++) {
devIds.add(String.valueOf(i));
}
devIds.remove("3");
for (VolumeVO vol : vols) {
devIds.remove(vol.getDeviceId().toString().trim());
availableDeviceIds.remove(vol.getDeviceId());
}
if (devIds.isEmpty()) {
if (availableDeviceIds.isEmpty()) {
throw new RuntimeException(String.format("All device Ids are used by vm %s", vm));
}
deviceId = Long.parseLong(devIds.iterator().next());
deviceId = availableDeviceIds.get(0);
}

return deviceId;
Expand Down
70 changes: 70 additions & 0 deletions server/src/main/java/com/cloud/template/TemplateManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
import java.util.Arrays;
import java.util.Date;
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 java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
Expand Down Expand Up @@ -151,6 +153,10 @@
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.hypervisor.HypervisorGuru;
import com.cloud.hypervisor.HypervisorGuruManager;
import com.cloud.network.Network.Provider;
import com.cloud.network.Network.Service;
import com.cloud.network.NetworkModel;
import com.cloud.network.element.ConfigDriveNetworkElement;
import com.cloud.projects.Project;
import com.cloud.projects.ProjectManager;
import com.cloud.resourcelimit.CheckedReservation;
Expand Down Expand Up @@ -215,6 +221,7 @@
import com.cloud.utils.db.TransactionCallbackNoReturn;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.NicVO;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VMInstanceVO;
Expand All @@ -223,8 +230,10 @@
import com.cloud.vm.VirtualMachineProfile;
import com.cloud.vm.VirtualMachineProfileImpl;
import com.cloud.vm.VmDetailConstants;
import com.cloud.vm.dao.NicDao;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.vm.dao.VMInstanceDetailsDao;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

Expand Down Expand Up @@ -324,6 +333,12 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
private HeuristicRuleHelper heuristicRuleHelper;
@Inject
private UserDataDao userDataDao;
@Inject
private NicDao _nicDao;
@Inject
private NetworkModel _networkModel;
@Inject
private VMInstanceDetailsDao _vmInstanceDetailsDao;

private List<TemplateAdapter> _adapters;

Expand Down Expand Up @@ -730,6 +745,61 @@ public void prepareIsoForVmProfile(VirtualMachineProfile profile, DeployDestinat
}
}

@Override
public Set<Long> getCdromDeviceIds(VirtualMachine vm) {
if (vm.getType() != VirtualMachine.Type.User) {
return getCdromDeviceIds(1);
}
return getCdromDeviceIds(vm, isConfigDriveUsed(vm));
}

@Override
public Set<Long> getCdromDeviceIds(VirtualMachine vm, boolean configDriveUsed) {
return getCdromDeviceIds(getCdromCount(vm, configDriveUsed));
}

private Set<Long> getCdromDeviceIds(int cdromCount) {
Set<Long> deviceIds = new HashSet<>();
for (int i = 0; i < cdromCount; i++) {
deviceIds.add((long) CDROM_PRIMARY_DEVICE_SEQ + i);
}
return deviceIds;
}

private int getCdromCount(VirtualMachine vm, boolean configDriveUsed) {
// an Instance has one CD-ROM device for its ISO
int cdromCount = 1;
// the config drive ISO is one more CD-ROM, which has the device id of ConfigDriveNetworkElement#CONFIGDRIVEDISKSEQ
if (vm.getType() == VirtualMachine.Type.User && configDriveUsed) {
cdromCount += ConfigDriveNetworkElement.CONFIGDRIVEDISKSEQ - CDROM_PRIMARY_DEVICE_SEQ;
}
return cdromCount;
}

/**
* Checks if a config drive ISO is (or will be) attached to the Instance, which is the case when any of its networks
* has ConfigDrive as the provider of the Dhcp, Dns or UserData service. Checking the network providers (instead of
* only the config drive location detail, which is added when the ISO is created) also covers Instances which have
* never been started.
*/
boolean isConfigDriveUsed(VirtualMachine vm) {
if (_vmInstanceDetailsDao.findDetail(vm.getId(), VmDetailConstants.CONFIG_DRIVE_LOCATION) != null) {
return true;
}
List<NicVO> nics = _nicDao.listByVmId(vm.getId());
if (CollectionUtils.isEmpty(nics)) {
return false;
}
for (NicVO nic : nics) {
for (Service service : Arrays.asList(Service.UserData, Service.Dhcp, Service.Dns)) {
if (_networkModel.isProviderSupportServiceInNetwork(nic.getNetworkId(), service, Provider.ConfigDrive)) {
return true;
}
}
}
return false;
}

private void prepareTemplateInOneStoragePool(final VMTemplateVO template, final StoragePoolVO pool) {
logger.info("Schedule to preload Template {} into primary storage {}", template, pool);
if (pool.getPoolType() == Storage.StoragePoolType.DatastoreCluster) {
Expand Down
Loading
Loading