Skip to content

Commit 4eab500

Browse files
committed
server: Reserve CD-ROM and config drive device ids for volumes
Data volumes could be assigned the same device id as the CD-ROM or the config drive ISO, which end up with the same libvirt target name (e.g. sde) when they are on the same bus namespace (SATA with UEFI/Q35). Add VolumeApiService.getReservedDeviceIds(), which returns the device ids of the CD-ROM devices and of the config drive ISO when the Instance uses config drive. The config drive is detected from the network providers (Dhcp, Dns, UserData), so Instances which have never been started are covered as well as the ones with the config drive location detail. The reserved ids are skipped when allocating device ids in: - attach volume - restore of volumes and Instances from backup - import of Instances (unmanaged Instances and VMware to KVM) The max number of data volumes is preserved when device ids are reserved, and the error message of an invalid deviceId lists the reserved ids.
1 parent 9709308 commit 4eab500

8 files changed

Lines changed: 347 additions & 39 deletions

File tree

‎api/src/main/java/com/cloud/storage/VolumeApiService.java‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
import java.net.MalformedURLException;
2222
import java.util.List;
2323
import java.util.Map;
24+
import java.util.Set;
2425

2526
import com.cloud.exception.ResourceAllocationException;
2627
import com.cloud.offering.DiskOffering;
2728
import com.cloud.user.Account;
2829
import com.cloud.utils.Pair;
2930
import com.cloud.utils.fsm.NoTransitionException;
31+
import com.cloud.vm.VirtualMachine;
3032

3133
import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
3234
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
@@ -180,6 +182,16 @@ Volume updateVolume(long volumeId, String path, String state, Long storageId,
180182
*/
181183
boolean doesStoragePoolSupportDiskOfferingTags(StoragePool destPool, String diskOfferingTags);
182184

185+
/**
186+
* Returns the device ids of the Instance which are used by non-volume devices (the CD-ROM drives and the config drive ISO),
187+
* and therefore must not be assigned to its volumes. Otherwise the hypervisor may end up with two devices using the same
188+
* target name (e.g. sde), which is rejected.
189+
*
190+
* @param vm the Instance
191+
* @return the reserved device ids
192+
*/
193+
Set<Long> getReservedDeviceIds(VirtualMachine vm);
194+
183195
boolean validateConditionsToReplaceDiskOfferingOfVolume(Volume volume, DiskOffering newDiskOffering, StoragePool destPool);
184196

185197
Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge);

‎engine/components-api/src/main/java/com/cloud/template/TemplateManager.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ public interface TemplateManager {
6767
static final String VMWARE_TOOLS_ISO = "vmware-tools.iso";
6868
static final String XS_TOOLS_ISO = "xs-tools.iso";
6969

70+
// KVM/libvirt maps deviceSeq=3 to hdc (hda/hdb are taken by the root volume on i440fx/IDE).
71+
int CDROM_PRIMARY_DEVICE_SEQ = 3;
72+
7073
/**
7174
* Prepares a template for vm creation for a certain storage pool.
7275
*

‎server/src/main/java/com/cloud/hypervisor/KVMGuru.java‎

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.cloud.storage.GuestOSHypervisorVO;
3434
import com.cloud.storage.GuestOSVO;
3535
import com.cloud.storage.Volume;
36+
import com.cloud.storage.VolumeApiService;
3637
import com.cloud.storage.VolumeVO;
3738
import com.cloud.storage.dao.GuestOSDao;
3839
import com.cloud.storage.dao.GuestOSHypervisorDao;
@@ -43,9 +44,7 @@
4344
import com.cloud.vm.VMInstanceVO;
4445
import com.cloud.vm.VirtualMachine;
4546
import com.cloud.vm.VirtualMachineProfile;
46-
import com.cloud.vm.VmDetailConstants;
4747
import com.cloud.vm.dao.VMInstanceDao;
48-
import com.cloud.vm.dao.VMInstanceDetailsDao;
4948
import org.apache.cloudstack.backup.Backup;
5049
import org.apache.cloudstack.storage.command.CopyCommand;
5150
import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
@@ -55,8 +54,10 @@
5554
import java.math.BigDecimal;
5655
import java.math.RoundingMode;
5756
import java.util.ArrayList;
57+
import java.util.HashSet;
5858
import java.util.List;
5959
import java.util.Map;
60+
import java.util.Set;
6061
import org.apache.cloudstack.api.ApiConstants;
6162
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
6263
import org.apache.commons.lang3.math.NumberUtils;
@@ -75,7 +76,7 @@ public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru {
7576
@Inject
7677
HypervisorCapabilitiesDao _hypervisorCapabilitiesDao;
7778
@Inject
78-
VMInstanceDetailsDao _vmInstanceDetailsDao;
79+
VolumeApiService volumeApiService;
7980

8081

8182
@Override
@@ -91,33 +92,29 @@ protected KVMGuru() {
9192
* Get next free DeviceId for a KVM Guest
9293
*/
9394

94-
protected Long getNextAvailableDeviceId(long vmId, List<VolumeVO> vmVolumes) {
95+
protected Long getNextAvailableDeviceId(Set<Long> reservedDeviceIds, List<VolumeVO> vmVolumes) {
9596

9697
int maxDataVolumesSupported;
97-
int maxDeviceId;
98-
List<String> devIds = new ArrayList<>();
98+
List<Long> devIds = new ArrayList<>();
9999

100100
try {
101101
maxDataVolumesSupported = _hypervisorCapabilitiesDao.getMaxDataVolumesLimit(HypervisorType.KVM,"default");
102-
int maxDevices = maxDataVolumesSupported + 2; // add 2 to consider devices root volume and cdrom
103-
maxDeviceId = maxDevices - 1;
104102
} catch (Exception e) {
105103
throw new RuntimeException("Cannot find maximum number of disk devices that can be attached to the KVM Hypervisor");
106104
}
107-
for (int i = 1; i <= maxDeviceId; i++) {
108-
devIds.add(String.valueOf(i));
109-
}
110-
devIds.remove("3");
111-
if (_vmInstanceDetailsDao.findDetail(vmId, VmDetailConstants.CONFIG_DRIVE_LOCATION) != null) {
112-
devIds.remove(ConfigDriveNetworkElement.CONFIGDRIVEDISKSEQ.toString());
105+
// the device ids reserved for the CD-ROM and config drive devices cannot be used by volumes
106+
for (long id = 1; devIds.size() < maxDataVolumesSupported; id++) {
107+
if (!reservedDeviceIds.contains(id)) {
108+
devIds.add(id);
109+
}
113110
}
114111
for (VolumeVO vmVolume : vmVolumes) {
115-
devIds.remove(vmVolume.getDeviceId().toString().trim());
112+
devIds.remove(vmVolume.getDeviceId());
116113
}
117114
if (devIds.isEmpty()) {
118115
throw new RuntimeException("All device Ids are in use.");
119116
}
120-
return Long.parseLong(devIds.iterator().next());
117+
return devIds.get(0);
121118
}
122119

123120

@@ -369,6 +366,9 @@ public VirtualMachine importVirtualMachineFromBackup(long zoneId, long domainId,
369366
vm.setPowerState(VirtualMachine.PowerState.PowerOff);
370367
_instanceDao.update(vm.getId(), vm);
371368
}
369+
// the networks of the (possibly expunged) VM may not be available, so always keep the config drive device id free
370+
Set<Long> reservedDeviceIds = new HashSet<>(volumeApiService.getReservedDeviceIds(vm));
371+
reservedDeviceIds.add(ConfigDriveNetworkElement.CONFIGDRIVEDISKSEQ.longValue());
372372
for (Backup.VolumeInfo VMVolToRestore : vm.getBackupVolumeList()) {
373373
VolumeVO volume = _volumeDao.findByUuidIncludingRemoved(VMVolToRestore.getUuid());
374374
volume.setState(Volume.State.Ready);
@@ -379,7 +379,7 @@ public VirtualMachine importVirtualMachineFromBackup(long zoneId, long domainId,
379379
} else if (VMVolToRestore.getType() == Volume.Type.DATADISK) {
380380
List<VolumeVO> vmVolumes = _volumeDao.findByInstance(vm.getId());
381381
_volumeDao.update(volume.getId(), volume);
382-
_volumeDao.attachVolume(volume.getId(), vm.getId(), getNextAvailableDeviceId(vm.getId(), vmVolumes));
382+
_volumeDao.attachVolume(volume.getId(), vm.getId(), getNextAvailableDeviceId(reservedDeviceIds, vmVolumes));
383383
}
384384
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_ATTACH, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(),
385385
volume.getDiskOfferingId(), volume.getTemplateId(), volume.getSize(), Volume.class.getName(), volume.getUuid(), vm.getId(), volume.isDisplay());
@@ -397,7 +397,7 @@ public VirtualMachine importVirtualMachineFromBackup(long zoneId, long domainId,
397397
VolumeVO restoredVolume = _volumeDao.findByUuid(location);
398398
if (restoredVolume != null) {
399399
try {
400-
_volumeDao.attachVolume(restoredVolume.getId(), vm.getId(), getNextAvailableDeviceId(vm.getId(), vmVolumes));
400+
_volumeDao.attachVolume(restoredVolume.getId(), vm.getId(), getNextAvailableDeviceId(volumeApiService.getReservedDeviceIds(vm), vmVolumes));
401401
restoredVolume.setState(Volume.State.Ready);
402402
_volumeDao.update(restoredVolume.getId(), restoredVolume);
403403
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_ATTACH, restoredVolume.getAccountId(), restoredVolume.getDataCenterId(), restoredVolume.getId(), restoredVolume.getName(),

‎server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java‎

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@
160160
import com.cloud.hypervisor.Hypervisor.HypervisorType;
161161
import com.cloud.hypervisor.HypervisorCapabilitiesVO;
162162
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
163+
import com.cloud.network.Network.Provider;
164+
import com.cloud.network.Network.Service;
165+
import com.cloud.network.NetworkModel;
163166
import com.cloud.network.element.ConfigDriveNetworkElement;
164167
import com.cloud.offering.DiskOffering;
165168
import com.cloud.org.Cluster;
@@ -220,6 +223,7 @@
220223
import com.cloud.utils.fsm.NoTransitionException;
221224
import com.cloud.utils.fsm.StateMachine2;
222225
import com.cloud.vm.DiskProfile;
226+
import com.cloud.vm.NicVO;
223227
import com.cloud.vm.VMInstanceDetailVO;
224228
import com.cloud.vm.UserVmManager;
225229
import com.cloud.vm.UserVmService;
@@ -241,6 +245,7 @@
241245
import com.cloud.vm.VmWorkResizeVolume;
242246
import com.cloud.vm.VmWorkSerializer;
243247
import com.cloud.vm.VmWorkTakeVolumeSnapshot;
248+
import com.cloud.vm.dao.NicDao;
244249
import com.cloud.vm.dao.UserVmDao;
245250
import com.cloud.vm.dao.VMInstanceDetailsDao;
246251
import com.cloud.vm.dao.VMInstanceDao;
@@ -294,6 +299,10 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
294299
@Inject
295300
private VMInstanceDetailsDao vmInstanceDetailsDao;
296301
@Inject
302+
private NicDao _nicDao;
303+
@Inject
304+
private NetworkModel _networkModel;
305+
@Inject
297306
private UserVmService _userVmService;
298307
@Inject
299308
private VolumeDataStoreDao _volumeStoreDao;
@@ -5038,16 +5047,69 @@ protected String getMinimumHypervisorVersionInDatacenter(long datacenterId, Hype
50385047
return StringUtils.isBlank(minHwVersion) ? defaultHypervisorVersion : minHwVersion;
50395048
}
50405049

5050+
/**
5051+
* The number of CD-ROM devices of the Instance. Each CD-ROM device occupies a device id, starting from
5052+
* {@link TemplateManager#CDROM_PRIMARY_DEVICE_SEQ}.
5053+
*/
5054+
protected int getVmCdromCount(VirtualMachine vm) {
5055+
return 1;
5056+
}
5057+
5058+
/**
5059+
* Checks if a config drive ISO is (or will be) attached to the Instance, which is the case when any of its networks
5060+
* has ConfigDrive as the provider of the Dhcp, Dns or UserData service. Checking the network providers (instead of
5061+
* only the config drive location detail, which is added when the ISO is created) also covers Instances which have
5062+
* never been started.
5063+
*/
5064+
protected boolean isConfigDriveUsed(VirtualMachine vm) {
5065+
if (vmInstanceDetailsDao.findDetail(vm.getId(), VmDetailConstants.CONFIG_DRIVE_LOCATION) != null) {
5066+
return true;
5067+
}
5068+
for (NicVO nic : _nicDao.listByVmId(vm.getId())) {
5069+
for (Service service : Arrays.asList(Service.UserData, Service.Dhcp, Service.Dns)) {
5070+
if (_networkModel.isProviderSupportServiceInNetwork(nic.getNetworkId(), service, Provider.ConfigDrive)) {
5071+
return true;
5072+
}
5073+
}
5074+
}
5075+
return false;
5076+
}
5077+
5078+
@Override
5079+
public Set<Long> getReservedDeviceIds(VirtualMachine vm) {
5080+
Set<Long> reservedDeviceIds = new HashSet<>();
5081+
for (int i = 0; i < getVmCdromCount(vm); i++) {
5082+
reservedDeviceIds.add((long) TemplateManager.CDROM_PRIMARY_DEVICE_SEQ + i);
5083+
}
5084+
if (isConfigDriveUsed(vm)) {
5085+
reservedDeviceIds.add(ConfigDriveNetworkElement.CONFIGDRIVEDISKSEQ.longValue());
5086+
}
5087+
return reservedDeviceIds;
5088+
}
5089+
5090+
/**
5091+
* Returns the first {@code maxDataVolumes} device ids (starting from 1) which are not reserved.
5092+
*/
5093+
private List<Long> getAvailableDeviceIds(int maxDataVolumes, Set<Long> reservedDeviceIds) {
5094+
List<Long> availableDeviceIds = new ArrayList<>();
5095+
for (long id = 1; availableDeviceIds.size() < maxDataVolumes; id++) {
5096+
if (!reservedDeviceIds.contains(id)) {
5097+
availableDeviceIds.add(id);
5098+
}
5099+
}
5100+
return availableDeviceIds;
5101+
}
5102+
50415103
private Long getDeviceId(UserVmVO vm, Long deviceId) {
50425104
// allocate deviceId
5043-
int maxDevices = getMaxDataVolumesSupported(vm) + 2; // add 2 to consider devices root volume and cdrom
5044-
int maxDeviceId = maxDevices - 1;
5105+
Set<Long> reservedDeviceIds = getReservedDeviceIds(vm);
5106+
List<Long> availableDeviceIds = getAvailableDeviceIds(getMaxDataVolumesSupported(vm), reservedDeviceIds);
5107+
long maxDeviceId = availableDeviceIds.get(availableDeviceIds.size() - 1);
50455108
List<VolumeVO> vols = _volsDao.findByInstance(vm.getId());
5046-
boolean vmHasConfigDrive = vmInstanceDetailsDao.findDetail(vm.getId(), VmDetailConstants.CONFIG_DRIVE_LOCATION) != null;
50475109
if (deviceId != null) {
5048-
if (deviceId.longValue() < 0 || deviceId.longValue() > maxDeviceId || deviceId.longValue() == 3
5049-
|| (vmHasConfigDrive && deviceId.longValue() == ConfigDriveNetworkElement.CONFIGDRIVEDISKSEQ)) {
5050-
throw new RuntimeException("deviceId should be 0,1,2,4-" + maxDeviceId);
5110+
if (deviceId.longValue() < 0 || deviceId.longValue() > maxDeviceId || reservedDeviceIds.contains(deviceId)) {
5111+
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",
5112+
maxDeviceId, reservedDeviceIds));
50515113
}
50525114
for (VolumeVO vol : vols) {
50535115
if (vol.getDeviceId().equals(deviceId)) {
@@ -5056,21 +5118,13 @@ private Long getDeviceId(UserVmVO vm, Long deviceId) {
50565118
}
50575119
} else {
50585120
// allocate deviceId here
5059-
List<String> devIds = new ArrayList<String>();
5060-
for (int i = 1; i <= maxDeviceId; i++) {
5061-
devIds.add(String.valueOf(i));
5062-
}
5063-
devIds.remove("3");
5064-
if (vmHasConfigDrive) {
5065-
devIds.remove(ConfigDriveNetworkElement.CONFIGDRIVEDISKSEQ.toString());
5066-
}
50675121
for (VolumeVO vol : vols) {
5068-
devIds.remove(vol.getDeviceId().toString().trim());
5122+
availableDeviceIds.remove(vol.getDeviceId());
50695123
}
5070-
if (devIds.isEmpty()) {
5124+
if (availableDeviceIds.isEmpty()) {
50715125
throw new RuntimeException(String.format("All device Ids are used by vm %s", vm));
50725126
}
5073-
deviceId = Long.parseLong(devIds.iterator().next());
5127+
deviceId = availableDeviceIds.get(0);
50745128
}
50755129

50765130
return deviceId;

‎server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java‎

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
import com.cloud.network.dao.NetworkDao;
7979
import com.cloud.network.dao.NetworkVO;
8080
import com.cloud.network.dao.PhysicalNetworkDao;
81+
import com.cloud.network.element.ConfigDriveNetworkElement;
8182
import com.cloud.offering.DiskOffering;
8283
import com.cloud.offering.NetworkOffering;
8384
import com.cloud.offering.ServiceOffering;
@@ -847,6 +848,23 @@ private Pair<DiskProfile, StoragePool> importDisk(UnmanagedInstanceTO.Disk disk,
847848
return new Pair<DiskProfile, StoragePool>(profile, storagePool);
848849
}
849850

851+
/**
852+
* Returns the device ids which cannot be used by the volumes of the imported Instance: the ones of the CD-ROM devices,
853+
* and the one of the config drive ISO (always kept free, as the NICs of the Instance are not imported yet at this point).
854+
*/
855+
private Set<Long> getReservedDeviceIds(VirtualMachine vm) {
856+
Set<Long> reservedDeviceIds = new HashSet<>(volumeApiService.getReservedDeviceIds(vm));
857+
reservedDeviceIds.add(ConfigDriveNetworkElement.CONFIGDRIVEDISKSEQ.longValue());
858+
return reservedDeviceIds;
859+
}
860+
861+
private long getNextDeviceId(long deviceId, Set<Long> reservedDeviceIds) {
862+
do {
863+
deviceId++;
864+
} while (reservedDeviceIds.contains(deviceId));
865+
return deviceId;
866+
}
867+
850868
private NicProfile importNic(UnmanagedInstanceTO.Nic nic, VirtualMachine vm, Network network, Network.IpAddresses ipAddresses, int deviceId, boolean isDefaultNic, boolean forced) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException {
851869
DataCenterVO dataCenterVO = dataCenterDao.findById(network.getDataCenterId());
852870
Pair<NicProfile, Integer> result = networkOrchestrationService.importNic(nic.getMacAddress(), deviceId, network, isDefaultNic, vm, ipAddresses, dataCenterVO, forced);
@@ -1165,7 +1183,8 @@ private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedI
11651183
DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId());
11661184
diskProfileStoragePoolList.add(importDisk(rootDisk, userVm, cluster, diskOffering, Volume.Type.ROOT, String.format("ROOT-%d", userVm.getId()),
11671185
rootDisk.getCapacity(), minIops, maxIops, template, owner, null));
1168-
long deviceId = 1L;
1186+
Set<Long> reservedDeviceIds = getReservedDeviceIds(userVm);
1187+
long deviceId = getNextDeviceId(0L, reservedDeviceIds);
11691188
for (UnmanagedInstanceTO.Disk disk : dataDisks) {
11701189
if (disk.getCapacity() == null || disk.getCapacity() == 0) {
11711190
throw new InvalidParameterValueException(String.format("Disk ID: %s size is invalid", rootDisk.getDiskId()));
@@ -1174,7 +1193,7 @@ private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedI
11741193
diskProfileStoragePoolList.add(importDisk(disk, userVm, cluster, offering, Volume.Type.DATADISK, String.format("DATA-%d-%s", userVm.getId(), disk.getDiskId()),
11751194
disk.getCapacity(), offering.getMinIops(), offering.getMaxIops(),
11761195
template, owner, deviceId));
1177-
deviceId++;
1196+
deviceId = getNextDeviceId(deviceId, reservedDeviceIds);
11781197
}
11791198
} catch (Exception e) {
11801199
logger.error(String.format("Failed to import volumes while importing vm: %s", displayName), e);
@@ -2789,15 +2808,16 @@ private UserVm importExternalKvmVirtualMachine(final UnmanagedInstanceTO unmanag
27892808
diskProfileStoragePoolList.add(importExternalDisk(rootDisk, userVm, dest, diskOffering, Volume.Type.ROOT,
27902809
template, null, remoteUrl, username, password, tmpPath, diskProfile));
27912810

2792-
long deviceId = 1L;
2811+
Set<Long> reservedDeviceIds = getReservedDeviceIds(userVm);
2812+
long deviceId = getNextDeviceId(0L, reservedDeviceIds);
27932813
diskSeq = 0;
27942814
for (UnmanagedInstanceTO.Disk disk : dataDisks) {
27952815
DiskProfile dataDiskProfile = dataDiskProfiles[diskSeq++];
27962816
DiskOffering offering = diskOfferingDao.findById(dataDiskOfferingMap.get(disk.getDiskId()));
27972817

27982818
diskProfileStoragePoolList.add(importExternalDisk(disk, userVm, dest, offering, Volume.Type.DATADISK,
27992819
template, deviceId, remoteUrl, username, password, tmpPath, dataDiskProfile));
2800-
deviceId++;
2820+
deviceId = getNextDeviceId(deviceId, reservedDeviceIds);
28012821
}
28022822
} catch (Exception e) {
28032823
logger.error(String.format("Failed to import volumes while importing vm: %s", instanceName), e);

0 commit comments

Comments
 (0)