Skip to content

Commit 30ed411

Browse files
author
Daan Hoogland
committed
Merge branch '4.20' into 4.22
2 parents d31b5ff + e742aad commit 30ed411

7 files changed

Lines changed: 229 additions & 21 deletions

File tree

‎engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDao.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public interface SnapshotDao extends GenericDao<SnapshotVO, Long>, StateDao<Snap
5353

5454
List<SnapshotVO> listByStatusNotIn(long volumeId, Snapshot.State... status);
5555

56+
SnapshotVO findByVolumeIdAndNameNotInStatus(long volumeId, String name, Snapshot.State... status);
57+
5658
/**
5759
* Retrieves a list of snapshots filtered by ids.
5860
* @param ids Snapshot ids.

‎engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDaoImpl.java‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public class SnapshotDaoImpl extends GenericDaoBase<SnapshotVO, Long> implements
7070
private SearchBuilder<SnapshotVO> InstanceIdSearch;
7171
private SearchBuilder<SnapshotVO> StatusSearch;
7272
private SearchBuilder<SnapshotVO> notInStatusSearch;
73+
private SearchBuilder<SnapshotVO> volumeIdNameNotInStatusSearch;
7374
private GenericSearchBuilder<SnapshotVO, Long> CountSnapshotsByAccount;
7475

7576
private SearchBuilder<SnapshotVO> volumeIdAndTypeNotInSearch;
@@ -166,6 +167,12 @@ protected void init() {
166167
notInStatusSearch.and("status", notInStatusSearch.entity().getState(), SearchCriteria.Op.NOTIN);
167168
notInStatusSearch.done();
168169

170+
volumeIdNameNotInStatusSearch = createSearchBuilder();
171+
volumeIdNameNotInStatusSearch.and("volumeId", volumeIdNameNotInStatusSearch.entity().getVolumeId(), SearchCriteria.Op.EQ);
172+
volumeIdNameNotInStatusSearch.and("name", volumeIdNameNotInStatusSearch.entity().getName(), SearchCriteria.Op.EQ);
173+
volumeIdNameNotInStatusSearch.and("status", volumeIdNameNotInStatusSearch.entity().getState(), SearchCriteria.Op.NOTIN);
174+
volumeIdNameNotInStatusSearch.done();
175+
169176
CountSnapshotsByAccount = createSearchBuilder(Long.class);
170177
CountSnapshotsByAccount.select(null, Func.COUNT, null);
171178
CountSnapshotsByAccount.and("account", CountSnapshotsByAccount.entity().getAccountId(), SearchCriteria.Op.EQ);
@@ -310,6 +317,15 @@ public List<SnapshotVO> listByStatusNotIn(long volumeId, Snapshot.State... statu
310317
return listBy(sc, null);
311318
}
312319

320+
@Override
321+
public SnapshotVO findByVolumeIdAndNameNotInStatus(long volumeId, String name, Snapshot.State... status) {
322+
SearchCriteria<SnapshotVO> sc = volumeIdNameNotInStatusSearch.create();
323+
sc.setParameters("volumeId", volumeId);
324+
sc.setParameters("name", name);
325+
sc.setParameters("status", (Object[]) status);
326+
return findOneBy(sc);
327+
}
328+
313329
@Override
314330
public List<SnapshotVO> searchByVolumes(List<Long> volumeIds) {
315331
if (CollectionUtils.isEmpty(volumeIds)) {
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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 com.cloud.storage.dao;
18+
19+
import java.lang.reflect.Field;
20+
21+
import org.junit.Assert;
22+
import org.junit.Before;
23+
import org.junit.Test;
24+
import org.junit.runner.RunWith;
25+
import org.mockito.Mock;
26+
import org.mockito.Mockito;
27+
import org.mockito.Spy;
28+
import org.mockito.junit.MockitoJUnitRunner;
29+
30+
import com.cloud.storage.Snapshot;
31+
import com.cloud.storage.SnapshotVO;
32+
import com.cloud.utils.db.SearchBuilder;
33+
import com.cloud.utils.db.SearchCriteria;
34+
35+
@RunWith(MockitoJUnitRunner.class)
36+
public class SnapshotDaoImplTest {
37+
38+
@Mock
39+
SearchBuilder<SnapshotVO> volumeIdNameNotInStatusSearchMock;
40+
41+
@Mock
42+
SearchCriteria<SnapshotVO> searchCriteriaMock;
43+
44+
@Spy
45+
SnapshotDaoImpl snapshotDao = new SnapshotDaoImpl();
46+
47+
@Before
48+
public void setUp() throws Exception {
49+
// init() wires up several search builders that join other DAOs, which are not available in a
50+
// unit test; inject only the search builder this method uses and stub it to return a criteria.
51+
Field searchField = SnapshotDaoImpl.class.getDeclaredField("volumeIdNameNotInStatusSearch");
52+
searchField.setAccessible(true);
53+
searchField.set(snapshotDao, volumeIdNameNotInStatusSearchMock);
54+
Mockito.lenient().doReturn(searchCriteriaMock).when(volumeIdNameNotInStatusSearchMock).create();
55+
}
56+
57+
@Test
58+
public void testFindByVolumeIdAndNameNotInStatusReturnsTheMatch() {
59+
SnapshotVO expected = Mockito.mock(SnapshotVO.class);
60+
Mockito.doReturn(expected).when(snapshotDao).findOneBy(Mockito.any(SearchCriteria.class));
61+
62+
SnapshotVO result = snapshotDao.findByVolumeIdAndNameNotInStatus(5L, "snap-1", Snapshot.State.Destroyed);
63+
64+
Assert.assertSame(expected, result);
65+
Mockito.verify(searchCriteriaMock).setParameters("volumeId", 5L);
66+
Mockito.verify(searchCriteriaMock).setParameters("name", "snap-1");
67+
Mockito.verify(snapshotDao).findOneBy(searchCriteriaMock);
68+
}
69+
70+
@Test
71+
public void testFindByVolumeIdAndNameNotInStatusReturnsNullWhenNoMatch() {
72+
Mockito.doReturn(null).when(snapshotDao).findOneBy(Mockito.any(SearchCriteria.class));
73+
74+
SnapshotVO result = snapshotDao.findByVolumeIdAndNameNotInStatus(5L, "snap-1");
75+
76+
Assert.assertNull(result);
77+
Mockito.verify(searchCriteriaMock).setParameters("volumeId", 5L);
78+
Mockito.verify(snapshotDao).findOneBy(searchCriteriaMock);
79+
}
80+
}

‎server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java‎

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -959,26 +959,8 @@ public boolean validateHAProxyLBRule(final LoadBalancingRule rule) {
959959

960960
}
961961
} else if (LbStickinessMethod.StickinessMethodType.AppCookieBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName())) {
962-
String length = null; // optional
963-
String holdTime = null; // optional
964-
965-
for (final Pair<String, String> paramKV : paramsList) {
966-
final String key = paramKV.first();
967-
final String value = paramKV.second();
968-
if ("length".equalsIgnoreCase(key)) {
969-
length = value;
970-
}
971-
if ("holdtime".equalsIgnoreCase(key)) {
972-
holdTime = value;
973-
}
974-
}
975-
976-
if (length != null && !containsOnlyNumbers(length, null)) {
977-
throw new InvalidParameterValueException(String.format("Failed LB in validation rule id: %s Cause: length is not a number: %s", rule.getLb(), length));
978-
}
979-
if (holdTime != null && !containsOnlyNumbers(holdTime, timeEndChar) && !containsOnlyNumbers(holdTime, null)) {
980-
throw new InvalidParameterValueException(String.format("Failed LB in validation rule id: %s Cause: holdtime is not in timeformat: %s", rule.getLb(), holdTime));
981-
}
962+
throw new InvalidParameterValueException(String.format("Failed LB in validation rule: %s Cause: AppCookie based stickiness "
963+
+ "(HAProxy 'appsession') is not supported on a Virtual Router, as this directive was removed in HAProxy 1.6", rule.getLb()));
982964
}
983965
}
984966
return true;

‎server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2028,9 +2028,14 @@ public Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName,
20282028
if (vmInstance != null) {
20292029
vmDisplayName = vmInstance.getHostName();
20302030
}
2031+
boolean nameProvided = snapshotName != null;
20312032
if (snapshotName == null)
20322033
snapshotName = vmDisplayName + "_" + volume.getName() + "_" + timeString;
20332034

2035+
if (nameProvided && _snapshotDao.findByVolumeIdAndNameNotInStatus(volumeId, snapshotName, Snapshot.State.Destroyed, Snapshot.State.Error) != null) {
2036+
throw new InvalidParameterValueException(String.format("A snapshot with name [%s] already exists for volume %s.", snapshotName, volume));
2037+
}
2038+
20342039
HypervisorType hypervisorType = HypervisorType.None;
20352040
StoragePoolVO storagePool = _storagePoolDao.findById(volume.getDataStore().getId());
20362041
if (storagePool.getScope() == ScopeType.ZONE) {

‎server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,15 @@
2929
import static org.mockito.Mockito.verify;
3030
import static org.mockito.Mockito.when;
3131

32+
import com.cloud.configuration.Config;
33+
import com.cloud.exception.InvalidParameterValueException;
34+
import com.cloud.network.lb.LoadBalancingRule;
35+
import com.cloud.network.rules.LbStickinessMethod;
36+
import com.cloud.network.rules.LoadBalancer;
37+
import com.cloud.utils.Pair;
38+
import com.cloud.utils.net.Ip;
3239
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
40+
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
3341
import org.apache.cloudstack.network.router.deployment.RouterDeploymentDefinition;
3442
import org.junit.Before;
3543
import org.junit.Test;
@@ -57,6 +65,10 @@
5765
import com.cloud.vm.dao.DomainRouterDao;
5866
import com.cloud.vm.dao.NicDao;
5967

68+
import java.util.ArrayList;
69+
import java.util.Collections;
70+
import java.util.List;
71+
6072

6173
@RunWith(MockitoJUnitRunner.class)
6274
public class NetworkHelperImplTest {
@@ -91,10 +103,15 @@ public class NetworkHelperImplTest {
91103
@Mock
92104
private VMTemplateVO template;
93105

106+
@Mock
107+
private ConfigurationDao configDao;
108+
94109
@Before
95110
public void setUp() {
96111
nwHelper._networkDao = networkDao;
97112
nwHelper._networkModel = networkModel;
113+
nwHelper._configDao = configDao;
114+
lenient().when(configDao.getValue(Config.NetworkLBHaproxyStatsPort.key())).thenReturn("8081");
98115
when(template.getId()).thenReturn(1L);
99116
when(template.isDynamicallyScalable()).thenReturn(true);
100117
when(virtualProvider.getId()).thenReturn(1L);
@@ -268,4 +285,33 @@ public void testUpdateDomainRouter() {
268285
assertEquals(Hypervisor.HypervisorType.KVM, result.getHypervisorType());
269286
assertTrue(result.isDynamicallyScalable());
270287
}
288+
289+
@Test(expected = InvalidParameterValueException.class)
290+
public void testValidateHAProxyLBRuleRejectsAppCookieStickiness() {
291+
LoadBalancer lb = mock(LoadBalancer.class);
292+
when(lb.getSourcePortStart()).thenReturn(80);
293+
294+
List<Pair<String, String>> params = new ArrayList<>();
295+
params.add(new Pair<>("cookie-name", "JSESSIONID"));
296+
LoadBalancingRule.LbStickinessPolicy stickinessPolicy =
297+
new LoadBalancingRule.LbStickinessPolicy(LbStickinessMethod.StickinessMethodType.AppCookieBased.getName(), params);
298+
LoadBalancingRule rule = new LoadBalancingRule(lb, new ArrayList<>(), Collections.singletonList(stickinessPolicy), new ArrayList<>(), mock(Ip.class));
299+
300+
nwHelper.validateHAProxyLBRule(rule);
301+
}
302+
303+
@Test
304+
public void testValidateHAProxyLBRuleAllowsSourceBasedStickiness() {
305+
LoadBalancer lb = mock(LoadBalancer.class);
306+
when(lb.getSourcePortStart()).thenReturn(80);
307+
308+
List<Pair<String, String>> params = new ArrayList<>();
309+
params.add(new Pair<>("tablesize", "200k"));
310+
params.add(new Pair<>("expire", "30m"));
311+
LoadBalancingRule.LbStickinessPolicy stickinessPolicy =
312+
new LoadBalancingRule.LbStickinessPolicy(LbStickinessMethod.StickinessMethodType.SourceBased.getName(), params);
313+
LoadBalancingRule rule = new LoadBalancingRule(lb, new ArrayList<>(), Collections.singletonList(stickinessPolicy), new ArrayList<>(), mock(Ip.class));
314+
315+
assertTrue(nwHelper.validateHAProxyLBRule(rule));
316+
}
271317
}

‎server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java‎

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
import com.cloud.exception.InvalidParameterValueException;
2525
import com.cloud.exception.PermissionDeniedException;
2626
import com.cloud.exception.ResourceUnavailableException;
27+
import com.cloud.hypervisor.Hypervisor.HypervisorType;
2728
import com.cloud.org.Grouping;
2829
import com.cloud.storage.DataStoreRole;
30+
import com.cloud.storage.ScopeType;
2931
import com.cloud.storage.Snapshot;
3032
import com.cloud.storage.SnapshotPolicyVO;
3133
import com.cloud.storage.SnapshotVO;
@@ -42,9 +44,10 @@
4244
import com.cloud.user.User;
4345
import com.cloud.user.dao.AccountDao;
4446
import com.cloud.utils.Pair;
45-
4647
import com.cloud.utils.db.SearchBuilder;
4748
import com.cloud.utils.db.SearchCriteria;
49+
import com.cloud.vm.dao.UserVmDao;
50+
4851
import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd;
4952
import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotPoliciesCmd;
5053
import org.apache.cloudstack.context.CallContext;
@@ -55,9 +58,13 @@
5558
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
5659
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotResult;
5760
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
61+
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
62+
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
5863
import org.apache.cloudstack.framework.async.AsyncCallFuture;
64+
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
5965
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
6066
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
67+
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
6168

6269
import org.junit.Assert;
6370
import org.junit.After;
@@ -106,6 +113,13 @@ public class SnapshotManagerImplTest {
106113
SnapshotScheduler snapshotScheduler;
107114
@Mock
108115
TaggedResourceService taggedResourceService;
116+
@Mock
117+
PrimaryDataStoreDao primaryDataStoreDao;
118+
@Mock
119+
VolumeDataFactory volFactory;
120+
@Mock
121+
UserVmDao userVmDao;
122+
109123
@InjectMocks
110124
SnapshotManagerImpl snapshotManager = new SnapshotManagerImpl();
111125

@@ -123,6 +137,69 @@ public void tearDown() {
123137
CallContext.unregister();
124138
}
125139

140+
@Test
141+
public void testAllocSnapshotRejectsDuplicateNameForVolume() {
142+
long volumeId = 1L;
143+
CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class));
144+
try {
145+
VolumeInfo volume = Mockito.mock(VolumeInfo.class);
146+
Mockito.when(volFactory.getVolume(volumeId)).thenReturn(volume);
147+
DataStore dataStore = Mockito.mock(DataStore.class);
148+
Mockito.when(volume.getDataStore()).thenReturn(dataStore);
149+
Mockito.when(dataStore.getId()).thenReturn(10L);
150+
StoragePoolVO pool = Mockito.mock(StoragePoolVO.class);
151+
Mockito.when(primaryDataStoreDao.findById(10L)).thenReturn(pool);
152+
Mockito.when(pool.getScope()).thenReturn(ScopeType.ZONE);
153+
Mockito.when(pool.getHypervisor()).thenReturn(HypervisorType.None);
154+
Mockito.when(volume.getInstanceId()).thenReturn(null);
155+
Mockito.when(volume.getAccountId()).thenReturn(2L);
156+
157+
Mockito.when(snapshotDao.findByVolumeIdAndNameNotInStatus(volumeId, "dup", Snapshot.State.Destroyed, Snapshot.State.Error))
158+
.thenReturn(Mockito.mock(SnapshotVO.class));
159+
160+
Assert.assertThrows(InvalidParameterValueException.class, () ->
161+
snapshotManager.allocSnapshot(volumeId, Snapshot.MANUAL_POLICY_ID, "dup", null));
162+
} finally {
163+
CallContext.unregister();
164+
}
165+
}
166+
167+
@Test
168+
public void testAllocSnapshotDoesNotCheckDuplicateForAutoGeneratedName() {
169+
long volumeId = 1L;
170+
CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class));
171+
try {
172+
VolumeInfo volume = Mockito.mock(VolumeInfo.class);
173+
Mockito.when(volFactory.getVolume(volumeId)).thenReturn(volume);
174+
DataStore dataStore = Mockito.mock(DataStore.class);
175+
Mockito.when(volume.getDataStore()).thenReturn(dataStore);
176+
Mockito.lenient().when(dataStore.getId()).thenReturn(10L);
177+
StoragePoolVO pool = Mockito.mock(StoragePoolVO.class);
178+
Mockito.lenient().when(primaryDataStoreDao.findById(10L)).thenReturn(pool);
179+
Mockito.lenient().when(pool.getScope()).thenReturn(ScopeType.ZONE);
180+
Mockito.lenient().when(pool.getHypervisor()).thenReturn(HypervisorType.None);
181+
Mockito.when(volume.getInstanceId()).thenReturn(null);
182+
Mockito.when(volume.getAccountId()).thenReturn(2L);
183+
184+
// A recurring snapshot carries no caller-supplied name, so the auto-generated name must not be checked.
185+
Mockito.lenient().when(snapshotDao.findByVolumeIdAndNameNotInStatus(Mockito.eq(volumeId), Mockito.anyString(),
186+
Mockito.eq(Snapshot.State.Destroyed), Mockito.eq(Snapshot.State.Error)))
187+
.thenReturn(Mockito.mock(SnapshotVO.class));
188+
189+
try {
190+
snapshotManager.allocSnapshot(volumeId, Snapshot.MANUAL_POLICY_ID, null, null);
191+
} catch (Exception e) {
192+
// The rest of allocSnapshot is not stubbed here; this test only asserts the duplicate-name guard is
193+
// not applied to an auto-generated name.
194+
}
195+
196+
Mockito.verify(snapshotDao, Mockito.never()).findByVolumeIdAndNameNotInStatus(Mockito.eq(volumeId),
197+
Mockito.anyString(), Mockito.eq(Snapshot.State.Destroyed), Mockito.eq(Snapshot.State.Error));
198+
} finally {
199+
CallContext.unregister();
200+
}
201+
}
202+
126203
@Test
127204
public void testGetSnapshotZoneImageStoreValid() {
128205
final long snapshotId = 1L;

0 commit comments

Comments
 (0)