Skip to content

Commit e35e8d7

Browse files
Allow preserving duplicate MACs during VM import
1 parent f6efda5 commit e35e8d7

9 files changed

Lines changed: 106 additions & 32 deletions

File tree

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ public class ApiConstants {
250250
public static final String FILESYSTEM = "filesystem";
251251
public static final String FIRSTNAME = "firstname";
252252
public static final String FORCED = "forced";
253+
public static final String ALLOW_DUPLICATE_MAC_ADDRESSES = "allowduplicatemacaddresses";
253254
public static final String FORCED_DESTROY_LOCAL_STORAGE = "forcedestroylocalstorage";
254255
public static final String FORCE_CONVERT_TO_POOL = "forceconverttopool";
255256

api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,15 @@ public class ImportUnmanagedInstanceCmd extends BaseAsyncCmd {
152152

153153
@Parameter(name = ApiConstants.FORCED,
154154
type = CommandType.BOOLEAN,
155-
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")
155+
description = "Instance is imported even if some NIC MAC addresses already exist. If a MAC address exists, a new MAC address is generated")
156156
private Boolean forced;
157157

158+
@Parameter(name = ApiConstants.ALLOW_DUPLICATE_MAC_ADDRESSES,
159+
type = CommandType.BOOLEAN,
160+
since = "4.23.0",
161+
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")
162+
private Boolean allowDuplicateMacAddresses;
163+
158164
/////////////////////////////////////////////////////
159165
/////////////////// Accessors ///////////////////////
160166
/////////////////////////////////////////////////////
@@ -278,6 +284,10 @@ public boolean isForced() {
278284
return BooleanUtils.isTrue(forced);
279285
}
280286

287+
public boolean isAllowDuplicateMacAddresses() {
288+
return BooleanUtils.isTrue(allowDuplicateMacAddresses);
289+
}
290+
281291
/////////////////////////////////////////////////////
282292
/////////////// API Implementation///////////////////
283293
/////////////////////////////////////////////////////

engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ void implementNetworkElementsAndResources(DeployDestination dest, ReservationCon
372372
*/
373373
void cleanupNicDhcpDnsEntry(Network network, VirtualMachineProfile vmProfile, NicProfile nicProfile);
374374

375-
Pair<NicProfile, Integer> 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;
375+
Pair<NicProfile, Integer> 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;
376376

377377
void unmanageNics(VirtualMachineProfile vm);
378378

engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4770,7 +4770,7 @@ public NicVO savePlaceholderNic(final Network network, final String ip4Address,
47704770

47714771
@DB
47724772
@Override
4773-
public Pair<NicProfile, Integer> 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)
4773+
public Pair<NicProfile, Integer> 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)
47744774
throws ConcurrentOperationException, InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException {
47754775
logger.debug("Allocating NIC for Instance {} in Network {} during import", vm, network);
47764776
String selectedIp = null;
@@ -4796,7 +4796,7 @@ public NicVO doInTransaction(TransactionStatus status) {
47964796
throw new CloudRuntimeException("Invalid mac address: " + macAddressToPersist);
47974797
}
47984798
NicVO existingNic = _nicDao.findByNetworkIdAndMacAddress(network.getId(), macAddressToPersist);
4799-
if (existingNic != null) {
4799+
if (existingNic != null && !allowDuplicateMacAddress) {
48004800
macAddressToPersist = generateNewMacAddressIfForced(network, macAddressToPersist, forced);
48014801
}
48024802
NicVO vo = new NicVO(network.getGuruName(), vm.getId(), network.getId(), vm.getType());

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

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -847,9 +847,10 @@ private Pair<DiskProfile, StoragePool> importDisk(UnmanagedInstanceTO.Disk disk,
847847
return new Pair<DiskProfile, StoragePool>(profile, storagePool);
848848
}
849849

850-
private NicProfile importNic(UnmanagedInstanceTO.Nic nic, VirtualMachine vm, Network network, Network.IpAddresses ipAddresses, int deviceId, boolean isDefaultNic, boolean forced) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException {
850+
private NicProfile importNic(UnmanagedInstanceTO.Nic nic, VirtualMachine vm, Network network, Network.IpAddresses ipAddresses, int deviceId, boolean isDefaultNic,
851+
boolean forced, boolean allowDuplicateMacAddress) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException {
851852
DataCenterVO dataCenterVO = dataCenterDao.findById(network.getDataCenterId());
852-
Pair<NicProfile, Integer> result = networkOrchestrationService.importNic(nic.getMacAddress(), deviceId, network, isDefaultNic, vm, ipAddresses, dataCenterVO, forced);
853+
Pair<NicProfile, Integer> result = networkOrchestrationService.importNic(nic.getMacAddress(), deviceId, network, isDefaultNic, vm, ipAddresses, dataCenterVO, forced, allowDuplicateMacAddress);
853854
if (result == null) {
854855
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("NIC ID: %s import failed", nic.getNicId()));
855856
}
@@ -1056,7 +1057,8 @@ private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedI
10561057
final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId,
10571058
final ServiceOfferingVO serviceOffering, final Map<String, Long> dataDiskOfferingMap,
10581059
final Map<String, Long> nicNetworkMap, final Map<String, Network.IpAddresses> callerNicIpAddressMap, final Long guestOsId,
1059-
final Map<String, String> details, final boolean migrateAllowed, final boolean forced, final boolean isImportUnmanagedFromSameHypervisor) {
1060+
final Map<String, String> details, final boolean migrateAllowed, final boolean forced, final boolean allowDuplicateMacAddress,
1061+
final boolean isImportUnmanagedFromSameHypervisor) {
10601062
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].",
10611063
unmanagedInstance, displayName, zone, cluster, host, template, serviceOffering, dataDiskOfferingMap, nicNetworkMap, details));
10621064
UserVm userVm = null;
@@ -1186,7 +1188,7 @@ private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedI
11861188
for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) {
11871189
Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId()));
11881190
Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId());
1189-
importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex == 0, forced);
1191+
importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex == 0, forced, allowDuplicateMacAddress);
11901192
nicIndex++;
11911193
}
11921194
} catch (Exception e) {
@@ -1319,6 +1321,10 @@ private UserVmResponse baseImportInstance(ImportUnmanagedInstanceCmd cmd) {
13191321
final Map<String, Long> dataDiskOfferingMap = cmd.getDataDiskToDiskOfferingList();
13201322
final Map<String, String> details = cmd.getDetails();
13211323
final boolean forced = cmd.isForced();
1324+
final boolean allowDuplicateMacAddress = cmd.isAllowDuplicateMacAddresses();
1325+
if (forced && allowDuplicateMacAddress) {
1326+
throw new InvalidParameterValueException(String.format("%s and %s are mutually exclusive", ApiConstants.FORCED, ApiConstants.ALLOW_DUPLICATE_MAC_ADDRESSES));
1327+
}
13221328
List<HostVO> hosts = resourceManager.listHostsInClusterByStatus(clusterId, Status.Up);
13231329
UserVm userVm = null;
13241330
List<String> additionalNameFilters = getAdditionalNameFilters(cluster);
@@ -1342,15 +1348,15 @@ private UserVmResponse baseImportInstance(ImportUnmanagedInstanceCmd cmd) {
13421348
template, instanceName, displayName, hostName, caller, owner, userId,
13431349
serviceOffering, dataDiskOfferingMap,
13441350
nicNetworkMap, nicIpAddressMap,
1345-
details, importVmCmd, forced);
1351+
details, importVmCmd, forced, allowDuplicateMacAddress);
13461352
}
13471353
} else {
13481354
if (List.of(Hypervisor.HypervisorType.VMware, Hypervisor.HypervisorType.KVM).contains(cluster.getHypervisorType())) {
13491355
userVm = importUnmanagedInstanceFromHypervisor(zone, cluster, hosts, additionalNameFilters,
13501356
template, instanceName, displayName, hostName, caller, owner, userId,
13511357
serviceOffering, dataDiskOfferingMap,
13521358
nicNetworkMap, nicIpAddressMap,
1353-
details, cmd.getMigrateAllowed(), managedVms, forced);
1359+
details, cmd.getMigrateAllowed(), managedVms, forced, allowDuplicateMacAddress);
13541360
}
13551361
}
13561362

@@ -1497,7 +1503,8 @@ private UserVm importUnmanagedInstanceFromHypervisor(DataCenter zone, Cluster cl
14971503
String hostName, Account caller, Account owner, long userId,
14981504
ServiceOfferingVO serviceOffering, Map<String, Long> dataDiskOfferingMap,
14991505
Map<String, Long> nicNetworkMap, Map<String, Network.IpAddresses> nicIpAddressMap,
1500-
Map<String, String> details, Boolean migrateAllowed, List<String> managedVms, boolean forced) throws ResourceAllocationException {
1506+
Map<String, String> details, Boolean migrateAllowed, List<String> managedVms, boolean forced,
1507+
boolean allowDuplicateMacAddress) throws ResourceAllocationException {
15011508
UserVm userVm = null;
15021509
for (HostVO host : hosts) {
15031510
HashMap<String, UnmanagedInstanceTO> unmanagedInstances = getUnmanagedInstancesForHost(host, instanceName, managedVms);
@@ -1549,7 +1556,7 @@ private UserVm importUnmanagedInstanceFromHypervisor(DataCenter zone, Cluster cl
15491556
template, displayName, hostName, CallContext.current().getCallingAccount(), owner, userId,
15501557
serviceOffering, dataDiskOfferingMap,
15511558
nicNetworkMap, nicIpAddressMap, null,
1552-
details, migrateAllowed, forced, true);
1559+
details, migrateAllowed, forced, allowDuplicateMacAddress, true);
15531560
} finally {
15541561
ReservationHelper.closeAll(reservations);
15551562
}
@@ -1648,7 +1655,7 @@ protected UserVm importUnmanagedInstanceFromVmwareToKvm(DataCenter zone, Cluster
16481655
Account caller, Account owner, long userId,
16491656
ServiceOfferingVO serviceOffering, Map<String, Long> dataDiskOfferingMap,
16501657
Map<String, Long> nicNetworkMap, Map<String, Network.IpAddresses> nicIpAddressMap,
1651-
Map<String, String> details, ImportVmCmd cmd, boolean forced) throws ResourceAllocationException {
1658+
Map<String, String> details, ImportVmCmd cmd, boolean forced, boolean allowDuplicateMacAddress) throws ResourceAllocationException {
16521659
Long existingVcenterId = cmd.getExistingVcenterId();
16531660
String vcenter = cmd.getVcenter();
16541661
String datacenterName = cmd.getDatacenterName();
@@ -1741,7 +1748,7 @@ protected UserVm importUnmanagedInstanceFromVmwareToKvm(DataCenter zone, Cluster
17411748
checkConversionSupportOnHost(convertHost, sourceVMName, true, useVddk, details);
17421749
}
17431750

1744-
checkNetworkingBeforeConvertingVmwareInstance(zone, owner, displayName, hostName, sourceVMwareInstance, nicNetworkMap, nicIpAddressMap, forced);
1751+
checkNetworkingBeforeConvertingVmwareInstance(zone, owner, displayName, hostName, sourceVMwareInstance, nicNetworkMap, nicIpAddressMap, forced, allowDuplicateMacAddress);
17451752
UnmanagedInstanceTO convertedInstance;
17461753
if (!useVddk && (forceMsToImportVmFiles || !isOvfExportSupported)) {
17471754
// Uses MS for OVF export to temporary conversion location
@@ -1769,7 +1776,7 @@ protected UserVm importUnmanagedInstanceFromVmwareToKvm(DataCenter zone, Cluster
17691776
template, displayName, hostName, caller, owner, userId,
17701777
serviceOffering, dataDiskOfferingMap,
17711778
nicNetworkMap, nicIpAddressMap, guestOsId,
1772-
details, false, forced, false);
1779+
details, false, forced, allowDuplicateMacAddress, false);
17731780
long timeElapsedInSecs = (System.currentTimeMillis() - importStartTime) / 1000;
17741781
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",
17751782
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
18591866
String hostName, UnmanagedInstanceTO sourceVMwareInstance,
18601867
Map<String, Long> nicNetworkMap,
18611868
Map<String, Network.IpAddresses> nicIpAddressMap,
1862-
boolean forced) {
1869+
boolean forced, boolean allowDuplicateMacAddress) {
18631870
List<UnmanagedInstanceTO.Nic> nics = sourceVMwareInstance.getNics();
18641871
List<Long> networkIds = new ArrayList<>(nicNetworkMap.values());
18651872
if (nics.size() != networkIds.size()) {
@@ -1882,18 +1889,19 @@ private void checkNetworkingBeforeConvertingVmwareInstance(DataCenter zone, Acco
18821889
ipAddresses = nicIpAddressMap.get(nic.getNicId());
18831890
}
18841891
boolean autoImport = ipAddresses != null && ipAddresses.getIp4Address() != null && ipAddresses.getIp4Address().equalsIgnoreCase("auto");
1885-
checkUnmanagedNicAndNetworkMacAddressForImport(network, nic, forced);
1892+
checkUnmanagedNicAndNetworkMacAddressForImport(network, nic, forced, allowDuplicateMacAddress);
18861893
checkUnmanagedNicAndNetworkForImport(displayName, nic, network, zone, owner, autoImport, Hypervisor.HypervisorType.KVM);
18871894
checkUnmanagedNicAndNetworkHostnameForImport(displayName, nic, network, hostName);
18881895
checkUnmanagedNicIpAndNetworkForImport(displayName, nic, network, ipAddresses);
18891896
}
18901897
}
18911898

1892-
private void checkUnmanagedNicAndNetworkMacAddressForImport(NetworkVO network, UnmanagedInstanceTO.Nic nic, boolean forced) {
1899+
private void checkUnmanagedNicAndNetworkMacAddressForImport(NetworkVO network, UnmanagedInstanceTO.Nic nic, boolean forced, boolean allowDuplicateMacAddress) {
18931900
NicVO existingNic = nicDao.findByNetworkIdAndMacAddress(network.getId(), nic.getMacAddress());
1894-
if (existingNic != null && !forced) {
1895-
String err = String.format("NIC %s with MAC address %s already exists on network %s and forced flag is disabled. " +
1896-
"Retry with forced flag enabled if a new MAC address to be generated.", nic, nic.getMacAddress(), network);
1901+
if (existingNic != null && !forced && !allowDuplicateMacAddress) {
1902+
String err = String.format("NIC %s with MAC address %s already exists on network %s. " +
1903+
"Retry with %s=true to generate a new MAC address or %s=true to preserve the duplicate MAC address.",
1904+
nic, nic.getMacAddress(), network, ApiConstants.FORCED, ApiConstants.ALLOW_DUPLICATE_MAC_ADDRESSES);
18971905
logger.error(err);
18981906
throw new CloudRuntimeException(err);
18991907
}
@@ -2809,7 +2817,7 @@ private UserVm importExternalKvmVirtualMachine(final UnmanagedInstanceTO unmanag
28092817
for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) {
28102818
Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId()));
28112819
Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId());
2812-
importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex==0, true);
2820+
importNic(nic, userVm, network, ipAddresses, nicIndex, nicIndex==0, true, false);
28132821
nicIndex++;
28142822
}
28152823
} catch (Exception e) {
@@ -2980,7 +2988,7 @@ private UserVm importKvmVirtualMachineFromDisk(final ImportSource importSource,
29802988
cleanupFailedImportVM(userVm);
29812989
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import volumes while importing vm: %s. %s", instanceName, StringUtils.defaultString(e.getMessage())));
29822990
}
2983-
networkOrchestrationService.importNic(macAddress, 0, network, true, userVm, requestedIpPair, zone, true);
2991+
networkOrchestrationService.importNic(macAddress, 0, network, true, userVm, requestedIpPair, zone, true, false);
29842992
publishVMUsageUpdateResourceCount(userVm, dummyOffering, template);
29852993
return userVm;
29862994

server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1071,7 +1071,7 @@ public AcquirePodIpCmdResponse allocatePodIp(Account account, String zoneId, Str
10711071
}
10721072

10731073
@Override
1074-
public Pair<NicProfile, Integer> importNic(String macAddress, int deviceId, Network network, Boolean isDefaultNic, VirtualMachine vm, IpAddresses ipAddresses, DataCenter dataCenter, boolean forced) {
1074+
public Pair<NicProfile, Integer> importNic(String macAddress, int deviceId, Network network, Boolean isDefaultNic, VirtualMachine vm, IpAddresses ipAddresses, DataCenter dataCenter, boolean forced, boolean allowDuplicateMacAddress) {
10751075
return null;
10761076
}
10771077

0 commit comments

Comments
 (0)