From b53c760ad8fac62c1cd1586f3e1b677c10605686 Mon Sep 17 00:00:00 2001 From: Andrija Panic Date: Thu, 7 May 2026 20:39:16 +0200 Subject: [PATCH] Allow preserving duplicate MACs during VM import --- .../apache/cloudstack/api/ApiConstants.java | 1 + .../admin/vm/ImportUnmanagedInstanceCmd.java | 12 +++- .../service/NetworkOrchestrationService.java | 2 +- .../orchestration/NetworkOrchestrator.java | 4 +- .../NetworkOrchestratorTest.java | 8 +-- .../vm/UnmanagedVMsManagerImpl.java | 46 +++++++------ .../com/cloud/vpc/MockNetworkManagerImpl.java | 2 +- .../vm/UnmanagedVMsManagerImplTest.java | 2 +- ui/public/locales/en.json | 1 + .../views/tools/ImportUnmanagedInstance.vue | 68 +++++++++++++++++-- 10 files changed, 110 insertions(+), 36 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 4d4ead277e5d..521e671bc795 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -250,6 +250,7 @@ public class ApiConstants { public static final String FILESYSTEM = "filesystem"; public static final String FIRSTNAME = "firstname"; public static final String FORCED = "forced"; + public static final String ALLOW_DUPLICATE_MAC_ADDRESSES = "allowduplicatemacaddresses"; public static final String FORCED_DESTROY_LOCAL_STORAGE = "forcedestroylocalstorage"; public static final String FORCE_CONVERT_TO_POOL = "forceconverttopool"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java index 3284dbafe7ca..9097788a87cf 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java @@ -152,9 +152,15 @@ public class ImportUnmanagedInstanceCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, - description = "Instance is imported despite some of its NIC's MAC addresses are already present, in case the MAC address exists then a new MAC address is generated") + description = "Instance is imported even if some NIC MAC addresses already exist. If a MAC address exists, a new MAC address is generated") private Boolean forced; + @Parameter(name = ApiConstants.ALLOW_DUPLICATE_MAC_ADDRESSES, + type = CommandType.BOOLEAN, + since = "4.23.0", + description = "Preserve imported NIC MAC addresses even when they already exist on the target network. Intended for migration cutover workflows where the source and imported VMs are not active on the same L2 network at the same time") + private Boolean allowDuplicateMacAddresses; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -278,6 +284,10 @@ public boolean isForced() { return BooleanUtils.isTrue(forced); } + public boolean isAllowDuplicateMacAddresses() { + return BooleanUtils.isTrue(allowDuplicateMacAddresses); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java index 030c1277efe2..39baf907c954 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java @@ -372,7 +372,7 @@ void implementNetworkElementsAndResources(DeployDestination dest, ReservationCon */ void cleanupNicDhcpDnsEntry(Network network, VirtualMachineProfile vmProfile, NicProfile nicProfile); - Pair importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, final Network.IpAddresses ipAddresses, final DataCenter datacenter, boolean forced) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException; + Pair importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, final Network.IpAddresses ipAddresses, final DataCenter datacenter, boolean forced, boolean allowDuplicateMacAddress) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException; void unmanageNics(VirtualMachineProfile vm); diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index 4262ee701aab..7fe466586f02 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -4770,7 +4770,7 @@ public NicVO savePlaceholderNic(final Network network, final String ip4Address, @DB @Override - public Pair importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, final Network.IpAddresses ipAddresses, final DataCenter dataCenter, final boolean forced) + public Pair importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, final Network.IpAddresses ipAddresses, final DataCenter dataCenter, final boolean forced, final boolean allowDuplicateMacAddress) throws ConcurrentOperationException, InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException { logger.debug("Allocating NIC for Instance {} in Network {} during import", vm, network); String selectedIp = null; @@ -4796,7 +4796,7 @@ public NicVO doInTransaction(TransactionStatus status) { throw new CloudRuntimeException("Invalid mac address: " + macAddressToPersist); } NicVO existingNic = _nicDao.findByNetworkIdAndMacAddress(network.getId(), macAddressToPersist); - if (existingNic != null) { + if (existingNic != null && !allowDuplicateMacAddress) { macAddressToPersist = generateNewMacAddressIfForced(network, macAddressToPersist, forced); } NicVO vo = new NicVO(network.getGuruName(), vm.getId(), network.getId(), vm.getType()); diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java index e3989737112d..fde23956013b 100644 --- a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java @@ -913,7 +913,7 @@ public void testImportNicAcquireGuestIPFailed() throws Exception { Mockito.when(testOrchestrator._networkModel.listNetworkOfferingServices(networkOfferingId)).thenReturn(Arrays.asList(Service.Dns, Service.Dhcp)); String macAddress = "02:01:01:82:00:01"; int deviceId = 0; - testOrchestrator.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false); + testOrchestrator.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false, false); } @Test(expected = InsufficientVirtualNetworkCapacityException.class) @@ -932,7 +932,7 @@ public void testImportNicAutoAcquireGuestIPFailed() throws Exception { Mockito.when(testOrchestrator._networkModel.listNetworkOfferingServices(networkOfferingId)).thenReturn(Arrays.asList(Service.Dns, Service.Dhcp)); String macAddress = "02:01:01:82:00:01"; int deviceId = 0; - testOrchestrator.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false); + testOrchestrator.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false, false); } @Test @@ -959,7 +959,7 @@ public void testImportNicNoIP4Address() throws Exception { Mockito.when(testOrchestrator._networkModel.getNetworkTag(hypervisorType, network)).thenReturn("testtag"); try (MockedStatic transactionMocked = Mockito.mockStatic(Transaction.class)) { transactionMocked.when(() -> Transaction.execute(any(TransactionCallback.class))).thenReturn(nic); - Pair nicProfileIntegerPair = testOrchestrator.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false); + Pair nicProfileIntegerPair = testOrchestrator.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false, false); verify(testOrchestrator._networkModel, times(1)).getNetworkRate(networkId, vmId); verify(testOrchestrator._networkModel, times(1)).isSecurityGroupSupportedInNetwork(network); verify(testOrchestrator._networkModel, times(1)).getNetworkTag(Hypervisor.HypervisorType.KVM, network); @@ -997,7 +997,7 @@ public void testImportNicWithIP4Address() throws Exception { Mockito.when(testOrchestrator._networkModel.getNetworkTag(hypervisorType, network)).thenReturn("testtag"); try (MockedStatic transactionMocked = Mockito.mockStatic(Transaction.class)) { transactionMocked.when(() -> Transaction.execute(any(TransactionCallback.class))).thenReturn(nic); - Pair nicProfileIntegerPair = testOrchestrator.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false); + Pair nicProfileIntegerPair = testOrchestrator.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false, false); verify(testOrchestrator, times(1)).getSelectedIpForNicImport(network, dataCenter, ipAddresses); verify(testOrchestrator._networkModel, times(1)).getNetworkRate(networkId, vmId); verify(testOrchestrator._networkModel, times(1)).isSecurityGroupSupportedInNetwork(network); diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 846eab599fd1..14b280420ab8 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -847,9 +847,10 @@ private Pair importDisk(UnmanagedInstanceTO.Disk disk, return new Pair(profile, storagePool); } - private NicProfile importNic(UnmanagedInstanceTO.Nic nic, VirtualMachine vm, Network network, Network.IpAddresses ipAddresses, int deviceId, boolean isDefaultNic, boolean forced) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException { + private NicProfile importNic(UnmanagedInstanceTO.Nic nic, VirtualMachine vm, Network network, Network.IpAddresses ipAddresses, int deviceId, boolean isDefaultNic, + boolean forced, boolean allowDuplicateMacAddress) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException { DataCenterVO dataCenterVO = dataCenterDao.findById(network.getDataCenterId()); - Pair result = networkOrchestrationService.importNic(nic.getMacAddress(), deviceId, network, isDefaultNic, vm, ipAddresses, dataCenterVO, forced); + Pair result = networkOrchestrationService.importNic(nic.getMacAddress(), deviceId, network, isDefaultNic, vm, ipAddresses, dataCenterVO, forced, allowDuplicateMacAddress); if (result == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("NIC ID: %s import failed", nic.getNicId())); } @@ -1056,7 +1057,8 @@ private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedI final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId, final ServiceOfferingVO serviceOffering, final Map dataDiskOfferingMap, final Map nicNetworkMap, final Map callerNicIpAddressMap, final Long guestOsId, - final Map details, final boolean migrateAllowed, final boolean forced, final boolean isImportUnmanagedFromSameHypervisor) { + final Map details, final boolean migrateAllowed, final boolean forced, final boolean allowDuplicateMacAddress, + final boolean isImportUnmanagedFromSameHypervisor) { logger.debug(LogUtils.logGsonWithoutException("Trying to import VM [%s] with name [%s], in zone [%s], cluster [%s], and host [%s], using template [%s], service offering [%s], disks map [%s], NICs map [%s] and details [%s].", unmanagedInstance, displayName, zone, cluster, host, template, serviceOffering, dataDiskOfferingMap, nicNetworkMap, details)); UserVm userVm = null; @@ -1186,7 +1188,7 @@ private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedI for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) { Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId())); Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId()); - importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex == 0, forced); + importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex == 0, forced, allowDuplicateMacAddress); nicIndex++; } } catch (Exception e) { @@ -1319,6 +1321,10 @@ private UserVmResponse baseImportInstance(ImportUnmanagedInstanceCmd cmd) { final Map dataDiskOfferingMap = cmd.getDataDiskToDiskOfferingList(); final Map details = cmd.getDetails(); final boolean forced = cmd.isForced(); + final boolean allowDuplicateMacAddress = cmd.isAllowDuplicateMacAddresses(); + if (forced && allowDuplicateMacAddress) { + throw new InvalidParameterValueException(String.format("%s and %s are mutually exclusive", ApiConstants.FORCED, ApiConstants.ALLOW_DUPLICATE_MAC_ADDRESSES)); + } List hosts = resourceManager.listHostsInClusterByStatus(clusterId, Status.Up); UserVm userVm = null; List additionalNameFilters = getAdditionalNameFilters(cluster); @@ -1342,7 +1348,7 @@ private UserVmResponse baseImportInstance(ImportUnmanagedInstanceCmd cmd) { template, instanceName, displayName, hostName, caller, owner, userId, serviceOffering, dataDiskOfferingMap, nicNetworkMap, nicIpAddressMap, - details, importVmCmd, forced); + details, importVmCmd, forced, allowDuplicateMacAddress); } } else { if (List.of(Hypervisor.HypervisorType.VMware, Hypervisor.HypervisorType.KVM).contains(cluster.getHypervisorType())) { @@ -1350,7 +1356,7 @@ private UserVmResponse baseImportInstance(ImportUnmanagedInstanceCmd cmd) { template, instanceName, displayName, hostName, caller, owner, userId, serviceOffering, dataDiskOfferingMap, nicNetworkMap, nicIpAddressMap, - details, cmd.getMigrateAllowed(), managedVms, forced); + details, cmd.getMigrateAllowed(), managedVms, forced, allowDuplicateMacAddress); } } @@ -1497,7 +1503,8 @@ private UserVm importUnmanagedInstanceFromHypervisor(DataCenter zone, Cluster cl String hostName, Account caller, Account owner, long userId, ServiceOfferingVO serviceOffering, Map dataDiskOfferingMap, Map nicNetworkMap, Map nicIpAddressMap, - Map details, Boolean migrateAllowed, List managedVms, boolean forced) throws ResourceAllocationException { + Map details, Boolean migrateAllowed, List managedVms, boolean forced, + boolean allowDuplicateMacAddress) throws ResourceAllocationException { UserVm userVm = null; for (HostVO host : hosts) { HashMap unmanagedInstances = getUnmanagedInstancesForHost(host, instanceName, managedVms); @@ -1549,7 +1556,7 @@ private UserVm importUnmanagedInstanceFromHypervisor(DataCenter zone, Cluster cl template, displayName, hostName, CallContext.current().getCallingAccount(), owner, userId, serviceOffering, dataDiskOfferingMap, nicNetworkMap, nicIpAddressMap, null, - details, migrateAllowed, forced, true); + details, migrateAllowed, forced, allowDuplicateMacAddress, true); } finally { ReservationHelper.closeAll(reservations); } @@ -1648,7 +1655,7 @@ protected UserVm importUnmanagedInstanceFromVmwareToKvm(DataCenter zone, Cluster Account caller, Account owner, long userId, ServiceOfferingVO serviceOffering, Map dataDiskOfferingMap, Map nicNetworkMap, Map nicIpAddressMap, - Map details, ImportVmCmd cmd, boolean forced) throws ResourceAllocationException { + Map details, ImportVmCmd cmd, boolean forced, boolean allowDuplicateMacAddress) throws ResourceAllocationException { Long existingVcenterId = cmd.getExistingVcenterId(); String vcenter = cmd.getVcenter(); String datacenterName = cmd.getDatacenterName(); @@ -1741,7 +1748,7 @@ protected UserVm importUnmanagedInstanceFromVmwareToKvm(DataCenter zone, Cluster checkConversionSupportOnHost(convertHost, sourceVMName, true, useVddk, details); } - checkNetworkingBeforeConvertingVmwareInstance(zone, owner, displayName, hostName, sourceVMwareInstance, nicNetworkMap, nicIpAddressMap, forced); + checkNetworkingBeforeConvertingVmwareInstance(zone, owner, displayName, hostName, sourceVMwareInstance, nicNetworkMap, nicIpAddressMap, forced, allowDuplicateMacAddress); UnmanagedInstanceTO convertedInstance; if (!useVddk && (forceMsToImportVmFiles || !isOvfExportSupported)) { // Uses MS for OVF export to temporary conversion location @@ -1769,7 +1776,7 @@ protected UserVm importUnmanagedInstanceFromVmwareToKvm(DataCenter zone, Cluster template, displayName, hostName, caller, owner, userId, serviceOffering, dataDiskOfferingMap, nicNetworkMap, nicIpAddressMap, guestOsId, - details, false, forced, false); + details, false, forced, allowDuplicateMacAddress, false); long timeElapsedInSecs = (System.currentTimeMillis() - importStartTime) / 1000; logger.debug(String.format("VMware VM %s imported successfully to CloudStack instance %s (%s), Time taken: %d secs, OVF files imported from %s, Source VMware VM details - OS: %s, PowerState: %s, Disks: %s, NICs: %s", sourceVMName, displayName, displayName, timeElapsedInSecs, (ovfTemplateOnConvertLocation != null)? "MS" : "KVM Host", sourceVMwareInstance.getOperatingSystem(), sourceVMwareInstance.getPowerState(), sourceVMwareInstance.getDisks(), sourceVMwareInstance.getNics())); @@ -1859,7 +1866,7 @@ private void checkNetworkingBeforeConvertingVmwareInstance(DataCenter zone, Acco String hostName, UnmanagedInstanceTO sourceVMwareInstance, Map nicNetworkMap, Map nicIpAddressMap, - boolean forced) { + boolean forced, boolean allowDuplicateMacAddress) { List nics = sourceVMwareInstance.getNics(); List networkIds = new ArrayList<>(nicNetworkMap.values()); if (nics.size() != networkIds.size()) { @@ -1882,18 +1889,19 @@ private void checkNetworkingBeforeConvertingVmwareInstance(DataCenter zone, Acco ipAddresses = nicIpAddressMap.get(nic.getNicId()); } boolean autoImport = ipAddresses != null && ipAddresses.getIp4Address() != null && ipAddresses.getIp4Address().equalsIgnoreCase("auto"); - checkUnmanagedNicAndNetworkMacAddressForImport(network, nic, forced); + checkUnmanagedNicAndNetworkMacAddressForImport(network, nic, forced, allowDuplicateMacAddress); checkUnmanagedNicAndNetworkForImport(displayName, nic, network, zone, owner, autoImport, Hypervisor.HypervisorType.KVM); checkUnmanagedNicAndNetworkHostnameForImport(displayName, nic, network, hostName); checkUnmanagedNicIpAndNetworkForImport(displayName, nic, network, ipAddresses); } } - private void checkUnmanagedNicAndNetworkMacAddressForImport(NetworkVO network, UnmanagedInstanceTO.Nic nic, boolean forced) { + private void checkUnmanagedNicAndNetworkMacAddressForImport(NetworkVO network, UnmanagedInstanceTO.Nic nic, boolean forced, boolean allowDuplicateMacAddress) { NicVO existingNic = nicDao.findByNetworkIdAndMacAddress(network.getId(), nic.getMacAddress()); - if (existingNic != null && !forced) { - String err = String.format("NIC %s with MAC address %s already exists on network %s and forced flag is disabled. " + - "Retry with forced flag enabled if a new MAC address to be generated.", nic, nic.getMacAddress(), network); + if (existingNic != null && !forced && !allowDuplicateMacAddress) { + String err = String.format("NIC %s with MAC address %s already exists on network %s. " + + "Retry with %s=true to generate a new MAC address or %s=true to preserve the duplicate MAC address.", + nic, nic.getMacAddress(), network, ApiConstants.FORCED, ApiConstants.ALLOW_DUPLICATE_MAC_ADDRESSES); logger.error(err); throw new CloudRuntimeException(err); } @@ -2809,7 +2817,7 @@ private UserVm importExternalKvmVirtualMachine(final UnmanagedInstanceTO unmanag for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) { Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId())); Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId()); - importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex==0, true); + importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex==0, true, false); nicIndex++; } } catch (Exception e) { @@ -2980,7 +2988,7 @@ private UserVm importKvmVirtualMachineFromDisk(final ImportSource importSource, cleanupFailedImportVM(userVm); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import volumes while importing vm: %s. %s", instanceName, StringUtils.defaultString(e.getMessage()))); } - networkOrchestrationService.importNic(macAddress, 0, network, true, userVm, requestedIpPair, zone, true); + networkOrchestrationService.importNic(macAddress, 0, network, true, userVm, requestedIpPair, zone, true, false); publishVMUsageUpdateResourceCount(userVm, dummyOffering, template); return userVm; diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index e4bc2096b9da..78fa8e508588 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -1071,7 +1071,7 @@ public AcquirePodIpCmdResponse allocatePodIp(Account account, String zoneId, Str } @Override - public Pair importNic(String macAddress, int deviceId, Network network, Boolean isDefaultNic, VirtualMachine vm, IpAddresses ipAddresses, DataCenter dataCenter, boolean forced) { + public Pair importNic(String macAddress, int deviceId, Network network, Boolean isDefaultNic, VirtualMachine vm, IpAddresses ipAddresses, DataCenter dataCenter, boolean forced, boolean allowDuplicateMacAddress) { return null; } diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index 874bce0f95ef..b83e41f76aee 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -388,7 +388,7 @@ public void setUp() throws Exception { NicProfile profile = Mockito.mock(NicProfile.class); Integer deviceId = 100; Pair pair = new Pair<>(profile, deviceId); - when(networkOrchestrationService.importNic(nullable(String.class), nullable(Integer.class), nullable(Network.class), nullable(Boolean.class), nullable(VirtualMachine.class), nullable(Network.IpAddresses.class), nullable(DataCenter.class), Mockito.anyBoolean())).thenReturn(pair); + when(networkOrchestrationService.importNic(nullable(String.class), nullable(Integer.class), nullable(Network.class), nullable(Boolean.class), nullable(VirtualMachine.class), nullable(Network.IpAddresses.class), nullable(DataCenter.class), Mockito.anyBoolean(), Mockito.anyBoolean())).thenReturn(pair); when(volumeDao.findByInstance(Mockito.anyLong())).thenReturn(volumes); List userVmResponses = new ArrayList<>(); UserVmResponse userVmResponse = new UserVmResponse(); diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index f0a6ad3c79b9..7679023bbbb6 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1133,6 +1133,7 @@ "label.forcks": "For CKS", "label.forbidden": "Forbidden", "label.forced": "Force", +"label.generate.new.mac.if.required": "Generate new MAC if required", "label.force.convert.to.pool": "Force converting to storage pool directly (not using temporary storage for conversion)", "label.force.ms.to.import.vm.files": "Enable to force OVF Download via Management Server. Disable to use KVM Host ovftool (if installed)", "label.force.update.os.type": "Force update OS type", diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue index ffa0d9344335..f7917636fed2 100644 --- a/ui/src/views/tools/ImportUnmanagedInstance.vue +++ b/ui/src/views/tools/ImportUnmanagedInstance.vue @@ -352,15 +352,25 @@ {{ $t('message.ip.address.changes.effect.after.vm.restart') }} - + - + + + + + + + @@ -414,14 +424,24 @@ - + - + + + + + + + @@ -673,6 +693,15 @@ export default { isKVMUnmanage () { return this.hypervisor && this.hypervisor === 'kvm' && (this.importsource === 'unmanaged' || this.importsource === 'external') }, + hasSourceNicMacAddresses () { + return !this.isDiskImport && this.resource?.nic?.some(nic => nic.macaddress || nic.mac) + }, + showMacConflictOptions () { + return this.hasSourceNicMacAddresses + }, + showAllowDuplicateMacAddresses () { + return this.showMacConflictOptions && this.apiParams.allowduplicatemacaddresses + }, domainSelectOptions () { var domains = this.options.domains.map((domain) => { return { @@ -789,6 +818,7 @@ export default { usevddk: false, migrateallowed: this.switches.migrateAllowed, forced: this.switches.forced, + allowduplicatemacaddresses: this.switches.allowDuplicateMacAddresses, forcemstoimportvmfiles: this.switches.forceMsToImportVmFiles, forceconverttopool: this.switches.forceConvertToPool, domainid: null, @@ -1163,6 +1193,22 @@ export default { this.showStoragePoolsForConversion = false this.resetStorageOptionsForConversion() }, + onForcedMacConflictChange (val) { + this.switches.forced = val + this.form.forced = val + if (val) { + this.switches.allowDuplicateMacAddresses = false + this.form.allowduplicatemacaddresses = false + } + }, + onAllowDuplicateMacAddressesChange (val) { + this.switches.allowDuplicateMacAddresses = val + this.form.allowduplicatemacaddresses = val + if (val) { + this.switches.forced = false + this.form.forced = false + } + }, onUseVddkChange (val, isUserChange = true) { if (isUserChange) { this.userModifiedVddkSetting = true @@ -1317,7 +1363,13 @@ export default { params.forceconverttopool = values.forceconverttopool } } - var keys = ['hostname', 'domainid', 'projectid', 'account', 'migrateallowed', 'forced', 'osid'] + var keys = ['hostname', 'domainid', 'projectid', 'account', 'migrateallowed', 'osid'] + if (this.showMacConflictOptions) { + keys.push('forced') + } + if (this.showAllowDuplicateMacAddresses) { + keys.push('allowduplicatemacaddresses') + } if (this.templateType !== 'auto') { keys.push('templateid') } @@ -1434,6 +1486,8 @@ export default { this.form.usevddk = false this.form.forceconverttopool = false this.form.forcemstoimportvmfiles = false + this.form.forced = false + this.form.allowduplicatemacaddresses = false this.userModifiedVddkSetting = false this.resetStorageOptionsForConversion() },