mirror of https://github.com/apache/cloudstack.git
Add support scheduling min & max for autoscaling groups
This commit is contained in:
parent
cc2d5df079
commit
f84692b257
|
|
@ -677,6 +677,7 @@ public class EventTypes {
|
|||
public static final String EVENT_AUTOSCALEVMGROUP_DISABLE = "AUTOSCALEVMGROUP.DISABLE";
|
||||
public static final String EVENT_AUTOSCALEVMGROUP_SCALEDOWN = "AUTOSCALEVMGROUP.SCALEDOWN";
|
||||
public static final String EVENT_AUTOSCALEVMGROUP_SCALEUP = "AUTOSCALEVMGROUP.SCALEUP";
|
||||
public static final String EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE = "AUTOSCALEVMGROUP.SCHEDULE.UPDATE";
|
||||
|
||||
public static final String EVENT_BAREMETAL_DHCP_SERVER_ADD = "PHYSICAL.DHCP.ADD";
|
||||
public static final String EVENT_BAREMETAL_DHCP_SERVER_DELETE = "PHYSICAL.DHCP.DELETE";
|
||||
|
|
@ -895,6 +896,7 @@ public class EventTypes {
|
|||
entityEventDetails.put(EVENT_VM_SCHEDULE_REBOOT, ResourceSchedule.class);
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_STOP, ResourceSchedule.class);
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_REBOOT, ResourceSchedule.class);
|
||||
entityEventDetails.put(EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE, ResourceSchedule.class);
|
||||
|
||||
// Generic Resource Schedule
|
||||
entityEventDetails.put(EVENT_SCHEDULE_CREATE, ResourceSchedule.class);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.cloudstack.schedule.autoscale;
|
||||
|
||||
import com.cloud.event.EventTypes;
|
||||
import org.apache.cloudstack.schedule.ResourceSchedule;
|
||||
|
||||
public enum AutoScaleScheduleAction implements ResourceSchedule.Action {
|
||||
UPDATE {
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return EventTypes.EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -48,6 +48,8 @@ public interface AutoScaleManager extends AutoScaleService {
|
|||
|
||||
void checkAutoScaleUser(Long autoscaleUserId, long accountId);
|
||||
|
||||
void validateMinMaxMembers(int minMembers, int maxMembers);
|
||||
|
||||
boolean deleteAutoScaleVmGroupsByAccount(Account account);
|
||||
|
||||
void cleanUpAutoScaleResources(Account account);
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ import org.apache.cloudstack.framework.config.ConfigKey;
|
|||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleManager;
|
||||
import org.apache.cloudstack.userdata.UserDataManager;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
|
|
@ -285,6 +286,8 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
|
|||
private VirtualMachineManager virtualMachineManager;
|
||||
@Inject
|
||||
GuestOSDao guestOSDao;
|
||||
@Inject
|
||||
private ResourceScheduleManager resourceScheduleManager;
|
||||
|
||||
private static final String PARAM_ROOT_DISK_SIZE = "rootdisksize";
|
||||
private static final String PARAM_DISK_OFFERING_ID = "diskofferingid";
|
||||
|
|
@ -1098,7 +1101,11 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
|
|||
|
||||
if (autoScaleVmGroupVO.getState().equals(AutoScaleVmGroup.State.NEW)) {
|
||||
/* This condition is for handling failures during creation command */
|
||||
return autoScaleVmGroupDao.remove(id);
|
||||
boolean removed = autoScaleVmGroupDao.remove(id);
|
||||
if (removed) {
|
||||
resourceScheduleManager.removeSchedulesForResource(ApiCommandResourceType.AutoScaleVmGroup, id);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
if (!autoScaleVmGroupVO.getState().equals(AutoScaleVmGroup.State.DISABLED) && !Boolean.TRUE.equals(cleanup)) {
|
||||
|
|
@ -1168,6 +1175,8 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
|
|||
return false;
|
||||
}
|
||||
|
||||
resourceScheduleManager.removeSchedulesForResource(ApiCommandResourceType.AutoScaleVmGroup, id);
|
||||
|
||||
logger.info("Successfully deleted autoscale vm group: {}", autoScaleVmGroupVO);
|
||||
return success; // Successfull
|
||||
}
|
||||
|
|
@ -1231,6 +1240,22 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
|
|||
return searchWrapper.search();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateMinMaxMembers(int minMembers, int maxMembers) {
|
||||
if (minMembers <= 0) {
|
||||
throw new InvalidParameterValueException(ApiConstants.MIN_MEMBERS + " is an invalid value: " + minMembers);
|
||||
}
|
||||
|
||||
if (maxMembers <= 0) {
|
||||
throw new InvalidParameterValueException(ApiConstants.MAX_MEMBERS + " is an invalid value: " + maxMembers);
|
||||
}
|
||||
|
||||
if (minMembers > maxMembers) {
|
||||
throw new InvalidParameterValueException(ApiConstants.MIN_MEMBERS + " (" + minMembers + ")cannot be greater than " + ApiConstants.MAX_MEMBERS + " (" +
|
||||
maxMembers + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@DB
|
||||
protected AutoScaleVmGroupVO checkValidityAndPersist(final AutoScaleVmGroupVO vmGroup, final List<Long> passedScaleUpPolicyIds,
|
||||
final List<Long> passedScaleDownPolicyIds) {
|
||||
|
|
@ -1249,18 +1274,7 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
|
|||
ApiDBUtils.getAutoScaleVmGroupPolicyIds(vmGroup.getId(), currentScaleUpPolicyIds, currentScaleDownPolicyIds);
|
||||
}
|
||||
|
||||
if (minMembers <= 0) {
|
||||
throw new InvalidParameterValueException(ApiConstants.MIN_MEMBERS + " is an invalid value: " + minMembers);
|
||||
}
|
||||
|
||||
if (maxMembers <= 0) {
|
||||
throw new InvalidParameterValueException(ApiConstants.MAX_MEMBERS + " is an invalid value: " + maxMembers);
|
||||
}
|
||||
|
||||
if (minMembers > maxMembers) {
|
||||
throw new InvalidParameterValueException(ApiConstants.MIN_MEMBERS + " (" + minMembers + ")cannot be greater than " + ApiConstants.MAX_MEMBERS + " (" +
|
||||
maxMembers + ")");
|
||||
}
|
||||
validateMinMaxMembers(minMembers, maxMembers);
|
||||
|
||||
if (interval <= 0) {
|
||||
throw new InvalidParameterValueException("interval is an invalid value: " + interval);
|
||||
|
|
@ -1341,10 +1355,10 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
|
|||
AutoScaleVmGroupVO vmGroupVO = getEntityInDatabase(CallContext.current().getCallingAccount(), "AutoScale Vm Group", vmGroupId, autoScaleVmGroupDao);
|
||||
int currentInterval = vmGroupVO.getInterval();
|
||||
|
||||
boolean physicalParametersUpdate = (minMembers != null || maxMembers != null || (interval != null && interval != currentInterval) || CollectionUtils.isNotEmpty(scaleUpPolicyIds) || CollectionUtils.isNotEmpty(scaleDownPolicyIds));
|
||||
boolean physicalParametersUpdate = ((interval != null && interval != currentInterval) || CollectionUtils.isNotEmpty(scaleUpPolicyIds) || CollectionUtils.isNotEmpty(scaleDownPolicyIds));
|
||||
|
||||
if (physicalParametersUpdate && !vmGroupVO.getState().equals(AutoScaleVmGroup.State.DISABLED)) {
|
||||
throw new InvalidParameterValueException("An AutoScale Vm Group can be updated with minMembers/maxMembers/Interval only when it is in disabled state");
|
||||
throw new InvalidParameterValueException("An AutoScale Vm Group can be updated with Interval/Policies only when it is in disabled state");
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(name)) {
|
||||
|
|
|
|||
|
|
@ -366,7 +366,8 @@ public abstract class BaseScheduleWorker extends ManagerBase {
|
|||
*/
|
||||
public <T extends BaseCmd> long submitAsyncJob(
|
||||
Class<T> cmdClass, long accountId, long resourceId, long eventId,
|
||||
Map<String, String> extra) {
|
||||
Map<String, String> extra
|
||||
) {
|
||||
Map<String, String> params = new HashMap<>(extra);
|
||||
params.put(ApiConstants.ID, String.valueOf(resourceId));
|
||||
params.put("ctxUserId", "1");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
package org.apache.cloudstack.schedule.autoscale;
|
||||
|
||||
import com.cloud.event.ActionEventUtils;
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.network.as.AutoScaleManager;
|
||||
import com.cloud.network.as.AutoScaleVmGroup;
|
||||
import com.cloud.network.as.AutoScaleVmGroupVO;
|
||||
import com.cloud.network.as.dao.AutoScaleVmGroupDao;
|
||||
import com.cloud.user.User;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmGroupCmd;
|
||||
import org.apache.cloudstack.schedule.BaseScheduleWorker;
|
||||
import org.apache.cloudstack.schedule.ResourceSchedule;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduledJobVO;
|
||||
import org.apache.cloudstack.schedule.dao.ResourceScheduleDetailsDao;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.EnumUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.cloudstack.api.ApiConstants.MAX_MEMBERS;
|
||||
import static org.apache.cloudstack.api.ApiConstants.MIN_MEMBERS;
|
||||
|
||||
public class AutoScaleScheduleWorker extends BaseScheduleWorker {
|
||||
|
||||
@Inject
|
||||
private AutoScaleManager autoScaleManager;
|
||||
|
||||
@Inject
|
||||
private AutoScaleVmGroupDao autoScaleVmGroupDao;
|
||||
|
||||
@Inject
|
||||
private ResourceScheduleDetailsDao resourceScheduleDetailsDao;
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.AutoScaleVmGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResourceValid(long resourceId) {
|
||||
AutoScaleVmGroupVO group = autoScaleVmGroupDao.findById(resourceId);
|
||||
return group != null && !AutoScaleVmGroup.State.REVOKE.equals(group.getState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId(long resourceId) {
|
||||
AutoScaleVmGroupVO group = autoScaleVmGroupDao.findById(resourceId);
|
||||
return group != null ? group.getAccountId() : User.UID_SYSTEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoScaleScheduleAction parseAction(String actionName) {
|
||||
AutoScaleScheduleAction action = EnumUtils.getEnumIgnoreCase(AutoScaleScheduleAction.class, actionName);
|
||||
if (action == null) {
|
||||
throw new InvalidParameterValueException(String.format(
|
||||
"Invalid action for AutoScaleVmGroup schedule: %s. Supported actions: %s",
|
||||
actionName, Arrays.toString(AutoScaleScheduleAction.values())));
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateDetails(ResourceSchedule.Action action, Map<String, String> details) {
|
||||
if (!(action instanceof AutoScaleScheduleAction)) {
|
||||
throw new InvalidParameterValueException("Invalid action type for AutoScaleVmGroup schedule");
|
||||
}
|
||||
if (MapUtils.isEmpty(details)) {
|
||||
throw new InvalidParameterValueException("Details are required for AutoScaleVmGroup schedule");
|
||||
}
|
||||
if (!details.keySet().stream().allMatch(key -> MIN_MEMBERS.equalsIgnoreCase(key) || MAX_MEMBERS.equalsIgnoreCase(key))) {
|
||||
throw new InvalidParameterValueException("Only minmembers and maxmembers are supported for AutoScaleVmGroup schedule details");
|
||||
}
|
||||
|
||||
String minMembersRaw = details.get(MIN_MEMBERS);
|
||||
String maxMembersRaw = details.get(MAX_MEMBERS);
|
||||
if (StringUtils.isBlank(minMembersRaw) || StringUtils.isBlank(maxMembersRaw)) {
|
||||
throw new InvalidParameterValueException("Both minmembers and maxmembers are required for AutoScaleVmGroup schedule");
|
||||
}
|
||||
|
||||
int minMembers;
|
||||
int maxMembers;
|
||||
try {
|
||||
minMembers = Integer.parseInt(minMembersRaw);
|
||||
maxMembers = Integer.parseInt(maxMembersRaw);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidParameterValueException("minmembers and maxmembers must be valid integers");
|
||||
}
|
||||
|
||||
autoScaleManager.validateMinMaxMembers(minMembers, maxMembers);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long processJob(ResourceScheduledJobVO job) {
|
||||
AutoScaleVmGroupVO group = autoScaleVmGroupDao.findById(job.getResourceId());
|
||||
if (group == null || AutoScaleVmGroup.State.REVOKE.equals(group.getState())) {
|
||||
logger.warn("AutoScaleVmGroup id={} not found/invalid; skipping scheduled job {}", job.getResourceId(), job);
|
||||
return null;
|
||||
}
|
||||
|
||||
AutoScaleScheduleAction action = parseAction(job.getActionName());
|
||||
Map<String, String> details = resourceScheduleDetailsDao.listDetailsKeyPairs(job.getScheduleId(), true);
|
||||
validateDetails(action, details);
|
||||
|
||||
long eventId = ActionEventUtils.onCompletedActionEvent(
|
||||
User.UID_SYSTEM, group.getAccountId(), null,
|
||||
action.getEventType(), true,
|
||||
String.format("Executing action (%s) for AutoScaleVmGroup: %s", action, group.getUuid()),
|
||||
group.getId(), ApiCommandResourceType.AutoScaleVmGroup.toString(), 0);
|
||||
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put(MIN_MEMBERS, details.get(MIN_MEMBERS));
|
||||
params.put(MAX_MEMBERS, details.get(MAX_MEMBERS));
|
||||
return submitAsyncJob(UpdateAutoScaleVmGroupCmd.class, group.getAccountId(), group.getId(), eventId, params);
|
||||
}
|
||||
}
|
||||
|
|
@ -376,6 +376,9 @@
|
|||
<bean id="VMScheduleWorker" class="org.apache.cloudstack.schedule.vm.VMScheduleWorker">
|
||||
<property name="asyncJobDispatcher" ref="ApiAsyncJobDispatcher" />
|
||||
</bean>
|
||||
<bean id="AutoScaleScheduleWorker" class="org.apache.cloudstack.schedule.autoscale.AutoScaleScheduleWorker">
|
||||
<property name="asyncJobDispatcher" ref="ApiAsyncJobDispatcher" />
|
||||
</bean>
|
||||
|
||||
<bean id="vnfTemplateManager" class="org.apache.cloudstack.storage.template.VnfTemplateManagerImpl" />
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import org.apache.cloudstack.affinity.AffinityGroupVO;
|
|||
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
|
||||
import org.apache.cloudstack.annotation.AnnotationService;
|
||||
import org.apache.cloudstack.annotation.dao.AnnotationDao;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.command.admin.autoscale.CreateCounterCmd;
|
||||
|
|
@ -62,6 +63,7 @@ import org.apache.cloudstack.api.command.user.vm.DeployVMCmd;
|
|||
import org.apache.cloudstack.config.ApiServiceConfiguration;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleManager;
|
||||
import org.apache.cloudstack.userdata.UserDataManager;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
|
|
@ -268,6 +270,8 @@ public class AutoScaleManagerImplTest {
|
|||
VirtualMachineManager virtualMachineManager;
|
||||
@Mock
|
||||
GuestOSDao guestOSDao;
|
||||
@Mock
|
||||
ResourceScheduleManager resourceScheduleManager;
|
||||
|
||||
@Mock
|
||||
NetworkOrchestrationService networkOrchestrationService;
|
||||
|
|
@ -1036,7 +1040,6 @@ public class AutoScaleManagerImplTest {
|
|||
when(asVmGroupMock.getInterval()).thenReturn(interval);
|
||||
when(asVmGroupMock.getMaxMembers()).thenReturn(maxMembers);
|
||||
when(asVmGroupMock.getMinMembers()).thenReturn(minMembers);
|
||||
when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.DISABLED);
|
||||
when(asVmGroupMock.getProfileId()).thenReturn(vmProfileId);
|
||||
when(asVmGroupMock.getLoadBalancerId()).thenReturn(loadBalancerId);
|
||||
|
||||
|
|
@ -1086,7 +1089,6 @@ public class AutoScaleManagerImplTest {
|
|||
|
||||
when(autoScaleVmGroupDao.findById(vmGroupId)).thenReturn(asVmGroupMock);
|
||||
when(asVmGroupMock.getInterval()).thenReturn(interval);
|
||||
when(asVmGroupMock.getState()).thenReturn(AutoScaleVmGroup.State.ENABLED);
|
||||
|
||||
AutoScaleVmGroup vmGroup = autoScaleManagerImplSpy.updateAutoScaleVmGroup(cmd);
|
||||
}
|
||||
|
|
@ -1213,6 +1215,7 @@ public class AutoScaleManagerImplTest {
|
|||
Mockito.verify(autoScaleManagerImplSpy).configureAutoScaleVmGroup(vmGroupId, AutoScaleVmGroup.State.ENABLED);
|
||||
Mockito.verify(annotationDao).removeByEntityType(AnnotationService.EntityType.AUTOSCALE_VM_GROUP.name(), vmGroupUuid);
|
||||
Mockito.verify(autoScaleManagerImplSpy).cancelMonitorTask(vmGroupId);
|
||||
Mockito.verify(resourceScheduleManager).removeSchedulesForResource(ApiCommandResourceType.AutoScaleVmGroup, vmGroupId);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -2557,4 +2560,19 @@ public class AutoScaleManagerImplTest {
|
|||
Assert.assertTrue(result.first().matches(vmHostNamePattern));
|
||||
Assert.assertEquals(result.first(), result.second());
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testValidateMinMaxMembersInvalidMin() {
|
||||
autoScaleManagerImplSpy.validateMinMaxMembers(-1, 5);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testValidateMinMaxMembersInvalidMax() {
|
||||
autoScaleManagerImplSpy.validateMinMaxMembers(1, -1);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testValidateMinMaxMembersInvalidRange() {
|
||||
autoScaleManagerImplSpy.validateMinMaxMembers(5, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
package org.apache.cloudstack.schedule;
|
||||
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.network.as.AutoScaleVmGroup;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.user.AccountManager;
|
||||
import com.cloud.user.User;
|
||||
|
|
@ -34,6 +35,8 @@ import org.apache.cloudstack.context.CallContext;
|
|||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.schedule.dao.ResourceScheduleDao;
|
||||
import org.apache.cloudstack.schedule.dao.ResourceScheduleDetailsDao;
|
||||
import org.apache.cloudstack.schedule.autoscale.AutoScaleScheduleAction;
|
||||
import org.apache.cloudstack.schedule.autoscale.AutoScaleScheduleWorker;
|
||||
import org.apache.cloudstack.schedule.vm.VMScheduleAction;
|
||||
import org.apache.cloudstack.schedule.vm.VMScheduleWorker;
|
||||
import org.apache.commons.lang.time.DateUtils;
|
||||
|
|
@ -73,6 +76,9 @@ public class ResourceScheduleManagerImplTest {
|
|||
@Mock
|
||||
VMScheduleWorker vmScheduleWorker;
|
||||
|
||||
@Mock
|
||||
AutoScaleScheduleWorker autoScaleScheduleWorker;
|
||||
|
||||
@Mock
|
||||
UserVmManager userVmManager;
|
||||
|
||||
|
|
@ -92,8 +98,10 @@ public class ResourceScheduleManagerImplTest {
|
|||
CallContext.register(callingUser, callingAccount);
|
||||
|
||||
Mockito.when(vmScheduleWorker.getApiResourceType()).thenReturn(ApiCommandResourceType.VirtualMachine);
|
||||
Mockito.when(autoScaleScheduleWorker.getApiResourceType()).thenReturn(ApiCommandResourceType.AutoScaleVmGroup);
|
||||
Map<ApiCommandResourceType, BaseScheduleWorker> workerMap = new HashMap<>();
|
||||
workerMap.put(ApiCommandResourceType.VirtualMachine, vmScheduleWorker);
|
||||
workerMap.put(ApiCommandResourceType.AutoScaleVmGroup, autoScaleScheduleWorker);
|
||||
ReflectionTestUtils.setField(resourceScheduleManager, "workerMap", workerMap);
|
||||
}
|
||||
|
||||
|
|
@ -226,6 +234,43 @@ public class ResourceScheduleManagerImplTest {
|
|||
validateResponse(response, schedule, vm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createScheduleAutoScale() {
|
||||
AutoScaleVmGroup group = Mockito.mock(AutoScaleVmGroup.class);
|
||||
ResourceScheduleVO schedule = Mockito.mock(ResourceScheduleVO.class);
|
||||
Account ownerAccount = Mockito.mock(Account.class);
|
||||
Map<String, String> details = new HashMap<>();
|
||||
details.put("minmembers", "2");
|
||||
details.put("maxmembers", "5");
|
||||
|
||||
Mockito.when(group.getId()).thenReturn(21L);
|
||||
Mockito.when(group.getUuid()).thenReturn(UUID.randomUUID().toString());
|
||||
Mockito.when(entityManager.findByUuid(AutoScaleVmGroup.class, "asg-uuid")).thenReturn(group);
|
||||
Mockito.when(autoScaleScheduleWorker.isResourceValid(21L)).thenReturn(true);
|
||||
Mockito.when(autoScaleScheduleWorker.getEntityOwnerId(21L)).thenReturn(2L);
|
||||
Mockito.when(autoScaleScheduleWorker.parseAction("UPDATE")).thenReturn(AutoScaleScheduleAction.UPDATE);
|
||||
Mockito.when(accountManager.getAccount(2L)).thenReturn(ownerAccount);
|
||||
Mockito.when(resourceScheduleDao.persist(Mockito.any(ResourceScheduleVO.class))).thenReturn(schedule);
|
||||
Mockito.when(schedule.getId()).thenReturn(99L);
|
||||
Mockito.when(schedule.getResourceType()).thenReturn(ApiCommandResourceType.AutoScaleVmGroup);
|
||||
Mockito.when(schedule.getResourceId()).thenReturn(21L);
|
||||
Mockito.when(schedule.getActionName()).thenReturn("UPDATE");
|
||||
Mockito.when(autoScaleScheduleWorker.parseAction("UPDATE")).thenReturn(AutoScaleScheduleAction.UPDATE);
|
||||
Mockito.when(entityManager.findById(AutoScaleVmGroup.class, 21L)).thenReturn(group);
|
||||
|
||||
ResourceScheduleResponse response = resourceScheduleManager.createSchedule(
|
||||
ApiCommandResourceType.AutoScaleVmGroup, "asg-uuid", null,
|
||||
"0 0 * * *", "UTC", "UPDATE",
|
||||
DateUtils.addDays(new Date(), 1), DateUtils.addDays(new Date(), 2),
|
||||
true, details);
|
||||
|
||||
Assert.assertEquals(ApiCommandResourceType.AutoScaleVmGroup, ReflectionTestUtils.getField(response, "resourceType"));
|
||||
Assert.assertEquals("21", ReflectionTestUtils.getField(response, "resourceId"));
|
||||
Assert.assertEquals("2", ((Map<String, String>) ReflectionTestUtils.getField(response, "details")).get("minmembers"));
|
||||
Assert.assertEquals("5", ((Map<String, String>) ReflectionTestUtils.getField(response, "details")).get("maxmembers"));
|
||||
Mockito.verify(autoScaleScheduleWorker, Mockito.times(1)).validateDetails(Mockito.eq(AutoScaleScheduleAction.UPDATE), Mockito.eq(details));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeSchedulesForResource() {
|
||||
ResourceScheduleVO schedule1 = Mockito.mock(ResourceScheduleVO.class);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.cloudstack.schedule.autoscale;
|
||||
|
||||
import com.cloud.event.ActionEventUtils;
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.network.as.AutoScaleManager;
|
||||
import com.cloud.network.as.AutoScaleVmGroup;
|
||||
import com.cloud.network.as.AutoScaleVmGroupVO;
|
||||
import com.cloud.network.as.dao.AutoScaleVmGroupDao;
|
||||
import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmGroupCmd;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduledJobVO;
|
||||
import org.apache.cloudstack.schedule.dao.ResourceScheduleDao;
|
||||
import org.apache.cloudstack.schedule.dao.ResourceScheduleDetailsDao;
|
||||
import org.apache.cloudstack.schedule.dao.ResourceScheduledJobDao;
|
||||
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class AutoScaleScheduleWorkerTest {
|
||||
@Spy
|
||||
@InjectMocks
|
||||
private AutoScaleScheduleWorker worker = new AutoScaleScheduleWorker();
|
||||
|
||||
@Mock
|
||||
private AutoScaleVmGroupDao autoScaleVmGroupDao;
|
||||
@Mock
|
||||
private ResourceScheduleDetailsDao resourceScheduleDetailsDao;
|
||||
@Mock
|
||||
private ResourceScheduleDao resourceScheduleDao;
|
||||
@Mock
|
||||
private ResourceScheduledJobDao resourceScheduledJobDao;
|
||||
@Mock
|
||||
private AsyncJobManager asyncJobManager;
|
||||
@Mock
|
||||
private AutoScaleManager autoScaleManager;
|
||||
|
||||
private AutoCloseable closeable;
|
||||
private MockedStatic<ActionEventUtils> actionEventUtilsMocked;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
closeable = MockitoAnnotations.openMocks(this);
|
||||
actionEventUtilsMocked = Mockito.mockStatic(ActionEventUtils.class);
|
||||
Mockito.when(ActionEventUtils.onCompletedActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(),
|
||||
Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyString(),
|
||||
Mockito.anyLong(), Mockito.anyString(), Mockito.anyLong())).thenReturn(1L);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
actionEventUtilsMocked.close();
|
||||
closeable.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessJobWithValidDetailsSubmitsUpdateAutoScaleVmGroupCmd() {
|
||||
ResourceScheduledJobVO job = Mockito.mock(ResourceScheduledJobVO.class);
|
||||
AutoScaleVmGroupVO group = Mockito.mock(AutoScaleVmGroupVO.class);
|
||||
Map<String, String> details = new HashMap<>();
|
||||
details.put("minmembers", "2");
|
||||
details.put("maxmembers", "5");
|
||||
|
||||
Mockito.when(job.getResourceId()).thenReturn(1L);
|
||||
Mockito.when(job.getScheduleId()).thenReturn(10L);
|
||||
Mockito.when(job.getActionName()).thenReturn(AutoScaleScheduleAction.UPDATE.name());
|
||||
Mockito.when(autoScaleVmGroupDao.findById(1L)).thenReturn(group);
|
||||
Mockito.when(group.getState()).thenReturn(AutoScaleVmGroup.State.ENABLED);
|
||||
Mockito.when(group.getAccountId()).thenReturn(3L);
|
||||
Mockito.when(group.getId()).thenReturn(1L);
|
||||
Mockito.when(group.getUuid()).thenReturn("asg-uuid");
|
||||
Mockito.when(resourceScheduleDetailsDao.listDetailsKeyPairs(10L, true)).thenReturn(details);
|
||||
Mockito.doReturn(11L).when(worker).submitAsyncJob(
|
||||
Mockito.eq(UpdateAutoScaleVmGroupCmd.class), Mockito.eq(3L), Mockito.eq(1L), Mockito.eq(1L), Mockito.anyMap());
|
||||
|
||||
Long asyncJobId = worker.processJob(job);
|
||||
|
||||
Assert.assertEquals(Long.valueOf(11L), asyncJobId);
|
||||
Mockito.verify(worker).submitAsyncJob(Mockito.eq(UpdateAutoScaleVmGroupCmd.class), Mockito.eq(3L), Mockito.eq(1L), Mockito.eq(1L),
|
||||
Mockito.argThat(map -> "2".equals(map.get("minmembers")) && "5".equals(map.get("maxmembers"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessJobWithMissingGroupSkipsExecution() {
|
||||
ResourceScheduledJobVO job = Mockito.mock(ResourceScheduledJobVO.class);
|
||||
Mockito.when(job.getResourceId()).thenReturn(1L);
|
||||
Mockito.when(autoScaleVmGroupDao.findById(1L)).thenReturn(null);
|
||||
|
||||
Long asyncJobId = worker.processJob(job);
|
||||
Assert.assertNull(asyncJobId);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testValidateDetailsMissingRequiredKeys() {
|
||||
Map<String, String> details = new HashMap<>();
|
||||
details.put("minmembers", "1");
|
||||
worker.validateDetails(AutoScaleScheduleAction.UPDATE, details);
|
||||
}
|
||||
}
|
||||
|
|
@ -2240,6 +2240,7 @@
|
|||
"label.schedule": "Schedule",
|
||||
"label.scheduled": "Scheduled",
|
||||
"label.schedule.add": "Add schedule",
|
||||
"label.update.members": "Update members",
|
||||
"label.scheduled.backups": "Scheduled backups",
|
||||
"label.schedules": "Schedules",
|
||||
"label.scope": "Scope",
|
||||
|
|
|
|||
|
|
@ -909,6 +909,12 @@ export default {
|
|||
name: 'scaledown.policy',
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/AutoScaleDownPolicyTab.vue')))
|
||||
},
|
||||
{
|
||||
name: 'schedules',
|
||||
resourceType: 'AutoScaleVmGroup',
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/ResourceSchedules.vue'))),
|
||||
show: () => { return 'listResourceSchedule' in store.getters.apis }
|
||||
},
|
||||
{
|
||||
name: 'events',
|
||||
resourceType: 'AutoScaleVmGroup',
|
||||
|
|
|
|||
|
|
@ -130,6 +130,41 @@
|
|||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-row
|
||||
v-if="resourceType === 'AutoScaleVmGroup'"
|
||||
justify="space-between"
|
||||
>
|
||||
<a-col :span="11">
|
||||
<a-form-item
|
||||
name="minMembers"
|
||||
ref="minMembers"
|
||||
>
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.minimum.members')" />
|
||||
</template>
|
||||
<a-input-number
|
||||
v-model:value="form.minMembers"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="11">
|
||||
<a-form-item
|
||||
name="maxMembers"
|
||||
ref="maxMembers"
|
||||
>
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.maximum.members')" />
|
||||
</template>
|
||||
<a-input-number
|
||||
v-model:value="form.maxMembers"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item
|
||||
name="timezone"
|
||||
ref="timezone"
|
||||
|
|
@ -323,6 +358,9 @@ export default {
|
|||
{ value: 'REBOOT', label: 'label.reboot' },
|
||||
{ value: 'FORCE_STOP', label: 'label.force.stop' },
|
||||
{ value: 'FORCE_REBOOT', label: 'label.force.reboot' }
|
||||
],
|
||||
AutoScaleVmGroup: [
|
||||
{ value: 'UPDATE', label: 'label.update.members' }
|
||||
]
|
||||
},
|
||||
periods: [
|
||||
|
|
@ -382,6 +420,8 @@ export default {
|
|||
schedule: '* * * * *',
|
||||
description: '',
|
||||
timezone: 'UTC',
|
||||
minMembers: null,
|
||||
maxMembers: null,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
enabled: true,
|
||||
|
|
@ -390,6 +430,8 @@ export default {
|
|||
this.rules = reactive({
|
||||
schedule: [{ type: 'string', required: true, message: this.$t('message.error.required.input') }],
|
||||
action: [{ type: 'string', required: true, message: this.$t('message.error.required.input') }],
|
||||
minMembers: [{ required: this.resourceType === 'AutoScaleVmGroup', message: this.$t('message.error.required.input') }],
|
||||
maxMembers: [{ required: this.resourceType === 'AutoScaleVmGroup', message: this.$t('message.error.required.input') }],
|
||||
timezone: [{ required: true, message: `${this.$t('message.error.select')}` }],
|
||||
startDate: [{ required: false, message: `${this.$t('message.error.select')}` }],
|
||||
endDate: [{ required: false, message: `${this.$t('message.error.select')}` }]
|
||||
|
|
@ -425,6 +467,8 @@ export default {
|
|||
this.resetForm()
|
||||
this.isEdit = true
|
||||
Object.assign(this.form, schedule)
|
||||
this.form.minMembers = schedule?.details?.minmembers ? Number(schedule.details.minmembers) : null
|
||||
this.form.maxMembers = schedule?.details?.maxmembers ? Number(schedule.details.maxmembers) : null
|
||||
// Some weird issue when we directly pass in the moment with tz object
|
||||
this.form.startDate = dayjs(schedule.startdate).tz(schedule.timezone)
|
||||
this.form.endDate = schedule.enddate ? dayjs(dayjs(schedule.enddate).tz(schedule.timezone)) : null
|
||||
|
|
@ -450,6 +494,10 @@ export default {
|
|||
startdate: (values.startDate) ? values.startDate.format(this.pattern) : null,
|
||||
enddate: (values.endDate) ? values.endDate.format(this.pattern) : null
|
||||
}
|
||||
if (this.resourceType === 'AutoScaleVmGroup') {
|
||||
params['details[0].minmembers'] = values.minMembers
|
||||
params['details[1].maxmembers'] = values.maxMembers
|
||||
}
|
||||
let command = null
|
||||
if (this.form.id === null || this.form.id === undefined) {
|
||||
command = 'createResourceSchedule'
|
||||
|
|
|
|||
Loading…
Reference in New Issue