Skip to content

Commit 9ecc535

Browse files
committed
Add support scheduling min & max for autoscaling groups
1 parent 20c3eaa commit 9ecc535

13 files changed

Lines changed: 454 additions & 18 deletions

File tree

api/src/main/java/com/cloud/event/EventTypes.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,7 @@ public class EventTypes {
677677
public static final String EVENT_AUTOSCALEVMGROUP_DISABLE = "AUTOSCALEVMGROUP.DISABLE";
678678
public static final String EVENT_AUTOSCALEVMGROUP_SCALEDOWN = "AUTOSCALEVMGROUP.SCALEDOWN";
679679
public static final String EVENT_AUTOSCALEVMGROUP_SCALEUP = "AUTOSCALEVMGROUP.SCALEUP";
680+
public static final String EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE = "AUTOSCALEVMGROUP.SCHEDULE.UPDATE";
680681

681682
public static final String EVENT_BAREMETAL_DHCP_SERVER_ADD = "PHYSICAL.DHCP.ADD";
682683
public static final String EVENT_BAREMETAL_DHCP_SERVER_DELETE = "PHYSICAL.DHCP.DELETE";
@@ -895,6 +896,7 @@ public class EventTypes {
895896
entityEventDetails.put(EVENT_VM_SCHEDULE_REBOOT, ResourceSchedule.class);
896897
entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_STOP, ResourceSchedule.class);
897898
entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_REBOOT, ResourceSchedule.class);
899+
entityEventDetails.put(EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE, ResourceSchedule.class);
898900

899901
// Generic Resource Schedule
900902
entityEventDetails.put(EVENT_SCHEDULE_CREATE, ResourceSchedule.class);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.cloudstack.schedule.autoscale;
20+
21+
import com.cloud.event.EventTypes;
22+
import org.apache.cloudstack.schedule.ResourceSchedule;
23+
24+
public enum AutoScaleScheduleAction implements ResourceSchedule.Action {
25+
UPDATE {
26+
@Override
27+
public String getEventType() {
28+
return EventTypes.EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE;
29+
}
30+
}
31+
}

server/src/main/java/com/cloud/network/as/AutoScaleManager.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public interface AutoScaleManager extends AutoScaleService {
4848

4949
void checkAutoScaleUser(Long autoscaleUserId, long accountId);
5050

51+
void validateMinMaxMembers(int minMembers, int maxMembers);
52+
5153
boolean deleteAutoScaleVmGroupsByAccount(Account account);
5254

5355
void cleanUpAutoScaleResources(Account account);

server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import org.apache.cloudstack.framework.config.Configurable;
7474
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
7575
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
76+
import org.apache.cloudstack.schedule.ResourceScheduleManager;
7677
import org.apache.cloudstack.userdata.UserDataManager;
7778
import org.apache.commons.collections.CollectionUtils;
7879
import org.apache.commons.collections.MapUtils;
@@ -285,6 +286,8 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
285286
private VirtualMachineManager virtualMachineManager;
286287
@Inject
287288
GuestOSDao guestOSDao;
289+
@Inject
290+
private ResourceScheduleManager resourceScheduleManager;
288291

289292
private static final String PARAM_ROOT_DISK_SIZE = "rootdisksize";
290293
private static final String PARAM_DISK_OFFERING_ID = "diskofferingid";
@@ -1098,7 +1101,11 @@ public boolean deleteAutoScaleVmGroup(final long id, final Boolean cleanup) {
10981101

10991102
if (autoScaleVmGroupVO.getState().equals(AutoScaleVmGroup.State.NEW)) {
11001103
/* This condition is for handling failures during creation command */
1101-
return autoScaleVmGroupDao.remove(id);
1104+
boolean removed = autoScaleVmGroupDao.remove(id);
1105+
if (removed) {
1106+
resourceScheduleManager.removeSchedulesForResource(ApiCommandResourceType.AutoScaleVmGroup, id);
1107+
}
1108+
return removed;
11021109
}
11031110

11041111
if (!autoScaleVmGroupVO.getState().equals(AutoScaleVmGroup.State.DISABLED) && !Boolean.TRUE.equals(cleanup)) {
@@ -1168,6 +1175,8 @@ public Boolean doInTransaction(TransactionStatus status) {
11681175
return false;
11691176
}
11701177

1178+
resourceScheduleManager.removeSchedulesForResource(ApiCommandResourceType.AutoScaleVmGroup, id);
1179+
11711180
logger.info("Successfully deleted autoscale vm group: {}", autoScaleVmGroupVO);
11721181
return success; // Successfull
11731182
}
@@ -1231,6 +1240,22 @@ public List<? extends AutoScaleVmGroup> listAutoScaleVmGroups(ListAutoScaleVmGro
12311240
return searchWrapper.search();
12321241
}
12331242

1243+
@Override
1244+
public void validateMinMaxMembers(int minMembers, int maxMembers) {
1245+
if (minMembers <= 0) {
1246+
throw new InvalidParameterValueException(ApiConstants.MIN_MEMBERS + " is an invalid value: " + minMembers);
1247+
}
1248+
1249+
if (maxMembers <= 0) {
1250+
throw new InvalidParameterValueException(ApiConstants.MAX_MEMBERS + " is an invalid value: " + maxMembers);
1251+
}
1252+
1253+
if (minMembers > maxMembers) {
1254+
throw new InvalidParameterValueException(ApiConstants.MIN_MEMBERS + " (" + minMembers + ")cannot be greater than " + ApiConstants.MAX_MEMBERS + " (" +
1255+
maxMembers + ")");
1256+
}
1257+
}
1258+
12341259
@DB
12351260
protected AutoScaleVmGroupVO checkValidityAndPersist(final AutoScaleVmGroupVO vmGroup, final List<Long> passedScaleUpPolicyIds,
12361261
final List<Long> passedScaleDownPolicyIds) {
@@ -1249,18 +1274,7 @@ protected AutoScaleVmGroupVO checkValidityAndPersist(final AutoScaleVmGroupVO vm
12491274
ApiDBUtils.getAutoScaleVmGroupPolicyIds(vmGroup.getId(), currentScaleUpPolicyIds, currentScaleDownPolicyIds);
12501275
}
12511276

1252-
if (minMembers <= 0) {
1253-
throw new InvalidParameterValueException(ApiConstants.MIN_MEMBERS + " is an invalid value: " + minMembers);
1254-
}
1255-
1256-
if (maxMembers <= 0) {
1257-
throw new InvalidParameterValueException(ApiConstants.MAX_MEMBERS + " is an invalid value: " + maxMembers);
1258-
}
1259-
1260-
if (minMembers > maxMembers) {
1261-
throw new InvalidParameterValueException(ApiConstants.MIN_MEMBERS + " (" + minMembers + ")cannot be greater than " + ApiConstants.MAX_MEMBERS + " (" +
1262-
maxMembers + ")");
1263-
}
1277+
validateMinMaxMembers(minMembers, maxMembers);
12641278

12651279
if (interval <= 0) {
12661280
throw new InvalidParameterValueException("interval is an invalid value: " + interval);
@@ -1341,10 +1355,10 @@ public AutoScaleVmGroup updateAutoScaleVmGroup(UpdateAutoScaleVmGroupCmd cmd) {
13411355
AutoScaleVmGroupVO vmGroupVO = getEntityInDatabase(CallContext.current().getCallingAccount(), "AutoScale Vm Group", vmGroupId, autoScaleVmGroupDao);
13421356
int currentInterval = vmGroupVO.getInterval();
13431357

1344-
boolean physicalParametersUpdate = (minMembers != null || maxMembers != null || (interval != null && interval != currentInterval) || CollectionUtils.isNotEmpty(scaleUpPolicyIds) || CollectionUtils.isNotEmpty(scaleDownPolicyIds));
1358+
boolean physicalParametersUpdate = ((interval != null && interval != currentInterval) || CollectionUtils.isNotEmpty(scaleUpPolicyIds) || CollectionUtils.isNotEmpty(scaleDownPolicyIds));
13451359

13461360
if (physicalParametersUpdate && !vmGroupVO.getState().equals(AutoScaleVmGroup.State.DISABLED)) {
1347-
throw new InvalidParameterValueException("An AutoScale Vm Group can be updated with minMembers/maxMembers/Interval only when it is in disabled state");
1361+
throw new InvalidParameterValueException("An AutoScale Vm Group can be updated with Interval/Policies only when it is in disabled state");
13481362
}
13491363

13501364
if (StringUtils.isNotBlank(name)) {

server/src/main/java/org/apache/cloudstack/schedule/BaseScheduleWorker.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,8 @@ private void cleanupScheduledJobs() {
366366
*/
367367
public <T extends BaseCmd> long submitAsyncJob(
368368
Class<T> cmdClass, long accountId, long resourceId, long eventId,
369-
Map<String, String> extra) {
369+
Map<String, String> extra
370+
) {
370371
Map<String, String> params = new HashMap<>(extra);
371372
params.put(ApiConstants.ID, String.valueOf(resourceId));
372373
params.put("ctxUserId", "1");
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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.schedule.autoscale;
18+
19+
import com.cloud.event.ActionEventUtils;
20+
import com.cloud.exception.InvalidParameterValueException;
21+
import com.cloud.network.as.AutoScaleManager;
22+
import com.cloud.network.as.AutoScaleVmGroup;
23+
import com.cloud.network.as.AutoScaleVmGroupVO;
24+
import com.cloud.network.as.dao.AutoScaleVmGroupDao;
25+
import com.cloud.user.User;
26+
import org.apache.cloudstack.api.ApiCommandResourceType;
27+
import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmGroupCmd;
28+
import org.apache.cloudstack.schedule.BaseScheduleWorker;
29+
import org.apache.cloudstack.schedule.ResourceSchedule;
30+
import org.apache.cloudstack.schedule.ResourceScheduledJobVO;
31+
import org.apache.cloudstack.schedule.dao.ResourceScheduleDetailsDao;
32+
import org.apache.commons.collections4.MapUtils;
33+
import org.apache.commons.lang3.EnumUtils;
34+
import org.apache.commons.lang3.StringUtils;
35+
36+
import javax.inject.Inject;
37+
import java.util.Arrays;
38+
import java.util.HashMap;
39+
import java.util.Map;
40+
41+
import static org.apache.cloudstack.api.ApiConstants.MAX_MEMBERS;
42+
import static org.apache.cloudstack.api.ApiConstants.MIN_MEMBERS;
43+
44+
public class AutoScaleScheduleWorker extends BaseScheduleWorker {
45+
46+
@Inject
47+
private AutoScaleManager autoScaleManager;
48+
49+
@Inject
50+
private AutoScaleVmGroupDao autoScaleVmGroupDao;
51+
52+
@Inject
53+
private ResourceScheduleDetailsDao resourceScheduleDetailsDao;
54+
55+
@Override
56+
public ApiCommandResourceType getApiResourceType() {
57+
return ApiCommandResourceType.AutoScaleVmGroup;
58+
}
59+
60+
@Override
61+
public boolean isResourceValid(long resourceId) {
62+
AutoScaleVmGroupVO group = autoScaleVmGroupDao.findById(resourceId);
63+
return group != null && !AutoScaleVmGroup.State.REVOKE.equals(group.getState());
64+
}
65+
66+
@Override
67+
public long getEntityOwnerId(long resourceId) {
68+
AutoScaleVmGroupVO group = autoScaleVmGroupDao.findById(resourceId);
69+
return group != null ? group.getAccountId() : User.UID_SYSTEM;
70+
}
71+
72+
@Override
73+
public AutoScaleScheduleAction parseAction(String actionName) {
74+
AutoScaleScheduleAction action = EnumUtils.getEnumIgnoreCase(AutoScaleScheduleAction.class, actionName);
75+
if (action == null) {
76+
throw new InvalidParameterValueException(String.format(
77+
"Invalid action for AutoScaleVmGroup schedule: %s. Supported actions: %s",
78+
actionName, Arrays.toString(AutoScaleScheduleAction.values())));
79+
}
80+
return action;
81+
}
82+
83+
@Override
84+
public void validateDetails(ResourceSchedule.Action action, Map<String, String> details) {
85+
if (!(action instanceof AutoScaleScheduleAction)) {
86+
throw new InvalidParameterValueException("Invalid action type for AutoScaleVmGroup schedule");
87+
}
88+
if (MapUtils.isEmpty(details)) {
89+
throw new InvalidParameterValueException("Details are required for AutoScaleVmGroup schedule");
90+
}
91+
if (!details.keySet().stream().allMatch(key -> MIN_MEMBERS.equalsIgnoreCase(key) || MAX_MEMBERS.equalsIgnoreCase(key))) {
92+
throw new InvalidParameterValueException("Only minmembers and maxmembers are supported for AutoScaleVmGroup schedule details");
93+
}
94+
95+
String minMembersRaw = details.get(MIN_MEMBERS);
96+
String maxMembersRaw = details.get(MAX_MEMBERS);
97+
if (StringUtils.isBlank(minMembersRaw) || StringUtils.isBlank(maxMembersRaw)) {
98+
throw new InvalidParameterValueException("Both minmembers and maxmembers are required for AutoScaleVmGroup schedule");
99+
}
100+
101+
int minMembers;
102+
int maxMembers;
103+
try {
104+
minMembers = Integer.parseInt(minMembersRaw);
105+
maxMembers = Integer.parseInt(maxMembersRaw);
106+
} catch (NumberFormatException e) {
107+
throw new InvalidParameterValueException("minmembers and maxmembers must be valid integers");
108+
}
109+
110+
autoScaleManager.validateMinMaxMembers(minMembers, maxMembers);
111+
}
112+
113+
@Override
114+
protected Long processJob(ResourceScheduledJobVO job) {
115+
AutoScaleVmGroupVO group = autoScaleVmGroupDao.findById(job.getResourceId());
116+
if (group == null || AutoScaleVmGroup.State.REVOKE.equals(group.getState())) {
117+
logger.warn("AutoScaleVmGroup id={} not found/invalid; skipping scheduled job {}", job.getResourceId(), job);
118+
return null;
119+
}
120+
121+
AutoScaleScheduleAction action = parseAction(job.getActionName());
122+
Map<String, String> details = resourceScheduleDetailsDao.listDetailsKeyPairs(job.getScheduleId(), true);
123+
validateDetails(action, details);
124+
125+
long eventId = ActionEventUtils.onCompletedActionEvent(
126+
User.UID_SYSTEM, group.getAccountId(), null,
127+
action.getEventType(), true,
128+
String.format("Executing action (%s) for AutoScaleVmGroup: %s", action, group.getUuid()),
129+
group.getId(), ApiCommandResourceType.AutoScaleVmGroup.toString(), 0);
130+
131+
Map<String, String> params = new HashMap<>();
132+
params.put(MIN_MEMBERS, details.get(MIN_MEMBERS));
133+
params.put(MAX_MEMBERS, details.get(MAX_MEMBERS));
134+
return submitAsyncJob(UpdateAutoScaleVmGroupCmd.class, group.getAccountId(), group.getId(), eventId, params);
135+
}
136+
}

server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,9 @@
376376
<bean id="VMScheduleWorker" class="org.apache.cloudstack.schedule.vm.VMScheduleWorker">
377377
<property name="asyncJobDispatcher" ref="ApiAsyncJobDispatcher" />
378378
</bean>
379+
<bean id="AutoScaleScheduleWorker" class="org.apache.cloudstack.schedule.autoscale.AutoScaleScheduleWorker">
380+
<property name="asyncJobDispatcher" ref="ApiAsyncJobDispatcher" />
381+
</bean>
379382

380383
<bean id="vnfTemplateManager" class="org.apache.cloudstack.storage.template.VnfTemplateManagerImpl" />
381384

server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
4848
import org.apache.cloudstack.annotation.AnnotationService;
4949
import org.apache.cloudstack.annotation.dao.AnnotationDao;
50+
import org.apache.cloudstack.api.ApiCommandResourceType;
5051
import org.apache.cloudstack.api.ApiConstants;
5152
import org.apache.cloudstack.api.BaseCmd;
5253
import org.apache.cloudstack.api.command.admin.autoscale.CreateCounterCmd;
@@ -62,6 +63,7 @@
6263
import org.apache.cloudstack.config.ApiServiceConfiguration;
6364
import org.apache.cloudstack.context.CallContext;
6465
import org.apache.cloudstack.framework.config.ConfigKey;
66+
import org.apache.cloudstack.schedule.ResourceScheduleManager;
6567
import org.apache.cloudstack.userdata.UserDataManager;
6668
import org.junit.After;
6769
import org.junit.Assert;
@@ -268,6 +270,8 @@ public class AutoScaleManagerImplTest {
268270
VirtualMachineManager virtualMachineManager;
269271
@Mock
270272
GuestOSDao guestOSDao;
273+
@Mock
274+
ResourceScheduleManager resourceScheduleManager;
271275

272276
@Mock
273277
NetworkOrchestrationService networkOrchestrationService;
@@ -1036,7 +1040,6 @@ public void testUpdateAutoScaleVmGroup() {
10361040
when(asVmGroupMock.getInterval()).thenReturn(interval);
10371041
when(asVmGroupMock.getMaxMembers()).thenReturn(maxMembers);
10381042
when(asVmGroupMock.getMinMembers()).thenReturn(minMembers);
1039-
when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.DISABLED);
10401043
when(asVmGroupMock.getProfileId()).thenReturn(vmProfileId);
10411044
when(asVmGroupMock.getLoadBalancerId()).thenReturn(loadBalancerId);
10421045

@@ -1086,7 +1089,6 @@ public void testUpdateAutoScaleVmGroupFail() {
10861089

10871090
when(autoScaleVmGroupDao.findById(vmGroupId)).thenReturn(asVmGroupMock);
10881091
when(asVmGroupMock.getInterval()).thenReturn(interval);
1089-
when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.ENABLED);
10901092

10911093
AutoScaleVmGroup vmGroup = autoScaleManagerImplSpy.updateAutoScaleVmGroup(cmd);
10921094
}
@@ -1213,6 +1215,7 @@ public void testDeleteAutoScaleVmGroupsByAccount() throws ResourceUnavailableExc
12131215
Mockito.verify(autoScaleManagerImplSpy).configureAutoScaleVmGroup(vmGroupId, AutoScaleVmGroup.State.ENABLED);
12141216
Mockito.verify(annotationDao).removeByEntityType(AnnotationService.EntityType.AUTOSCALE_VM_GROUP.name(), vmGroupUuid);
12151217
Mockito.verify(autoScaleManagerImplSpy).cancelMonitorTask(vmGroupId);
1218+
Mockito.verify(resourceScheduleManager).removeSchedulesForResource(ApiCommandResourceType.AutoScaleVmGroup, vmGroupId);
12161219
}
12171220

12181221
@Test
@@ -2557,4 +2560,19 @@ public void getNextVmHostAndDisplayNameHandlesNullGuestOS() {
25572560
Assert.assertTrue(result.first().matches(vmHostNamePattern));
25582561
Assert.assertEquals(result.first(), result.second());
25592562
}
2563+
2564+
@Test(expected = InvalidParameterValueException.class)
2565+
public void testValidateMinMaxMembersInvalidMin() {
2566+
autoScaleManagerImplSpy.validateMinMaxMembers(-1, 5);
2567+
}
2568+
2569+
@Test(expected = InvalidParameterValueException.class)
2570+
public void testValidateMinMaxMembersInvalidMax() {
2571+
autoScaleManagerImplSpy.validateMinMaxMembers(1, -1);
2572+
}
2573+
2574+
@Test(expected = InvalidParameterValueException.class)
2575+
public void testValidateMinMaxMembersInvalidRange() {
2576+
autoScaleManagerImplSpy.validateMinMaxMembers(5, 1);
2577+
}
25602578
}

0 commit comments

Comments
 (0)