Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,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";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ///////////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -278,6 +284,14 @@ public boolean isForced() {
return BooleanUtils.isTrue(forced);
}

public Boolean getForced() {
return forced;
}

public boolean isAllowDuplicateMacAddresses() {
return BooleanUtils.isTrue(allowDuplicateMacAddresses);
}

/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ void implementNetworkElementsAndResources(DeployDestination dest, ReservationCon
*/
void cleanupNicDhcpDnsEntry(Network network, VirtualMachineProfile vmProfile, NicProfile nicProfile);

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;
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;

void unmanageNics(VirtualMachineProfile vm);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4881,7 +4881,7 @@ public NicVO savePlaceholderNic(final Network network, final String ip4Address,

@DB
@Override
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)
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)
throws ConcurrentOperationException, InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException {
logger.debug("Allocating NIC for Instance {} in Network {} during import", vm, network);
String selectedIp = null;
Expand All @@ -4907,7 +4907,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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
import com.cloud.utils.db.EntityManager;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.Ip;
import com.cloud.vm.DomainRouterVO;
Expand Down Expand Up @@ -923,7 +924,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)
Expand All @@ -942,7 +943,24 @@ 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(expected = InsufficientVirtualNetworkCapacityException.class)
public void testImportNicDuplicateMacAllowanceDoesNotBypassIpAllocation() throws Exception {
DataCenter dataCenter = Mockito.mock(DataCenter.class);
VirtualMachine vm = mock(VirtualMachine.class);
Network network = Mockito.mock(Network.class);
Mockito.when(network.getGuestType()).thenReturn(GuestType.Isolated);
Mockito.when(network.getNetworkOfferingId()).thenReturn(networkOfferingId);
long dataCenterId = 1L;
Mockito.when(network.getDataCenterId()).thenReturn(dataCenterId);
Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class);
Mockito.when(ipAddresses.getIp4Address()).thenReturn("10.1.10.10");
Mockito.when(testOrchestrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses)).thenReturn(null);
Mockito.when(testOrchestrator._networkModel.listNetworkOfferingServices(networkOfferingId)).thenReturn(Arrays.asList(Service.Dns, Service.Dhcp));

testOrchestrator.importNic("02:01:01:82:00:01", 0, network, true, vm, ipAddresses, dataCenter, false, true);
}

@Test
Expand All @@ -969,7 +987,7 @@ public void testImportNicNoIP4Address() throws Exception {
Mockito.when(testOrchestrator._networkModel.getNetworkTag(hypervisorType, network)).thenReturn("testtag");
try (MockedStatic<Transaction> transactionMocked = Mockito.mockStatic(Transaction.class)) {
transactionMocked.when(() -> Transaction.execute(any(TransactionCallback.class))).thenReturn(nic);
Pair<NicProfile, Integer> nicProfileIntegerPair = testOrchestrator.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false);
Pair<NicProfile, Integer> 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);
Expand Down Expand Up @@ -1007,7 +1025,7 @@ public void testImportNicWithIP4Address() throws Exception {
Mockito.when(testOrchestrator._networkModel.getNetworkTag(hypervisorType, network)).thenReturn("testtag");
try (MockedStatic<Transaction> transactionMocked = Mockito.mockStatic(Transaction.class)) {
transactionMocked.when(() -> Transaction.execute(any(TransactionCallback.class))).thenReturn(nic);
Pair<NicProfile, Integer> nicProfileIntegerPair = testOrchestrator.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false);
Pair<NicProfile, Integer> 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);
Expand All @@ -1021,6 +1039,70 @@ public void testImportNicWithIP4Address() throws Exception {
}
}

@Test
public void testImportNicReplacesConflictingMacWhenForced() throws Exception {
String sourceMacAddress = "02:01:01:82:00:01";
String replacementMacAddress = "02:01:01:82:00:02";
when(testOrchestrator._networkModel.getNextAvailableMacAddressInNetwork(1L)).thenReturn(replacementMacAddress);

NicProfile nicProfile = importNicWithMacPolicy(sourceMacAddress, true, true, false);

assertEquals(replacementMacAddress, nicProfile.getMacAddress());
}

@Test(expected = CloudRuntimeException.class)
public void testImportNicRejectsConflictingMacWhenNotForced() throws Exception {
importNicWithMacPolicy("02:01:01:82:00:01", true, false, false);
}

@Test
public void testImportNicPreservesAllowedDuplicateMac() throws Exception {
String sourceMacAddress = "02:01:01:82:00:01";

NicProfile nicProfile = importNicWithMacPolicy(sourceMacAddress, true, false, true);

assertEquals(sourceMacAddress, nicProfile.getMacAddress());
verify(testOrchestrator._networkModel, never()).getNextAvailableMacAddressInNetwork(1L);
}

@Test
public void testImportNicPreservesUnusedSourceMac() throws Exception {
String sourceMacAddress = "02:01:01:82:00:01";

NicProfile nicProfile = importNicWithMacPolicy(sourceMacAddress, false, true, false);

assertEquals(sourceMacAddress, nicProfile.getMacAddress());
verify(testOrchestrator._networkModel, never()).getNextAvailableMacAddressInNetwork(1L);
}

@SuppressWarnings("unchecked")
private NicProfile importNicWithMacPolicy(String macAddress, boolean macAddressExists, boolean forced,
boolean allowDuplicateMacAddress) throws Exception {
long networkId = 1L;
long vmId = 1L;
DataCenter dataCenter = mock(DataCenter.class);
Network network = mock(Network.class);
VirtualMachine vm = mock(VirtualMachine.class);
when(network.getId()).thenReturn(networkId);
when(network.getGuestType()).thenReturn(GuestType.L2);
when(vm.getId()).thenReturn(vmId);
when(vm.getType()).thenReturn(Type.User);
when(vm.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
if (macAddressExists) {
when(testOrchestrator._nicDao.findByNetworkIdAndMacAddress(networkId, macAddress)).thenReturn(mock(NicVO.class));
}
when(testOrchestrator._nicDao.persist(any(NicVO.class))).thenAnswer(invocation -> invocation.getArgument(0));

try (MockedStatic<Transaction> transactionMocked = Mockito.mockStatic(Transaction.class)) {
transactionMocked.when(() -> Transaction.execute(any(TransactionCallback.class))).thenAnswer(invocation -> {
TransactionCallback<NicVO> callback = invocation.getArgument(0);
return callback.doInTransaction(mock(TransactionStatus.class));
});
return testOrchestrator.importNic(macAddress, 0, network, true, vm, null, dataCenter,
forced, allowDuplicateMacAddress).first();
}
}

// -----------------------------------------------------------------------
// Tests for getNetworkElementsIncludingExtensions
// -----------------------------------------------------------------------
Expand Down
Loading
Loading