Skip to content

Commit 0035c61

Browse files
engine: retry NIC IP allocation instead of NPE when the allocation race is lost
checkForRaceAndAllocateNic dereferenced the requested NicProfile when a concurrent deploy had already taken the IP (persistNicAfterRaceCheck returned null). On the common path the user requests no explicit IP, so requested is null and the loser threw a NullPointerException instead of nulling the IP and retrying. This defeats the ipv4AllocationRaceCheck retry for exactly the case it exists for (bulk/autoscale/CKS deploys onto one network). Null-guard the requested profile so the allocation is retried.
1 parent 2cd8c5e commit 0035c61

2 files changed

Lines changed: 102 additions & 4 deletions

File tree

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,7 +1058,7 @@ public void saveExtraDhcpOptions(final String networkUuid, final Long nicId, fin
10581058
}
10591059
}
10601060

1061-
private NicVO persistNicAfterRaceCheck(final NicVO nic, final Long networkId, final NicProfile profile, int deviceId) {
1061+
protected NicVO persistNicAfterRaceCheck(final NicVO nic, final Long networkId, final NicProfile profile, int deviceId) {
10621062
return Transaction.execute(new TransactionCallback<NicVO>() {
10631063
@Override
10641064
public NicVO doInTransaction(TransactionStatus status) {
@@ -1074,7 +1074,7 @@ public NicVO doInTransaction(TransactionStatus status) {
10741074
});
10751075
}
10761076

1077-
private NicVO checkForRaceAndAllocateNic(final NicProfile requested, final Network network, final Boolean isDefaultNic, int deviceId, final VirtualMachineProfile vm)
1077+
protected NicVO checkForRaceAndAllocateNic(final NicProfile requested, final Network network, final Boolean isDefaultNic, int deviceId, final VirtualMachineProfile vm)
10781078
throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException {
10791079
final NetworkVO ntwkVO = _networksDao.findById(network.getId());
10801080
logger.debug("Allocating NIC for Instance {} in Network {} with requested profile {}", vm.getVirtualMachine(), network, requested);
@@ -1120,9 +1120,9 @@ private NicVO checkForRaceAndAllocateNic(final NicProfile requested, final Netwo
11201120
}
11211121

11221122
if (vo == null) {
1123-
if (requested.getRequestedIPv4() != null) {
1123+
if (requested != null && requested.getRequestedIPv4() != null) {
11241124
throw new InsufficientVirtualNetworkCapacityException("Unable to acquire requested Guest IP address " + requested.getRequestedIPv4() + " for network " + network, DataCenter.class, dcVo.getId());
1125-
} else {
1125+
} else if (requested != null) {
11261126
requested.setIPv4Address(null);
11271127
}
11281128
retryIpAllocation = true;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.engine.orchestration;
18+
19+
import static org.mockito.ArgumentMatchers.any;
20+
import static org.mockito.ArgumentMatchers.anyInt;
21+
import static org.mockito.ArgumentMatchers.eq;
22+
import static org.mockito.ArgumentMatchers.isNull;
23+
24+
import java.util.Collections;
25+
26+
import org.junit.Assert;
27+
import org.junit.Test;
28+
import org.junit.runner.RunWith;
29+
import org.mockito.InjectMocks;
30+
import org.mockito.Mock;
31+
import org.mockito.Mockito;
32+
import org.mockito.Spy;
33+
import org.mockito.junit.MockitoJUnitRunner;
34+
35+
import com.cloud.dc.DataCenter.NetworkType;
36+
import com.cloud.dc.DataCenterVO;
37+
import com.cloud.dc.dao.DataCenterDao;
38+
import com.cloud.network.Network;
39+
import com.cloud.network.Networks.TrafficType;
40+
import com.cloud.network.dao.NetworkDao;
41+
import com.cloud.network.dao.NetworkVO;
42+
import com.cloud.network.guru.NetworkGuru;
43+
import com.cloud.vm.NicProfile;
44+
import com.cloud.vm.NicVO;
45+
import com.cloud.vm.VirtualMachine.Type;
46+
import com.cloud.vm.VirtualMachineProfile;
47+
48+
@RunWith(MockitoJUnitRunner.Silent.class)
49+
public class NetworkOrchestratorNicRaceTest {
50+
51+
@Mock
52+
NetworkDao _networksDao;
53+
@Mock
54+
DataCenterDao _dcDao;
55+
56+
@Spy
57+
@InjectMocks
58+
NetworkOrchestrator orchestrator = new NetworkOrchestrator();
59+
60+
@Test
61+
public void checkForRaceAndAllocateNicRetriesInsteadOfNpeWhenNoIpRequested() throws Exception {
62+
Network network = Mockito.mock(Network.class);
63+
Mockito.when(network.getId()).thenReturn(1L);
64+
Mockito.when(network.getDataCenterId()).thenReturn(1L);
65+
Mockito.when(network.getTrafficType()).thenReturn(TrafficType.Guest);
66+
67+
NetworkVO ntwkVO = Mockito.mock(NetworkVO.class);
68+
Mockito.when(ntwkVO.getGuruName()).thenReturn("TestGuru");
69+
Mockito.when(_networksDao.findById(1L)).thenReturn(ntwkVO);
70+
71+
DataCenterVO dcVo = Mockito.mock(DataCenterVO.class);
72+
Mockito.when(dcVo.getNetworkType()).thenReturn(NetworkType.Advanced);
73+
Mockito.when(_dcDao.findById(1L)).thenReturn(dcVo);
74+
75+
VirtualMachineProfile vm = Mockito.mock(VirtualMachineProfile.class);
76+
Mockito.when(vm.getId()).thenReturn(1L);
77+
Mockito.when(vm.getType()).thenReturn(Type.User);
78+
79+
NicProfile profile = Mockito.mock(NicProfile.class);
80+
Mockito.when(profile.getIpv4AllocationRaceCheck()).thenReturn(true);
81+
82+
NetworkGuru guru = Mockito.mock(NetworkGuru.class);
83+
Mockito.when(guru.getName()).thenReturn("TestGuru");
84+
Mockito.when(guru.allocate(eq(network), isNull(), eq(vm))).thenReturn(profile);
85+
orchestrator.setNetworkGurus(Collections.singletonList(guru));
86+
87+
NicVO persisted = Mockito.mock(NicVO.class);
88+
// First attempt loses the IP-allocation race (persist returns null); the retry wins (non-null).
89+
Mockito.doReturn(null).doReturn(persisted)
90+
.when(orchestrator).persistNicAfterRaceCheck(any(NicVO.class), eq(1L), eq(profile), anyInt());
91+
92+
// requested == null is the common "no explicit IP" deploy path. Before the fix, losing the race
93+
// dereferenced the null requested profile and threw NPE instead of retrying the allocation.
94+
NicVO result = orchestrator.checkForRaceAndAllocateNic(null, network, null, 0, vm);
95+
96+
Assert.assertSame("losing the race should retry and return the persisted NIC, not NPE", persisted, result);
97+
}
98+
}

0 commit comments

Comments
 (0)