This commit is contained in:
Andrija Panic 2026-07-04 15:34:20 +01:00 committed by GitHub
commit 67ac1184af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 307 additions and 11 deletions

View File

@ -3083,10 +3083,11 @@ public class ManagementServerImpl extends MutualExclusiveIdsManagerBase implemen
if (osDisplayName != null) {
List<GuestOSVO> guestOSVOS = _guestOSDao.listLikeDisplayName(osDisplayName);
if (CollectionUtils.isNotEmpty(guestOSVOS)) {
List<Long> guestOSids = guestOSVOS.stream().map(mo -> mo.getId()).collect(Collectors.toList());
sc.addAnd(guestOsId, SearchCriteria.Op.IN, guestOSids.toArray());
if (CollectionUtils.isEmpty(guestOSVOS)) {
return new Pair<>(Collections.emptyList(), 0);
}
List<Long> guestOSids = guestOSVOS.stream().map(mo -> mo.getId()).collect(Collectors.toList());
sc.addAnd(guestOsId, SearchCriteria.Op.IN, guestOSids.toArray());
}
final Pair<List<GuestOSHypervisorVO>, Integer> result = _guestOSHypervisorDao.searchAndCount(sc, searchFilter);

View File

@ -1732,11 +1732,12 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
Pair<UnmanagedInstanceTO, Boolean> sourceInstanceDetails = getSourceVmwareUnmanagedInstance(vcenter, datacenterName, username, password, clusterName, sourceHostName, sourceVMName, serviceOffering);
sourceVMwareInstance = sourceInstanceDetails.first();
isClonedInstance = sourceInstanceDetails.second();
guestOsId = resolveVmwareToKvmImportGuestOsId(guestOsId, sourceVMwareInstance);
// Ensure that the configured resource limits will not be exceeded before beginning the conversion process
checkVmResourceLimitsForUnmanagedInstanceImport(owner, sourceVMwareInstance, serviceOffering, template, reservations);
boolean isWindowsVm = sourceVMwareInstance.getOperatingSystem().toLowerCase().contains("windows");
boolean isWindowsVm = StringUtils.containsIgnoreCase(sourceVMwareInstance.getOperatingSystem(), "windows");
if (isWindowsVm) {
checkConversionSupportOnHost(convertHost, sourceVMName, true, useVddk, details);
}
@ -1792,6 +1793,104 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
}
}
protected Long resolveVmwareToKvmImportGuestOsId(Long requestedGuestOsId, UnmanagedInstanceTO sourceVMwareInstance) {
if (requestedGuestOsId != null || sourceVMwareInstance == null) {
return requestedGuestOsId;
}
String osDisplayName = sourceVMwareInstance.getOperatingSystem();
String osNameForHypervisor = sourceVMwareInstance.getOperatingSystemId();
String sourceHypervisorVersion = sourceVMwareInstance.getHostHypervisorVersion();
GuestOSHypervisor guestOSHypervisor = findVmwareGuestOsMappingByDisplayName(osDisplayName, sourceHypervisorVersion);
if (guestOSHypervisor == null) {
guestOSHypervisor = findVmwareGuestOsMappingByOsName(osNameForHypervisor, sourceHypervisorVersion);
}
if (guestOSHypervisor == null) {
guestOSHypervisor = findVmwareGuestOsMappingByDisplayName(osDisplayName, null);
}
if (guestOSHypervisor == null) {
guestOSHypervisor = findVmwareGuestOsMappingByOsName(osNameForHypervisor, null);
}
if (guestOSHypervisor != null) {
return guestOSHypervisor.getGuestOsId();
}
GuestOS guestOS = findVmwareGuestOsByDisplayName(osDisplayName);
return guestOS != null ? guestOS.getId() : null;
}
private GuestOSHypervisor findVmwareGuestOsMappingByDisplayName(String osDisplayName, String hypervisorVersion) {
List<GuestOS> guestOSes = listStrongMatchingGuestOses(osDisplayName);
for (GuestOS guestOS : guestOSes) {
GuestOSHypervisor guestOSHypervisor = guestOSHypervisorDao.findByOsIdAndHypervisor(
guestOS.getId(), Hypervisor.HypervisorType.VMware.toString(), hypervisorVersion);
if (guestOSHypervisor != null) {
return guestOSHypervisor;
}
}
return null;
}
private GuestOSHypervisor findVmwareGuestOsMappingByOsName(String osNameForHypervisor, String hypervisorVersion) {
if (StringUtils.isBlank(osNameForHypervisor)) {
return null;
}
return guestOSHypervisorDao.findByOsNameAndHypervisor(
osNameForHypervisor, Hypervisor.HypervisorType.VMware.toString(), hypervisorVersion);
}
private GuestOS findVmwareGuestOsByDisplayName(String osDisplayName) {
List<GuestOS> guestOSes = listStrongMatchingGuestOses(osDisplayName);
return CollectionUtils.isNotEmpty(guestOSes) ? guestOSes.get(0) : null;
}
private List<GuestOS> listStrongMatchingGuestOses(String osDisplayName) {
List<GuestOS> guestOSes = new ArrayList<>();
if (StringUtils.isBlank(osDisplayName)) {
return guestOSes;
}
GuestOS exactMatch = guestOSDao.findOneByDisplayName(osDisplayName);
if (exactMatch != null) {
guestOSes.add(exactMatch);
}
List<? extends GuestOS> candidates = guestOSDao.listLikeDisplayName(osDisplayName);
if (CollectionUtils.isEmpty(candidates)) {
return guestOSes;
}
for (GuestOS candidate : candidates) {
if (candidate == null || exactMatch != null && candidate.getId() == exactMatch.getId()) {
continue;
}
if (isStrongGuestOsNameMatch(candidate.getDisplayName(), osDisplayName)) {
guestOSes.add(candidate);
}
}
return guestOSes;
}
private boolean isStrongGuestOsNameMatch(String candidateName, String sourceName) {
String candidate = normalizeGuestOsName(candidateName);
String source = normalizeGuestOsName(sourceName);
if (StringUtils.isAnyBlank(candidate, source)) {
return false;
}
if (candidate.equals(source)) {
return true;
}
if (Math.min(candidate.length(), source.length()) < 8) {
return false;
}
return candidate.contains(source) || source.contains(candidate);
}
private String normalizeGuestOsName(String name) {
return StringUtils.defaultString(name).toLowerCase().replaceAll("[^a-z0-9]+", " ").trim();
}
/**
* Check whether the conversion storage pool exists and is suitable for the conversion or not.
* Secondary storage is only allowed when forceConvertToPool is false.

View File

@ -135,6 +135,8 @@ import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.GuestOSHypervisorVO;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.ScopeType;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
@ -147,6 +149,8 @@ import com.cloud.storage.Volume;
import com.cloud.storage.VolumeApiService;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.storage.dao.GuestOSHypervisorDao;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.StoragePoolHostDao;
import com.cloud.storage.dao.VMTemplateDao;
@ -249,6 +253,10 @@ public class UnmanagedVMsManagerImplTest {
private ImportVmTasksManager importVmTasksManager;
@Mock
private SnapshotDao snapshotDao;
@Mock
private GuestOSDao guestOSDao;
@Mock
private GuestOSHypervisorDao guestOSHypervisorDao;
@Mock
private VMInstanceVO virtualMachine;
@ -730,6 +738,88 @@ public class UnmanagedVMsManagerImplTest {
Assert.assertEquals(defaultTemplateName, templateForImportInstance.getName());
}
@Test
public void testResolveVmwareToKvmImportGuestOsIdKeepsRequestedGuestOsId() {
Long resolvedGuestOsId = unmanagedVMsManager.resolveVmwareToKvmImportGuestOsId(1L, instance);
Assert.assertEquals(Long.valueOf(1L), resolvedGuestOsId);
Mockito.verifyNoInteractions(guestOSDao, guestOSHypervisorDao);
}
@Test
public void testResolveVmwareToKvmImportGuestOsIdPrefersDisplayNameMapping() {
String osDisplayName = "Debian GNU/Linux 11 (64-bit)";
instance.setOperatingSystem(osDisplayName);
instance.setOperatingSystemId("otherLinux64Guest");
instance.setHostHypervisorVersion("8.0.3");
GuestOSVO guestOS = mock(GuestOSVO.class);
when(guestOS.getId()).thenReturn(10L);
when(guestOSDao.findOneByDisplayName(osDisplayName)).thenReturn(guestOS);
when(guestOSDao.listLikeDisplayName(osDisplayName)).thenReturn(List.of(guestOS));
GuestOSHypervisorVO guestOSHypervisor = mock(GuestOSHypervisorVO.class);
when(guestOSHypervisor.getGuestOsId()).thenReturn(20L);
when(guestOSHypervisorDao.findByOsIdAndHypervisor(10L, Hypervisor.HypervisorType.VMware.toString(), "8.0.3")).thenReturn(guestOSHypervisor);
Long resolvedGuestOsId = unmanagedVMsManager.resolveVmwareToKvmImportGuestOsId(null, instance);
Assert.assertEquals(Long.valueOf(20L), resolvedGuestOsId);
Mockito.verify(guestOSHypervisorDao, Mockito.never()).findByOsNameAndHypervisor(anyString(), anyString(), anyString());
}
@Test
public void testResolveVmwareToKvmImportGuestOsIdFallsBackToConfiguredGuestOsIdentifier() {
String osDisplayName = "Unmatched Runtime OS";
instance.setOperatingSystem(osDisplayName);
instance.setOperatingSystemId("ubuntu64Guest");
instance.setHostHypervisorVersion("8.0.3");
when(guestOSDao.findOneByDisplayName(osDisplayName)).thenReturn(null);
when(guestOSDao.listLikeDisplayName(osDisplayName)).thenReturn(Collections.emptyList());
GuestOSHypervisorVO guestOSHypervisor = mock(GuestOSHypervisorVO.class);
when(guestOSHypervisor.getGuestOsId()).thenReturn(30L);
when(guestOSHypervisorDao.findByOsNameAndHypervisor("ubuntu64Guest", Hypervisor.HypervisorType.VMware.toString(), "8.0.3")).thenReturn(guestOSHypervisor);
Long resolvedGuestOsId = unmanagedVMsManager.resolveVmwareToKvmImportGuestOsId(null, instance);
Assert.assertEquals(Long.valueOf(30L), resolvedGuestOsId);
}
@Test
public void testResolveVmwareToKvmImportGuestOsIdFallsBackToGuestOsTypeMatch() {
String osDisplayName = "Debian GNU/Linux 11 (64-bit)";
instance.setOperatingSystem(osDisplayName);
instance.setOperatingSystemId("otherLinux64Guest");
instance.setHostHypervisorVersion("8.0.3");
GuestOSVO guestOS = mock(GuestOSVO.class);
when(guestOS.getId()).thenReturn(40L);
when(guestOSDao.findOneByDisplayName(osDisplayName)).thenReturn(guestOS);
when(guestOSDao.listLikeDisplayName(osDisplayName)).thenReturn(List.of(guestOS));
Long resolvedGuestOsId = unmanagedVMsManager.resolveVmwareToKvmImportGuestOsId(null, instance);
Assert.assertEquals(Long.valueOf(40L), resolvedGuestOsId);
}
@Test
public void testResolveVmwareToKvmImportGuestOsIdAvoidsWeakGuestOsTypeMatch() {
String osDisplayName = "Linux";
instance.setOperatingSystem(osDisplayName);
instance.setOperatingSystemId(null);
instance.setHostHypervisorVersion("8.0.3");
GuestOSVO guestOS = mock(GuestOSVO.class);
when(guestOS.getDisplayName()).thenReturn("Other Linux (64-bit)");
when(guestOSDao.findOneByDisplayName(osDisplayName)).thenReturn(null);
when(guestOSDao.listLikeDisplayName(osDisplayName)).thenReturn(List.of(guestOS));
Long resolvedGuestOsId = unmanagedVMsManager.resolveVmwareToKvmImportGuestOsId(null, instance);
Assert.assertNull(resolvedGuestOsId);
}
private enum VcenterParameter {
EXISTING,
EXTERNAL,

View File

@ -253,7 +253,7 @@
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}">
<a-select-option v-for="mapping in resource.guestOsMappings" :key="mapping.ostypeid" :label="mapping.osdisplayname">
<a-select-option v-for="mapping in resource.guestOsMappings" :key="mapping.ostypeid" :value="mapping.ostypeid" :label="mapping.osdisplayname">
<span>
{{ mapping.osdisplayname }}
</span>

View File

@ -1379,11 +1379,7 @@ export default {
this.fetchInstances()
}
},
async fetchGuestOsMappings (osIdentifier, hypervisorVersion) {
const params = {}
params.hypervisor = 'VMware'
params.hypervisorversion = hypervisorVersion
params.osnameforhypervisor = osIdentifier
async fetchGuestOsMappingsByParams (params) {
return await getAPI('listGuestOsMapping', params).then(json => {
return json.listguestosmappingresponse?.guestosmapping || []
}).catch(error => {
@ -1391,6 +1387,113 @@ export default {
return []
})
},
normalizeGuestOsName (name) {
return (name || '')
.toLowerCase()
.replace(/^microsoft\s+/, '')
.replace(/[^a-z0-9]+/g, ' ')
.replace(/\s+/g, ' ')
.trim()
},
isStrongGuestOsNameMatch (osType, osDisplayName) {
const candidate = this.normalizeGuestOsName(osType.description || osType.osdisplayname)
const source = this.normalizeGuestOsName(osDisplayName)
if (!candidate || !source) {
return false
}
if (candidate === source) {
return true
}
if (Math.min(candidate.length, source.length) < 8) {
return false
}
return candidate.includes(source) || source.includes(candidate)
},
async fetchGuestOsTypeFallbackMappings (osDisplayName) {
if (!osDisplayName || !('listOsTypes' in this.$store.getters.apis)) {
return []
}
return await getAPI('listOsTypes', {
description: osDisplayName
}).then(json => {
const osTypes = json.listostypesresponse?.ostype || []
return osTypes
.filter(osType => this.isStrongGuestOsNameMatch(osType, osDisplayName))
.map(osType => {
return {
ostypeid: osType.id,
osdisplayname: osType.description || osType.osdisplayname
}
})
}).catch(error => {
this.$notifyError(error)
return []
})
},
filterGuestOsMappings (mappings, params, osDisplayName) {
if (!mappings || mappings.length === 0) {
return []
}
const osNameForHypervisor = (params.osnameforhypervisor || '').toLowerCase()
let filteredMappings = mappings
if (osNameForHypervisor) {
filteredMappings = mappings.filter(mapping => (mapping.osnameforhypervisor || '').toLowerCase() === osNameForHypervisor)
}
if (osDisplayName) {
const displayNameMatches = filteredMappings.filter(mapping => this.isStrongGuestOsNameMatch(mapping, osDisplayName))
if (displayNameMatches.length > 0) {
return displayNameMatches
}
if (params.osdisplayname) {
return []
}
}
return filteredMappings
},
async fetchGuestOsMappings (osIdentifier, osDisplayName, hypervisorVersion) {
const lookups = []
if (osIdentifier) {
lookups.push({
hypervisor: 'VMware',
hypervisorversion: hypervisorVersion,
osnameforhypervisor: osIdentifier
})
}
if (osIdentifier) {
lookups.push({
hypervisor: 'VMware',
osnameforhypervisor: osIdentifier
})
}
if (osDisplayName) {
lookups.push({
hypervisor: 'VMware',
hypervisorversion: hypervisorVersion,
osdisplayname: osDisplayName
})
}
if (osDisplayName) {
lookups.push({
hypervisor: 'VMware',
osdisplayname: osDisplayName
})
}
for (const params of lookups) {
if (!params.hypervisorversion) {
delete params.hypervisorversion
}
const mappings = await this.fetchGuestOsMappingsByParams(params)
const filteredMappings = this.filterGuestOsMappings(mappings, params, osDisplayName)
if (filteredMappings.length > 0) {
return filteredMappings
}
}
return await this.fetchGuestOsTypeFallbackMappings(osDisplayName)
},
fetchVmwareInstanceForKVMMigration (vmname, hostname) {
const params = {}
this.loadingGuestOsMappings = true
@ -1411,7 +1514,10 @@ export default {
this.selectedUnmanagedInstance = response.unmanagedinstance[0]
this.selectedUnmanagedInstance.ostypename = this.selectedUnmanagedInstance.osdisplayname
this.selectedUnmanagedInstance.state = this.selectedUnmanagedInstance.powerstate
this.selectedUnmanagedInstance.guestOsMappings = await this.fetchGuestOsMappings(this.selectedUnmanagedInstance.osid, this.selectedUnmanagedInstance.hypervisorversion)
this.selectedUnmanagedInstance.guestOsMappings = await this.fetchGuestOsMappings(
this.selectedUnmanagedInstance.osid,
this.selectedUnmanagedInstance.osdisplayname,
this.selectedUnmanagedInstance.hypervisorversion)
}).catch(error => {
this.$notifyError(error)
}).finally(() => {