|
21 | 21 | import java.net.URL; |
22 | 22 | import java.util.ArrayList; |
23 | 23 | import java.util.Arrays; |
| 24 | +import java.util.Collections; |
24 | 25 | import java.util.Date; |
25 | 26 | import java.util.HashMap; |
26 | 27 | import java.util.List; |
@@ -683,54 +684,73 @@ private String extract(Account caller, Long templateId, String url, Long zoneId, |
683 | 684 | @Override |
684 | 685 | public void prepareIsoForVmProfile(VirtualMachineProfile profile, DeployDestination dest) { |
685 | 686 | UserVmVO vm = _userVmDao.findById(profile.getId()); |
| 687 | + Long poolId = singleStoragePoolId(dest); |
| 688 | + Map<Integer, Long> slotToIsoId = loadAttachedIsoSlots(vm); |
| 689 | + |
| 690 | + // Pre-allocate every cdrom slot at boot. QEMU/IDE refuses to hot-add new cdrom drives, so |
| 691 | + // runtime attachIso can only media-swap into a slot the domain already owns. |
| 692 | + int totalSlots = Math.max(effectiveMaxCdroms(vm), slotsNeededFor(slotToIsoId)); |
| 693 | + for (int i = 0; i < totalSlots; i++) { |
| 694 | + int diskSeq = CDROM_PRIMARY_DEVICE_SEQ + i; |
| 695 | + Long isoId = slotToIsoId.get(diskSeq); |
| 696 | + profile.addDisk(isoId != null |
| 697 | + ? buildIsoDisk(profile, vm, dest, poolId, diskSeq, isoId) |
| 698 | + : buildEmptyCdromDisk(diskSeq)); |
| 699 | + } |
| 700 | + } |
686 | 701 |
|
| 702 | + private Long singleStoragePoolId(DeployDestination dest) { |
687 | 703 | Long poolId = null; |
688 | 704 | Map<Volume, StoragePool> storageForDisks = dest.getStorageForDisks(); |
689 | 705 | if (MapUtils.isNotEmpty(storageForDisks)) { |
690 | | - for (StoragePool storagePool : storageForDisks.values()) { |
691 | | - if (poolId != null && storagePool.getId() != poolId) { |
| 706 | + for (StoragePool pool : storageForDisks.values()) { |
| 707 | + if (poolId != null && pool.getId() != poolId) { |
692 | 708 | throw new CloudRuntimeException("Cannot determine where to download ISO"); |
693 | 709 | } |
694 | | - poolId = storagePool.getId(); |
| 710 | + poolId = pool.getId(); |
695 | 711 | } |
696 | 712 | } |
| 713 | + return poolId; |
| 714 | + } |
697 | 715 |
|
698 | | - Map<Integer, Long> slotToIsoId = new HashMap<>(); |
| 716 | + private Map<Integer, Long> loadAttachedIsoSlots(UserVmVO vm) { |
| 717 | + Map<Integer, Long> slots = new HashMap<>(); |
699 | 718 | if (vm.getIsoId() != null) { |
700 | | - slotToIsoId.put(CDROM_PRIMARY_DEVICE_SEQ, vm.getIsoId()); |
| 719 | + slots.put(CDROM_PRIMARY_DEVICE_SEQ, vm.getIsoId()); |
701 | 720 | } |
702 | 721 | for (VmIsoMapVO row : _vmIsoMapDao.listByVmId(vm.getId())) { |
703 | | - slotToIsoId.put(row.getDeviceSeq(), row.getIsoId()); |
| 722 | + slots.put(row.getDeviceSeq(), row.getIsoId()); |
704 | 723 | } |
| 724 | + return slots; |
| 725 | + } |
705 | 726 |
|
706 | | - // Empty ISO DiskTOs pre-allocate cdrom drives at boot so runtime attachIso can media-swap into them. |
707 | | - int cap = effectiveMaxCdroms(vm); |
708 | | - int neededForAttached = slotToIsoId.isEmpty() ? 0 |
709 | | - : slotToIsoId.keySet().stream().max(Integer::compare).get() - (CDROM_PRIMARY_DEVICE_SEQ - 1); |
710 | | - int totalSlots = Math.max(cap, neededForAttached); |
711 | | - for (int slot = 0; slot < totalSlots; slot++) { |
712 | | - int diskSeq = CDROM_PRIMARY_DEVICE_SEQ + slot; |
713 | | - Long isoId = slotToIsoId.get(diskSeq); |
714 | | - if (isoId != null) { |
715 | | - TemplateInfo template = prepareIso(isoId, vm.getDataCenterId(), dest.getHost().getId(), poolId); |
716 | | - if (template == null) { |
717 | | - logger.error("Failed to prepare ISO on secondary or cache storage"); |
718 | | - throw new CloudRuntimeException("Failed to prepare ISO on secondary or cache storage"); |
719 | | - } |
720 | | - if (diskSeq == CDROM_PRIMARY_DEVICE_SEQ && template.isBootable()) { |
721 | | - profile.setBootLoaderType(BootloaderType.CD); |
722 | | - } |
723 | | - GuestOSVO guestOS = _guestOSDao.findById(template.getGuestOSId()); |
724 | | - TemplateObjectTO iso = (TemplateObjectTO) template.getTO(); |
725 | | - iso.setDirectDownload(template.isDirectDownload()); |
726 | | - iso.setGuestOsType(guestOS != null ? guestOS.getDisplayName() : null); |
727 | | - profile.addDisk(new DiskTO(iso, (long) diskSeq, null, Volume.Type.ISO)); |
728 | | - } else { |
729 | | - TemplateObjectTO empty = new TemplateObjectTO(); |
730 | | - empty.setFormat(ImageFormat.ISO); |
731 | | - profile.addDisk(new DiskTO(empty, (long) diskSeq, null, Volume.Type.ISO)); |
732 | | - } |
| 727 | + private int slotsNeededFor(Map<Integer, Long> slotToIsoId) { |
| 728 | + if (slotToIsoId.isEmpty()) { |
| 729 | + return 0; |
733 | 730 | } |
| 731 | + return Collections.max(slotToIsoId.keySet()) - CDROM_PRIMARY_DEVICE_SEQ + 1; |
| 732 | + } |
| 733 | + |
| 734 | + private DiskTO buildIsoDisk(VirtualMachineProfile profile, UserVmVO vm, DeployDestination dest, Long poolId, int diskSeq, long isoId) { |
| 735 | + TemplateInfo template = prepareIso(isoId, vm.getDataCenterId(), dest.getHost().getId(), poolId); |
| 736 | + if (template == null) { |
| 737 | + logger.error("Failed to prepare ISO on secondary or cache storage"); |
| 738 | + throw new CloudRuntimeException("Failed to prepare ISO on secondary or cache storage"); |
| 739 | + } |
| 740 | + if (diskSeq == CDROM_PRIMARY_DEVICE_SEQ && template.isBootable()) { |
| 741 | + profile.setBootLoaderType(BootloaderType.CD); |
| 742 | + } |
| 743 | + GuestOSVO guestOS = _guestOSDao.findById(template.getGuestOSId()); |
| 744 | + TemplateObjectTO iso = (TemplateObjectTO) template.getTO(); |
| 745 | + iso.setDirectDownload(template.isDirectDownload()); |
| 746 | + iso.setGuestOsType(guestOS != null ? guestOS.getDisplayName() : null); |
| 747 | + return new DiskTO(iso, (long) diskSeq, null, Volume.Type.ISO); |
| 748 | + } |
| 749 | + |
| 750 | + private DiskTO buildEmptyCdromDisk(int diskSeq) { |
| 751 | + TemplateObjectTO empty = new TemplateObjectTO(); |
| 752 | + empty.setFormat(ImageFormat.ISO); |
| 753 | + return new DiskTO(empty, (long) diskSeq, null, Volume.Type.ISO); |
734 | 754 | } |
735 | 755 |
|
736 | 756 | private void prepareTemplateInOneStoragePool(final VMTemplateVO template, final StoragePoolVO pool) { |
@@ -1342,16 +1362,7 @@ public boolean attachIso(long isoId, long vmId, Boolean... extraParams) { |
1342 | 1362 | throw new InvalidParameterValueException("Cannot attach VMware tools drivers to incompatible hypervisor " + vm.getHypervisorType()); |
1343 | 1363 | } |
1344 | 1364 | if (!isVirtualRouter) { |
1345 | | - Long primaryIsoId = ((UserVm) vm).getIsoId(); |
1346 | | - if (isIsoAlreadyAttached(vmId, primaryIsoId, isoId)) { |
1347 | | - throw new InvalidParameterValueException("The specified ISO is already attached to this Instance."); |
1348 | | - } |
1349 | | - int effectiveMax = effectiveMaxCdroms(vm); |
1350 | | - int attached = (primaryIsoId != null ? 1 : 0) + _vmIsoMapDao.listByVmId(vmId).size(); |
1351 | | - if (attached >= effectiveMax) { |
1352 | | - throw new InvalidParameterValueException(String.format( |
1353 | | - "Instance has reached the maximum of %d attached CD-ROM(s); detach one before attaching another.", effectiveMax)); |
1354 | | - } |
| 1365 | + enforceCdromAttachLimits((UserVm) vm, isoId); |
1355 | 1366 | } |
1356 | 1367 | boolean result = attachISOToVM(vmId, userId, isoId, true, forced, isVirtualRouter); |
1357 | 1368 | if (result) { |
@@ -1438,45 +1449,54 @@ boolean attachISOToVM(long vmId, long userId, long isoId, boolean attach, boolea |
1438 | 1449 | UserVmVO vm = _userVmDao.findById(vmId); |
1439 | 1450 | VMTemplateVO iso = _tmpltDao.findById(isoId); |
1440 | 1451 |
|
1441 | | - int targetSlot; |
1442 | | - VmIsoMapVO mapEntry = null; |
| 1452 | + int targetSlot = attach ? chooseAttachSlot(vm) : findAttachedSlot(vm, isoId); |
| 1453 | + boolean success = attachISOToVM(vmId, isoId, targetSlot, attach, forced, isVirtualRouter); |
| 1454 | + if (!success || isVirtualRouter) { |
| 1455 | + return success; |
| 1456 | + } |
1443 | 1457 | if (attach) { |
1444 | | - VmIsoMapVO highestExtra = highestCdromMapEntry(vmId); |
1445 | | - if (vm.getIsoId() == null) { |
1446 | | - targetSlot = CDROM_PRIMARY_DEVICE_SEQ; |
1447 | | - } else if (highestExtra == null) { |
1448 | | - targetSlot = CDROM_PRIMARY_DEVICE_SEQ + 1; |
1449 | | - } else { |
1450 | | - targetSlot = highestExtra.getDeviceSeq() + 1; |
1451 | | - } |
| 1458 | + persistIsoAttachment(vm, iso, targetSlot); |
1452 | 1459 | } else { |
1453 | | - if (vm.getIsoId() != null && vm.getIsoId() == isoId) { |
1454 | | - targetSlot = CDROM_PRIMARY_DEVICE_SEQ; |
1455 | | - } else { |
1456 | | - mapEntry = _vmIsoMapDao.findByVmIdIsoId(vmId, isoId); |
1457 | | - targetSlot = mapEntry != null ? mapEntry.getDeviceSeq() : CDROM_PRIMARY_DEVICE_SEQ; |
1458 | | - } |
| 1460 | + persistIsoDetachment(vm, isoId, targetSlot); |
1459 | 1461 | } |
| 1462 | + return success; |
| 1463 | + } |
1460 | 1464 |
|
1461 | | - boolean success = attachISOToVM(vmId, isoId, targetSlot, attach, forced, isVirtualRouter); |
| 1465 | + private int chooseAttachSlot(UserVmVO vm) { |
| 1466 | + if (vm.getIsoId() == null) { |
| 1467 | + return CDROM_PRIMARY_DEVICE_SEQ; |
| 1468 | + } |
| 1469 | + VmIsoMapVO highest = highestCdromMapEntry(vm.getId()); |
| 1470 | + return highest == null ? CDROM_PRIMARY_DEVICE_SEQ + 1 : highest.getDeviceSeq() + 1; |
| 1471 | + } |
1462 | 1472 |
|
1463 | | - if (success && attach && !isVirtualRouter) { |
1464 | | - if (targetSlot == CDROM_PRIMARY_DEVICE_SEQ) { |
1465 | | - vm.setIsoId(iso.getId()); |
1466 | | - _userVmDao.update(vmId, vm); |
1467 | | - } else { |
1468 | | - _vmIsoMapDao.persist(new VmIsoMapVO(vmId, iso.getId(), targetSlot)); |
1469 | | - } |
| 1473 | + private int findAttachedSlot(UserVmVO vm, long isoId) { |
| 1474 | + if (vm.getIsoId() != null && vm.getIsoId() == isoId) { |
| 1475 | + return CDROM_PRIMARY_DEVICE_SEQ; |
1470 | 1476 | } |
1471 | | - if (success && !attach && !isVirtualRouter) { |
1472 | | - if (targetSlot == CDROM_PRIMARY_DEVICE_SEQ) { |
1473 | | - vm.setIsoId(null); |
1474 | | - _userVmDao.update(vmId, vm); |
1475 | | - } else if (mapEntry != null) { |
1476 | | - _vmIsoMapDao.remove(mapEntry.getId()); |
1477 | | - } |
| 1477 | + VmIsoMapVO entry = _vmIsoMapDao.findByVmIdIsoId(vm.getId(), isoId); |
| 1478 | + return entry != null ? entry.getDeviceSeq() : CDROM_PRIMARY_DEVICE_SEQ; |
| 1479 | + } |
| 1480 | + |
| 1481 | + private void persistIsoAttachment(UserVmVO vm, VMTemplateVO iso, int slot) { |
| 1482 | + if (slot == CDROM_PRIMARY_DEVICE_SEQ) { |
| 1483 | + vm.setIsoId(iso.getId()); |
| 1484 | + _userVmDao.update(vm.getId(), vm); |
| 1485 | + } else { |
| 1486 | + _vmIsoMapDao.persist(new VmIsoMapVO(vm.getId(), iso.getId(), slot)); |
| 1487 | + } |
| 1488 | + } |
| 1489 | + |
| 1490 | + private void persistIsoDetachment(UserVmVO vm, long isoId, int slot) { |
| 1491 | + if (slot == CDROM_PRIMARY_DEVICE_SEQ) { |
| 1492 | + vm.setIsoId(null); |
| 1493 | + _userVmDao.update(vm.getId(), vm); |
| 1494 | + return; |
| 1495 | + } |
| 1496 | + VmIsoMapVO entry = _vmIsoMapDao.findByVmIdIsoId(vm.getId(), isoId); |
| 1497 | + if (entry != null) { |
| 1498 | + _vmIsoMapDao.remove(entry.getId()); |
1478 | 1499 | } |
1479 | | - return success; |
1480 | 1500 | } |
1481 | 1501 |
|
1482 | 1502 | VmIsoMapVO highestCdromMapEntry(long vmId) { |
@@ -1515,9 +1535,22 @@ boolean isIsoAlreadyAttached(long vmId, Long primaryIsoId, long isoId) { |
1515 | 1535 | return _vmIsoMapDao.findByVmIdIsoId(vmId, isoId) != null; |
1516 | 1536 | } |
1517 | 1537 |
|
| 1538 | + private void enforceCdromAttachLimits(UserVm vm, long isoId) { |
| 1539 | + Long primaryIsoId = vm.getIsoId(); |
| 1540 | + if (isIsoAlreadyAttached(vm.getId(), primaryIsoId, isoId)) { |
| 1541 | + throw new InvalidParameterValueException("The specified ISO is already attached to this Instance."); |
| 1542 | + } |
| 1543 | + int effectiveMax = effectiveMaxCdroms(vm); |
| 1544 | + int attached = (primaryIsoId != null ? 1 : 0) + _vmIsoMapDao.listByVmId(vm.getId()).size(); |
| 1545 | + if (attached >= effectiveMax) { |
| 1546 | + throw new InvalidParameterValueException(String.format( |
| 1547 | + "Instance has reached the maximum of %d attached CD-ROM(s); detach one before attaching another.", effectiveMax)); |
| 1548 | + } |
| 1549 | + } |
| 1550 | + |
1518 | 1551 | private int effectiveMaxCdroms(VirtualMachine vm) { |
1519 | 1552 | int globalCap = VmCdromMaxCount.value(); |
1520 | | - // i440fx/IDE: hda is root, our slot scheme puts cdroms at hdc/hdd → 2 max on KVM. |
| 1553 | + // hda is root on i440fx/IDE, leaving hdc/hdd available for cdroms. |
1521 | 1554 | int hypervisorCap = (vm.getHypervisorType() == HypervisorType.KVM) ? 2 : 1; |
1522 | 1555 | return Math.min(globalCap, hypervisorCap); |
1523 | 1556 | } |
|
0 commit comments