This commit is contained in:
Vishesh 2026-05-13 08:55:18 +00:00 committed by GitHub
commit fa69b06f8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 3700 additions and 1933 deletions

View File

@ -43,7 +43,7 @@ import org.apache.cloudstack.storage.object.Bucket;
import org.apache.cloudstack.storage.object.ObjectStore;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.usage.Usage;
import org.apache.cloudstack.vm.schedule.VMSchedule;
import org.apache.cloudstack.schedule.ResourceSchedule;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterGuestIpv6Prefix;
@ -125,17 +125,18 @@ public class EventTypes {
public static final String EVENT_VM_UNMANAGE = "VM.UNMANAGE";
public static final String EVENT_VM_RECOVER = "VM.RECOVER";
// VM Schedule
public static final String EVENT_VM_SCHEDULE_CREATE = "VM.SCHEDULE.CREATE";
public static final String EVENT_VM_SCHEDULE_UPDATE = "VM.SCHEDULE.UPDATE";
public static final String EVENT_VM_SCHEDULE_DELETE = "VM.SCHEDULE.DELETE";
// VM Schedule action-execution events (fired when a scheduled action runs).
public static final String EVENT_VM_SCHEDULE_START = "VM.SCHEDULE.START";
public static final String EVENT_VM_SCHEDULE_STOP = "VM.SCHEDULE.STOP";
public static final String EVENT_VM_SCHEDULE_REBOOT = "VM.SCHEDULE.REBOOT";
public static final String EVENT_VM_SCHEDULE_FORCE_STOP = "VM.SCHEDULE.FORCE.STOP";
public static final String EVENT_VM_SCHEDULE_FORCE_REBOOT = "VM.SCHEDULE.FORCE.REBOOT";
// Generic Resource Schedule CRUD events (apply to all resource types).
public static final String EVENT_SCHEDULE_CREATE = "SCHEDULE.CREATE";
public static final String EVENT_SCHEDULE_UPDATE = "SCHEDULE.UPDATE";
public static final String EVENT_SCHEDULE_DELETE = "SCHEDULE.DELETE";
// Domain Router
public static final String EVENT_ROUTER_CREATE = "ROUTER.CREATE";
public static final String EVENT_ROUTER_DESTROY = "ROUTER.DESTROY";
@ -676,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";
@ -888,15 +890,18 @@ public class EventTypes {
entityEventDetails.put(EVENT_VM_IMPORT, VirtualMachine.class);
entityEventDetails.put(EVENT_VM_UNMANAGE, VirtualMachine.class);
// VMSchedule
entityEventDetails.put(EVENT_VM_SCHEDULE_CREATE, VMSchedule.class);
entityEventDetails.put(EVENT_VM_SCHEDULE_DELETE, VMSchedule.class);
entityEventDetails.put(EVENT_VM_SCHEDULE_UPDATE, VMSchedule.class);
entityEventDetails.put(EVENT_VM_SCHEDULE_START, VMSchedule.class);
entityEventDetails.put(EVENT_VM_SCHEDULE_STOP, VMSchedule.class);
entityEventDetails.put(EVENT_VM_SCHEDULE_REBOOT, VMSchedule.class);
entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_STOP, VMSchedule.class);
entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_REBOOT, VMSchedule.class);
// VMSchedule action-execution events
entityEventDetails.put(EVENT_VM_SCHEDULE_START, ResourceSchedule.class);
entityEventDetails.put(EVENT_VM_SCHEDULE_STOP, ResourceSchedule.class);
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);
entityEventDetails.put(EVENT_SCHEDULE_UPDATE, ResourceSchedule.class);
entityEventDetails.put(EVENT_SCHEDULE_DELETE, ResourceSchedule.class);
entityEventDetails.put(EVENT_ROUTER_CREATE, VirtualRouter.class);
entityEventDetails.put(EVENT_ROUTER_DESTROY, VirtualRouter.class);

View File

@ -0,0 +1,132 @@
// 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.api.command.user.schedule;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.schedule.ResourceScheduleManager;
import org.apache.commons.lang3.EnumUtils;
import javax.inject.Inject;
import java.util.Date;
import java.util.Map;
@APICommand(name = "createResourceSchedule", description = "Create Resource Schedule", responseObject = ResourceScheduleResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class CreateResourceScheduleCmd extends BaseCmd {
@Inject
ResourceScheduleManager resourceScheduleManager;
@Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true, description = "Type of the resource")
private String resourceType;
@Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING, required = true, description = "ID of the resource for which schedule is to be defined")
private String resourceId;
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = false, description = "Description of the schedule")
private String description;
@Parameter(name = ApiConstants.SCHEDULE, type = CommandType.STRING, required = true, description = "Schedule for action on resource in cron format. e.g. '0 15 10 * *' for 'at 15:00 on 10th day of every month'")
private String schedule;
@Parameter(name = ApiConstants.TIMEZONE, type = CommandType.STRING, required = true, description = "Specifies a timezone for this command. For more information on the timezone parameter, see TimeZone Format.")
private String timeZone;
@Parameter(name = ApiConstants.ACTION, type = CommandType.STRING, required = true, description = "Action to take on the resource.")
private String action;
@Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, required = false, description = "Start date from which the schedule becomes active. Defaults to current date plus 1 minute. Use format \"yyyy-MM-dd hh:mm:ss\"")
private Date startDate;
@Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, required = false, description = "End date after which the schedule becomes inactive. Use format \"yyyy-MM-dd hh:mm:ss\"")
private Date endDate;
@Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, required = false, description = "Enable schedule. Defaults to true")
private Boolean enabled;
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = false, description = "Map of (key/value pairs) details for the schedule.")
private Map details;
public ApiCommandResourceType getResourceType() {
ApiCommandResourceType type = EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, resourceType);
if (type == null) {
throw new InvalidParameterValueException("Unknown resource type: " + resourceType);
}
return type;
}
public String getResourceId() {
return resourceId;
}
public String getDescription() {
return description;
}
public String getSchedule() {
return schedule;
}
public String getTimeZone() {
return timeZone;
}
public String getAction() {
return action;
}
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
public Boolean getEnabled() {
if (enabled == null) {
enabled = true;
}
return enabled;
}
public Map<String, String> getDetails() {
return convertDetailsToMap(details);
}
@Override
public void execute() {
ResourceScheduleResponse response = resourceScheduleManager.createSchedule(getResourceType(), getResourceId(),
getDescription(), getSchedule(), getTimeZone(), getAction(), getStartDate(), getEndDate(), getEnabled(), getDetails());
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getAccountId();
}
}

View File

@ -0,0 +1,86 @@
// 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.api.command.user.schedule;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.schedule.ResourceScheduleManager;
import org.apache.commons.lang3.EnumUtils;
import javax.inject.Inject;
import java.util.List;
@APICommand(name = "deleteResourceSchedule", description = "Delete Resource Schedule", responseObject = SuccessResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class DeleteResourceScheduleCmd extends BaseCmd {
@Inject
ResourceScheduleManager resourceScheduleManager;
@Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true, description = "Type of the resource")
private String resourceType;
@Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING, required = true, description = "ID of the resource for which schedules are to be deleted")
private String resourceId;
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "ID of the schedule to be deleted")
private Long id;
@Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "comma separated list of schedule ids to be deleted")
private List<Long> ids;
public ApiCommandResourceType getResourceType() {
ApiCommandResourceType type = EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, resourceType);
if (type == null) {
throw new InvalidParameterValueException("Unknown resource type: " + resourceType);
}
return type;
}
public String getResourceId() {
return resourceId;
}
public Long getId() {
return id;
}
public List<Long> getIds() {
return ids;
}
@Override
public void execute() {
resourceScheduleManager.removeSchedule(getResourceType(), getResourceId(), getId(), getIds());
SuccessResponse response = new SuccessResponse(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getAccountId();
}
}

View File

@ -0,0 +1,97 @@
// 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.api.command.user.schedule;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.schedule.ResourceScheduleManager;
import org.apache.commons.lang3.EnumUtils;
import javax.inject.Inject;
import java.util.List;
@APICommand(name = "listResourceSchedule", description = "List Resource Schedules", responseObject = ResourceScheduleResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class ListResourceScheduleCmd extends BaseListCmd {
@Inject
ResourceScheduleManager resourceScheduleManager;
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "ID of the schedule")
private Long id;
@Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "comma separated list of schedule ids")
private List<Long> ids;
@Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true, description = "Type of the resource.")
private String resourceType;
@Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING, required = true, description = "ID of the resource for which schedules are to be listed.")
private String resourceId;
@Parameter(name = ApiConstants.ACTION, type = CommandType.STRING, required = false, description = "Action to take on the resource.")
private String action;
@Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, required = false, description = "Filter by enabled status.")
private Boolean enabled;
public Long getId() {
return id;
}
public List<Long> getIds() {
return ids;
}
public ApiCommandResourceType getResourceType() {
ApiCommandResourceType type = EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, resourceType);
if (type == null) {
throw new InvalidParameterValueException("Unknown resource type: " + resourceType);
}
return type;
}
public String getResourceId() {
return resourceId;
}
public String getAction() {
return action;
}
public Boolean getEnabled() {
return enabled;
}
@Override
public void execute() {
ListResponse<ResourceScheduleResponse> response = resourceScheduleManager.listSchedule(
getId(), getIds(), getResourceType(), getResourceId(), getAction(), getEnabled(),
getStartIndex(), getPageSizeVal()
);
response.setResponseName(getCommandName());
setResponseObject(response);
}
}

View File

@ -0,0 +1,108 @@
// 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.api.command.user.schedule;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.schedule.ResourceScheduleManager;
import javax.inject.Inject;
import java.util.Date;
import java.util.Map;
@APICommand(name = "updateResourceSchedule", description = "Update Resource Schedule", responseObject = ResourceScheduleResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class UpdateResourceScheduleCmd extends BaseCmd {
@Inject
ResourceScheduleManager resourceScheduleManager;
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = true, description = "ID of the schedule to be updated")
private Long id;
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = false, description = "Description of the schedule")
private String description;
@Parameter(name = ApiConstants.SCHEDULE, type = CommandType.STRING, required = false, description = "Schedule for action on resource in cron format.")
private String schedule;
@Parameter(name = ApiConstants.TIMEZONE, type = CommandType.STRING, required = false, description = "Specifies a timezone for this command.")
private String timeZone;
@Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, required = false, description = "Start date from which the schedule becomes active.")
private Date startDate;
@Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, required = false, description = "End date after which the schedule becomes inactive.")
private Date endDate;
@Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, required = false, description = "Enable or disable the schedule.")
private Boolean enabled;
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = false, description = "Map of (key/value pairs) details for the schedule.")
private Map details;
public Long getId() {
return id;
}
public String getDescription() {
return description;
}
public String getSchedule() {
return schedule;
}
public String getTimeZone() {
return timeZone;
}
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
public Boolean getEnabled() {
return enabled;
}
public Map<String, String> getDetails() {
return convertDetailsToMap(details);
}
@Override
public void execute() {
ResourceScheduleResponse response = resourceScheduleManager.updateSchedule(getId(), getDescription(), getSchedule(),
getTimeZone(), getStartDate(), getEndDate(), getEnabled(), getDetails());
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getAccountId();
}
}

View File

@ -22,23 +22,26 @@ import com.cloud.exception.InvalidParameterValueException;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
import org.apache.cloudstack.schedule.ResourceScheduleManager;
import javax.inject.Inject;
import java.util.Date;
@Deprecated
@APICommand(name = "createVMSchedule", description = "Create Instance Schedule", responseObject = VMScheduleResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class CreateVMScheduleCmd extends BaseCmd {
@Inject
VMScheduleManager vmScheduleManager;
ResourceScheduleManager resourceScheduleManager;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
type = CommandType.UUID,
@ -75,14 +78,14 @@ public class CreateVMScheduleCmd extends BaseCmd {
type = CommandType.DATE,
required = false,
description = "Start date from which the schedule becomes active. Defaults to current date plus 1 minute."
+ "Use format \"yyyy-MM-dd hh:mm:ss\")")
+ "Use format \"yyyy-MM-dd hh:mm:ss\"")
private Date startDate;
@Parameter(name = ApiConstants.END_DATE,
type = CommandType.DATE,
required = false,
description = "End date after which the schedule becomes inactive"
+ "Use format \"yyyy-MM-dd hh:mm:ss\")")
+ "Use format \"yyyy-MM-dd hh:mm:ss\"")
private Date endDate;
@Parameter(name = ApiConstants.ENABLED,
@ -91,9 +94,9 @@ public class CreateVMScheduleCmd extends BaseCmd {
description = "Enable Instance schedule. Defaults to true")
private Boolean enabled;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
/// //////////////////////////////////////////////////
/// //////////////// Accessors ///////////////////////
/// //////////////////////////////////////////////////
public Long getVmId() {
return vmId;
@ -130,13 +133,19 @@ public class CreateVMScheduleCmd extends BaseCmd {
return enabled;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
/// //////////////////////////////////////////////////
/// //////////// API Implementation///////////////////
/// //////////////////////////////////////////////////
@Override
public void execute() {
VMScheduleResponse response = vmScheduleManager.createSchedule(this);
String resourceIdStr = getVmId() != null ? String.valueOf(getVmId()) : null;
ResourceScheduleResponse scheduleResponse = resourceScheduleManager.createSchedule(
ApiCommandResourceType.VirtualMachine,
resourceIdStr, getDescription(), getSchedule(), getTimeZone(), getAction(),
getStartDate(), getEndDate(), getEnabled(), null);
VMScheduleResponse response = new VMScheduleResponse(scheduleResponse);
response.setResponseName(getCommandName());
setResponseObject(response);
}

View File

@ -22,6 +22,7 @@ import com.cloud.exception.InvalidParameterValueException;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
@ -30,19 +31,19 @@ import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
import org.apache.cloudstack.vm.schedule.VMSchedule;
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
import org.apache.cloudstack.schedule.ResourceScheduleManager;
import javax.inject.Inject;
import java.util.Collections;
import java.util.List;
@Deprecated
@APICommand(name = "deleteVMSchedule", description = "Delete Instance Schedule.", responseObject = SuccessResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class DeleteVMScheduleCmd extends BaseCmd {
@Inject
VMScheduleManager vmScheduleManager;
ResourceScheduleManager resourceScheduleManager;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
type = CommandType.UUID,
@ -50,12 +51,14 @@ public class DeleteVMScheduleCmd extends BaseCmd {
required = true,
description = "ID of Instance")
private Long vmId;
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
entityType = VMScheduleResponse.class,
required = false,
description = "ID of Instance schedule")
private Long id;
@Parameter(name = ApiConstants.IDS,
type = CommandType.LIST,
collectionType = CommandType.UUID,
@ -64,9 +67,9 @@ public class DeleteVMScheduleCmd extends BaseCmd {
description = "IDs of Instance schedule")
private List<Long> ids;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
/// //////////////////////////////////////////////////
/// //////////////// Accessors ///////////////////////
/// //////////////////////////////////////////////////
public Long getId() {
return id;
@ -83,18 +86,21 @@ public class DeleteVMScheduleCmd extends BaseCmd {
return vmId;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
/// //////////////////////////////////////////////////
/// //////////// API Implementation///////////////////
/// //////////////////////////////////////////////////
@Override
public void execute() {
long rowsRemoved = vmScheduleManager.removeSchedule(this);
String resourceIdStr = getVmId() != null ? String.valueOf(getVmId()) : null;
long rowsRemoved = resourceScheduleManager.removeSchedule(
ApiCommandResourceType.VirtualMachine,
resourceIdStr, getId(), getIds());
if (rowsRemoved > 0) {
final SuccessResponse response = new SuccessResponse();
response.setResponseName(getCommandName());
response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
response.setObjectName("vmschedule");
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete Instance Schedules");

View File

@ -20,23 +20,27 @@ package org.apache.cloudstack.api.command.user.vm;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
import org.apache.cloudstack.vm.schedule.VMSchedule;
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
import org.apache.cloudstack.schedule.ResourceScheduleManager;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
@Deprecated
@APICommand(name = "listVMSchedule", description = "List Instance Schedules.", responseObject = VMScheduleResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class ListVMScheduleCmd extends BaseListCmd {
@Inject
VMScheduleManager vmScheduleManager;
ResourceScheduleManager resourceScheduleManager;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
type = CommandType.UUID,
@ -61,12 +65,12 @@ public class ListVMScheduleCmd extends BaseListCmd {
@Parameter(name = ApiConstants.ENABLED,
type = CommandType.BOOLEAN,
required = false,
description = "ID of Instance schedule")
description = "Filter by enabled status")
private Boolean enabled;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
/// //////////////////////////////////////////////////
/// //////////////// Accessors ///////////////////////
/// //////////////////////////////////////////////////
public Long getVmId() {
return vmId;
@ -84,14 +88,26 @@ public class ListVMScheduleCmd extends BaseListCmd {
return enabled;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
/// //////////////////////////////////////////////////
/// //////////// API Implementation///////////////////
/// //////////////////////////////////////////////////
@Override
public void execute() {
ListResponse<VMScheduleResponse> response = vmScheduleManager.listSchedule(this);
String resourceIdStr = getVmId() != null ? String.valueOf(getVmId()) : null;
ListResponse<ResourceScheduleResponse> scheduleResponse = resourceScheduleManager.listSchedule(
getId(), null, ApiCommandResourceType.VirtualMachine, resourceIdStr, getAction(), getEnabled(),
getStartIndex(), getPageSizeVal()
);
List<VMScheduleResponse> vmScheduleResponses = new ArrayList<>();
for (ResourceScheduleResponse resourceScheduleResponse : scheduleResponse.getResponses()) {
vmScheduleResponses.add(new VMScheduleResponse(resourceScheduleResponse));
}
ListResponse<VMScheduleResponse> response = new ListResponse<>();
response.setResponses(vmScheduleResponses, scheduleResponse.getCount());
response.setResponseName(getCommandName());
response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
response.setObjectName("vmschedule");
setResponseObject(response);
}
}

View File

@ -22,22 +22,25 @@ import com.cloud.exception.InvalidParameterValueException;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
import org.apache.cloudstack.vm.schedule.VMSchedule;
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
import org.apache.cloudstack.schedule.ResourceSchedule;
import org.apache.cloudstack.schedule.ResourceScheduleManager;
import javax.inject.Inject;
import java.util.Date;
@Deprecated
@APICommand(name = "updateVMSchedule", description = "Update Instance Schedule.", responseObject = VMScheduleResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class UpdateVMScheduleCmd extends BaseCmd {
@Inject
VMScheduleManager vmScheduleManager;
ResourceScheduleManager resourceScheduleManager;
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
@ -68,14 +71,14 @@ public class UpdateVMScheduleCmd extends BaseCmd {
type = CommandType.DATE,
required = false,
description = "Start date from which the schedule becomes active"
+ "Use format \"yyyy-MM-dd hh:mm:ss\")")
+ "Use format \"yyyy-MM-dd hh:mm:ss\"")
private Date startDate;
@Parameter(name = ApiConstants.END_DATE,
type = CommandType.DATE,
required = false,
description = "End date after which the schedule becomes inactive"
+ "Use format \"yyyy-MM-dd hh:mm:ss\")")
+ "Use format \"yyyy-MM-dd hh:mm:ss\"")
private Date endDate;
@Parameter(name = ApiConstants.ENABLED,
@ -84,9 +87,9 @@ public class UpdateVMScheduleCmd extends BaseCmd {
description = "Enable Instance schedule")
private Boolean enabled;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
/// //////////////////////////////////////////////////
/// //////////////// Accessors ///////////////////////
/// //////////////////////////////////////////////////
public Long getId() {
return id;
@ -116,24 +119,29 @@ public class UpdateVMScheduleCmd extends BaseCmd {
return enabled;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
/// //////////////////////////////////////////////////
/// //////////// API Implementation///////////////////
/// //////////////////////////////////////////////////
@Override
public void execute() {
VMScheduleResponse response = vmScheduleManager.updateSchedule(this);
ResourceScheduleResponse scheduleResponse = resourceScheduleManager.updateSchedule(
getId(), getDescription(), getSchedule(), getTimeZone(), getStartDate(), getEndDate(), getEnabled(), null);
VMScheduleResponse response = new VMScheduleResponse(scheduleResponse);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
VMSchedule vmSchedule = _entityMgr.findById(VMSchedule.class, getId());
if (vmSchedule == null) {
throw new InvalidParameterValueException(String.format("Unable to find vmSchedule by id=%d", getId()));
ResourceSchedule schedule = _entityMgr.findById(ResourceSchedule.class, getId());
if (schedule == null || !ApiCommandResourceType.VirtualMachine.equals(schedule.getResourceType())) {
throw new InvalidParameterValueException(String.format("Unable to find VM schedule by id=%d", getId()));
}
VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, schedule.getResourceId());
if (vm == null) {
throw new InvalidParameterValueException(String.format("Unable to find VM schedule by id=%d", getId()));
}
VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, vmSchedule.getVmId());
return vm.getAccountId();
}
}

View File

@ -0,0 +1,178 @@
/*
* 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.api.response;
import java.util.Date;
import java.util.Map;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.EntityReference;
import org.apache.cloudstack.schedule.ResourceSchedule;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
@EntityReference(value = ResourceSchedule.class)
public class ResourceScheduleResponse extends BaseResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "The ID of resource schedule")
private String id;
@SerializedName(ApiConstants.RESOURCE_TYPE)
@Param(description = "Type of the resource")
private ApiCommandResourceType resourceType;
@SerializedName(ApiConstants.RESOURCE_ID)
@Param(description = "ID of the resource")
private String resourceId;
@SerializedName(ApiConstants.DESCRIPTION)
@Param(description = "Description of resource schedule")
private String description;
@SerializedName(ApiConstants.SCHEDULE)
@Param(description = "Cron formatted resource schedule")
private String schedule;
@SerializedName(ApiConstants.TIMEZONE)
@Param(description = "Timezone of the schedule")
private String timeZone;
@SerializedName(ApiConstants.ACTION)
@Param(description = "Action")
private ResourceSchedule.Action action;
@SerializedName(ApiConstants.ENABLED)
@Param(description = "Resource schedule is enabled")
private boolean enabled;
@SerializedName(ApiConstants.START_DATE)
@Param(description = "Date from which the schedule is active")
private Date startDate;
@SerializedName(ApiConstants.END_DATE)
@Param(description = "Date after which the schedule becomes inactive")
private Date endDate;
@SerializedName(ApiConstants.DETAILS)
@Param(description = "Schedule details")
private Map<String, String> details;
@SerializedName(ApiConstants.CREATED)
@Param(description = "Date when the schedule was created")
private Date created;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setResourceType(ApiCommandResourceType resourceType) {
this.resourceType = resourceType;
}
public ApiCommandResourceType getResourceType() {
return resourceType;
}
public void setResourceId(String resourceId) {
this.resourceId = resourceId;
}
public String getResourceId() {
return resourceId;
}
public void setDescription(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public void setSchedule(String schedule) {
this.schedule = schedule;
}
public String getSchedule() {
return schedule;
}
public void setTimeZone(String timeZone) {
this.timeZone = timeZone;
}
public String getTimeZone() {
return timeZone;
}
public void setAction(ResourceSchedule.Action action) {
this.action = action;
}
public ResourceSchedule.Action getAction() {
return action;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean getEnabled() {
return enabled;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public Date getStartDate() {
return startDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public Date getEndDate() {
return endDate;
}
public void setDetails(Map<String, String> details) {
this.details = details;
}
public Map<String, String> getDetails() {
return details;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getCreated() {
return created;
}
}

View File

@ -23,11 +23,11 @@ import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.EntityReference;
import org.apache.cloudstack.vm.schedule.VMSchedule;
import org.apache.cloudstack.schedule.ResourceSchedule;
import java.util.Date;
@EntityReference(value = VMSchedule.class)
@EntityReference(value = ResourceSchedule.class)
public class VMScheduleResponse extends BaseResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "The ID of Instance schedule")
@ -51,7 +51,7 @@ public class VMScheduleResponse extends BaseResponse {
@SerializedName(ApiConstants.ACTION)
@Param(description = "Action")
private VMSchedule.Action action;
private ResourceSchedule.Action action;
@SerializedName(ApiConstants.ENABLED)
@Param(description = "VM schedule is enabled")
@ -69,6 +69,20 @@ public class VMScheduleResponse extends BaseResponse {
@Param(description = "Date when the schedule was created")
private Date created;
public VMScheduleResponse(ResourceScheduleResponse scheduleResponse) {
super("vmschedule");
this.id = scheduleResponse.getId();
this.vmId = scheduleResponse.getResourceId();
this.description = scheduleResponse.getDescription();
this.schedule = scheduleResponse.getSchedule();
this.timeZone = scheduleResponse.getTimeZone();
this.action = scheduleResponse.getAction();
this.enabled = scheduleResponse.getEnabled();
this.startDate = scheduleResponse.getStartDate();
this.endDate = scheduleResponse.getEndDate();
this.created = scheduleResponse.getCreated();
}
public void setId(String id) {
this.id = id;
}
@ -89,7 +103,7 @@ public class VMScheduleResponse extends BaseResponse {
this.timeZone = timeZone;
}
public void setAction(VMSchedule.Action action) {
public void setAction(ResourceSchedule.Action action) {
this.action = action;
}
@ -105,5 +119,7 @@ public class VMScheduleResponse extends BaseResponse {
this.endDate = endDate;
}
public void setCreated(Date created) {this.created = created;}
public void setCreated(Date created) {
this.created = created;
}
}

View File

@ -16,20 +16,31 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cloudstack.vm.schedule;
package org.apache.cloudstack.schedule;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import java.time.ZoneId;
import java.util.Date;
public interface VMSchedule extends Identity, InternalIdentity {
enum Action {
START, STOP, REBOOT, FORCE_STOP, FORCE_REBOOT
public interface ResourceSchedule extends Identity, InternalIdentity {
/**
* Common contract for scheduler actions. Each provider defines its own enum
* implementing this interface so the generic machinery can call {@link #name()}
* and {@link #getEventType()} without knowing the concrete type.
*/
interface Action {
String name();
String getEventType();
}
long getVmId();
ApiCommandResourceType getResourceType();
long getResourceId();
String getDescription();
@ -37,7 +48,7 @@ public interface VMSchedule extends Identity, InternalIdentity {
String getTimeZone();
Action getAction();
String getActionName();
boolean getEnabled();

View File

@ -0,0 +1,45 @@
/*
* 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;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import java.util.Date;
import java.util.List;
import java.util.Map;
public interface ResourceScheduleManager {
ResourceScheduleResponse createSchedule(ApiCommandResourceType resourceType, String resourceUuid,
String description, String schedule, String timeZone, String action,
Date startDate, Date endDate, boolean enabled, Map<String, String> details);
ResourceScheduleResponse updateSchedule(Long id, String description, String schedule, String timeZone,
Date startDate, Date endDate, Boolean enabled, Map<String, String> details);
ListResponse<ResourceScheduleResponse> listSchedule(Long id, List<Long> ids, ApiCommandResourceType resourceType,
String resourceUuid, String action, Boolean enabled,
Long startIndex, Long pageSize);
Long removeSchedule(ApiCommandResourceType resourceType, String resourceUuid, Long id, List<Long> ids);
void removeSchedulesForResource(ApiCommandResourceType resourceType, long resourceId);
}

View File

@ -16,23 +16,26 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cloudstack.vm.schedule;
package org.apache.cloudstack.schedule;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import java.util.Date;
public interface VMScheduledJob extends Identity, InternalIdentity {
long getVmId();
public interface ResourceScheduledJob extends Identity, InternalIdentity {
ApiCommandResourceType getResourceType();
long getVmScheduleId();
long getResourceId();
long getScheduleId();
Long getAsyncJobId();
void setAsyncJobId(long asyncJobId);
VMSchedule.Action getAction();
String getActionName();
Date getScheduledTime();
}

View File

@ -16,21 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cloudstack.vm.schedule.dao;
package org.apache.cloudstack.schedule.autoscale;
import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.vm.schedule.VMScheduledJobVO;
import com.cloud.event.EventTypes;
import org.apache.cloudstack.schedule.ResourceSchedule;
import java.util.Date;
import java.util.List;
public interface VMScheduledJobDao extends GenericDao<VMScheduledJobVO, Long> {
List<VMScheduledJobVO> listJobsToStart(Date currentTimestamp);
int expungeJobsForSchedules(List<Long> scheduleId, Date dateAfter);
int expungeJobsBefore(Date currentTimestamp);
VMScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp);
public enum AutoScaleScheduleAction implements ResourceSchedule.Action {
UPDATE {
@Override
public String getEventType() {
return EventTypes.EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE;
}
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.vm;
import com.cloud.event.EventTypes;
import org.apache.cloudstack.schedule.ResourceSchedule;
public enum VMScheduleAction implements ResourceSchedule.Action {
START {
@Override
public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_START; }
},
STOP {
@Override
public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_STOP; }
},
REBOOT {
@Override
public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_REBOOT; }
},
FORCE_STOP {
@Override
public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_FORCE_STOP; }
},
FORCE_REBOOT {
@Override
public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_FORCE_REBOOT; }
};
}

View File

@ -1,40 +0,0 @@
/*
* 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.vm.schedule;
import org.apache.cloudstack.api.command.user.vm.CreateVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.DeleteVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.ListVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.UpdateVMScheduleCmd;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
public interface VMScheduleManager {
VMScheduleResponse createSchedule(CreateVMScheduleCmd createVMScheduleCmd);
VMScheduleResponse createResponse(VMSchedule vmSchedule);
ListResponse<VMScheduleResponse> listSchedule(ListVMScheduleCmd listVMScheduleCmd);
VMScheduleResponse updateSchedule(UpdateVMScheduleCmd updateVMScheduleCmd);
long removeScheduleByVmId(long vmId, boolean expunge);
Long removeSchedule(DeleteVMScheduleCmd deleteVMScheduleCmd);
}

View File

@ -21,8 +21,9 @@ package org.apache.cloudstack.api.command.user.vm;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.db.EntityManager;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
import org.apache.cloudstack.schedule.ResourceScheduleManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -34,9 +35,11 @@ import org.mockito.MockitoAnnotations;
public class CreateVMScheduleCmdTest {
@Mock
public VMScheduleManager vmScheduleManager;
public ResourceScheduleManager resourceScheduleManager;
@Mock
public EntityManager entityManager;
@InjectMocks
private CreateVMScheduleCmd createVMScheduleCmd = new CreateVMScheduleCmd();
@ -59,10 +62,19 @@ public class CreateVMScheduleCmdTest {
*/
@Test
public void testSuccessfulExecution() {
VMScheduleResponse vmScheduleResponse = Mockito.mock(VMScheduleResponse.class);
Mockito.when(vmScheduleManager.createSchedule(createVMScheduleCmd)).thenReturn(vmScheduleResponse);
ResourceScheduleResponse scheduleResponse = new ResourceScheduleResponse();
scheduleResponse.setId("schedule-uuid");
scheduleResponse.setResourceId("vm-uuid");
Mockito.when(resourceScheduleManager.createSchedule(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.anyBoolean(), Mockito.any()
)).thenReturn(scheduleResponse);
createVMScheduleCmd.execute();
Assert.assertEquals(vmScheduleResponse, createVMScheduleCmd.getResponseObject());
VMScheduleResponse response = (VMScheduleResponse) createVMScheduleCmd.getResponseObject();
Assert.assertNotNull(response);
Assert.assertEquals("schedule-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "id"));
Assert.assertEquals("vm-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "vmId"));
}
/**
@ -72,7 +84,11 @@ public class CreateVMScheduleCmdTest {
*/
@Test(expected = InvalidParameterValueException.class)
public void testInvalidParameterValueException() {
Mockito.when(vmScheduleManager.createSchedule(createVMScheduleCmd)).thenThrow(InvalidParameterValueException.class);
Mockito.when(resourceScheduleManager.createSchedule(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.anyBoolean(), Mockito.any()
)).thenThrow(new InvalidParameterValueException("Invalid schedule"));
createVMScheduleCmd.execute();
}

View File

@ -23,8 +23,7 @@ import com.cloud.utils.db.EntityManager;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.vm.schedule.VMSchedule;
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
import org.apache.cloudstack.schedule.ResourceScheduleManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -36,7 +35,8 @@ import org.mockito.MockitoAnnotations;
public class DeleteVMScheduleCmdTest {
@Mock
public VMScheduleManager vmScheduleManager;
public ResourceScheduleManager resourceScheduleManager;
@Mock
public EntityManager entityManager;
@ -64,9 +64,11 @@ public class DeleteVMScheduleCmdTest {
public void testSuccessfulExecution() {
final SuccessResponse response = new SuccessResponse();
response.setResponseName(deleteVMScheduleCmd.getCommandName());
response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
response.setObjectName("vmschedule");
Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenReturn(1L);
Mockito.when(resourceScheduleManager.removeSchedule(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
)).thenReturn(1L);
deleteVMScheduleCmd.execute();
SuccessResponse actualResponse = (SuccessResponse) deleteVMScheduleCmd.getResponseObject();
Assert.assertEquals(response.getResponseName(), actualResponse.getResponseName());
@ -80,7 +82,9 @@ public class DeleteVMScheduleCmdTest {
*/
@Test(expected = ServerApiException.class)
public void testServerApiException() {
Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenReturn(0L);
Mockito.when(resourceScheduleManager.removeSchedule(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
)).thenReturn(0L);
deleteVMScheduleCmd.execute();
}
@ -91,7 +95,9 @@ public class DeleteVMScheduleCmdTest {
*/
@Test(expected = InvalidParameterValueException.class)
public void testInvalidParameterValueException() {
Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenThrow(InvalidParameterValueException.class);
Mockito.when(resourceScheduleManager.removeSchedule(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
)).thenThrow(new InvalidParameterValueException("Invalid schedule"));
deleteVMScheduleCmd.execute();
}

View File

@ -20,8 +20,9 @@ package org.apache.cloudstack.api.command.user.vm;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
import org.apache.cloudstack.schedule.ResourceScheduleManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -36,7 +37,8 @@ import java.util.Collections;
public class ListVMScheduleCmdTest {
@Mock
public VMScheduleManager vmScheduleManager;
public ResourceScheduleManager resourceScheduleManager;
@InjectMocks
private ListVMScheduleCmd listVMScheduleCmd = new ListVMScheduleCmd();
private AutoCloseable closeable;
@ -58,12 +60,14 @@ public class ListVMScheduleCmdTest {
*/
@Test
public void testEmptyResponse() {
ListResponse<VMScheduleResponse> response = new ListResponse<VMScheduleResponse>();
response.setResponses(new ArrayList<VMScheduleResponse>());
Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenReturn(response);
ListResponse<ResourceScheduleResponse> response = new ListResponse<ResourceScheduleResponse>();
response.setResponses(new ArrayList<ResourceScheduleResponse>());
Mockito.when(resourceScheduleManager.listSchedule(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
)).thenReturn(response);
listVMScheduleCmd.execute();
ListResponse<VMScheduleResponse> actualResponseObject = (ListResponse<VMScheduleResponse>) listVMScheduleCmd.getResponseObject();
Assert.assertEquals(response, actualResponseObject);
Assert.assertEquals(0L, actualResponseObject.getResponses().size());
}
@ -74,15 +78,22 @@ public class ListVMScheduleCmdTest {
*/
@Test
public void testNonEmptyResponse() {
ListResponse<VMScheduleResponse> listResponse = new ListResponse<VMScheduleResponse>();
VMScheduleResponse response = Mockito.mock(VMScheduleResponse.class);
ListResponse<ResourceScheduleResponse> listResponse = new ListResponse<ResourceScheduleResponse>();
ResourceScheduleResponse response = new ResourceScheduleResponse();
response.setId("schedule-uuid");
response.setResourceId("vm-uuid");
listResponse.setResponses(Collections.singletonList(response));
Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenReturn(listResponse);
Mockito.when(resourceScheduleManager.listSchedule(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
)).thenReturn(listResponse);
listVMScheduleCmd.execute();
ListResponse<VMScheduleResponse> actualResponseObject = (ListResponse<VMScheduleResponse>) listVMScheduleCmd.getResponseObject();
Assert.assertEquals(listResponse, actualResponseObject);
Assert.assertEquals(1L, actualResponseObject.getResponses().size());
Assert.assertEquals(response, actualResponseObject.getResponses().get(0));
Assert.assertEquals("schedule-uuid",
org.springframework.test.util.ReflectionTestUtils.getField(actualResponseObject.getResponses().get(0), "id"));
Assert.assertEquals("vm-uuid",
org.springframework.test.util.ReflectionTestUtils.getField(actualResponseObject.getResponses().get(0), "vmId"));
}
/**
@ -92,7 +103,10 @@ public class ListVMScheduleCmdTest {
*/
@Test(expected = InvalidParameterValueException.class)
public void testInvalidParameterValueException() {
Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenThrow(InvalidParameterValueException.class);
Mockito.when(resourceScheduleManager.listSchedule(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
)).thenThrow(InvalidParameterValueException.class);
listVMScheduleCmd.execute();
ListResponse<VMScheduleResponse> actualResponseObject = (ListResponse<VMScheduleResponse>) listVMScheduleCmd.getResponseObject();
}

View File

@ -21,9 +21,11 @@ package org.apache.cloudstack.api.command.user.vm;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.db.EntityManager;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
import org.apache.cloudstack.vm.schedule.VMSchedule;
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
import org.apache.cloudstack.schedule.ResourceSchedule;
import org.apache.cloudstack.schedule.ResourceScheduleManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -35,9 +37,11 @@ import org.mockito.MockitoAnnotations;
public class UpdateVMScheduleCmdTest {
@Mock
public VMScheduleManager vmScheduleManager;
public ResourceScheduleManager resourceScheduleManager;
@Mock
public EntityManager entityManager;
@InjectMocks
private UpdateVMScheduleCmd updateVMScheduleCmd = new UpdateVMScheduleCmd();
@ -60,10 +64,18 @@ public class UpdateVMScheduleCmdTest {
*/
@Test
public void testSuccessfulExecution() {
VMScheduleResponse vmScheduleResponse = Mockito.mock(VMScheduleResponse.class);
Mockito.when(vmScheduleManager.updateSchedule(updateVMScheduleCmd)).thenReturn(vmScheduleResponse);
ResourceScheduleResponse scheduleResponse = new ResourceScheduleResponse();
scheduleResponse.setId("schedule-uuid");
scheduleResponse.setResourceId("vm-uuid");
Mockito.when(resourceScheduleManager.updateSchedule(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
)).thenReturn(scheduleResponse);
updateVMScheduleCmd.execute();
Assert.assertEquals(vmScheduleResponse, updateVMScheduleCmd.getResponseObject());
VMScheduleResponse response = (VMScheduleResponse) updateVMScheduleCmd.getResponseObject();
Assert.assertNotNull(response);
Assert.assertEquals("schedule-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "id"));
Assert.assertEquals("vm-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "vmId"));
}
/**
@ -73,7 +85,10 @@ public class UpdateVMScheduleCmdTest {
*/
@Test(expected = InvalidParameterValueException.class)
public void testInvalidParameterValueException() {
Mockito.when(vmScheduleManager.updateSchedule(updateVMScheduleCmd)).thenThrow(InvalidParameterValueException.class);
Mockito.when(resourceScheduleManager.updateSchedule(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
)).thenThrow(new InvalidParameterValueException("Invalid schedule"));
updateVMScheduleCmd.execute();
}
@ -84,11 +99,12 @@ public class UpdateVMScheduleCmdTest {
*/
@Test
public void testSuccessfulGetEntityOwnerId() {
VMSchedule vmSchedule = Mockito.mock(VMSchedule.class);
ResourceSchedule schedule = Mockito.mock(ResourceSchedule.class);
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Mockito.when(entityManager.findById(VMSchedule.class, updateVMScheduleCmd.getId())).thenReturn(vmSchedule);
Mockito.when(entityManager.findById(VirtualMachine.class, vmSchedule.getVmId())).thenReturn(vm);
Mockito.when(schedule.getResourceType()).thenReturn(ApiCommandResourceType.VirtualMachine);
Mockito.when(entityManager.findById(ResourceSchedule.class, updateVMScheduleCmd.getId())).thenReturn(schedule);
Mockito.when(entityManager.findById(VirtualMachine.class, schedule.getResourceId())).thenReturn(vm);
long ownerId = updateVMScheduleCmd.getEntityOwnerId();
Assert.assertEquals(vm.getAccountId(), ownerId);
@ -101,8 +117,7 @@ public class UpdateVMScheduleCmdTest {
*/
@Test(expected = InvalidParameterValueException.class)
public void testFailureGetEntityOwnerId() {
VMSchedule vmSchedule = Mockito.mock(VMSchedule.class);
Mockito.when(entityManager.findById(VMSchedule.class, updateVMScheduleCmd.getId())).thenReturn(null);
long ownerId = updateVMScheduleCmd.getEntityOwnerId();
Mockito.when(entityManager.findById(ResourceSchedule.class, updateVMScheduleCmd.getId())).thenReturn(null);
updateVMScheduleCmd.getEntityOwnerId();
}
}

View File

@ -0,0 +1,82 @@
// 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;
import org.apache.cloudstack.api.ResourceDetail;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "resource_schedule_details")
public class ResourceScheduleDetailVO implements ResourceDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private long id;
@Column(name = "schedule_id")
private long resourceId;
@Column(name = "name")
private String name;
@Column(name = "value", length = 1024)
private String value;
@Column(name = "display")
private boolean display = true;
public ResourceScheduleDetailVO() {
}
public ResourceScheduleDetailVO(long scheduleId, String name, String value, boolean display) {
this.resourceId = scheduleId;
this.name = name;
this.value = value;
this.display = display;
}
@Override
public long getId() {
return id;
}
@Override
public long getResourceId() {
return resourceId;
}
@Override
public String getName() {
return name;
}
@Override
public String getValue() {
return value;
}
@Override
public boolean isDisplay() {
return display;
}
}

View File

@ -16,9 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cloudstack.vm.schedule;
package org.apache.cloudstack.schedule;
import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import javax.persistence.Column;
@ -37,8 +38,8 @@ import java.util.TimeZone;
import java.util.UUID;
@Entity
@Table(name = "vm_schedule")
public class VMScheduleVO implements VMSchedule {
@Table(name = "resource_schedule")
public class ResourceScheduleVO implements ResourceSchedule {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
@ -50,8 +51,12 @@ public class VMScheduleVO implements VMSchedule {
@Column(name = "description")
String description;
@Column(name = "vm_id", nullable = false)
long vmId;
@Enumerated(value = EnumType.STRING)
@Column(name = "resource_type", nullable = false)
ApiCommandResourceType resourceType;
@Column(name = "resource_id", nullable = false)
long resourceId;
@Column(name = "schedule", nullable = false)
String schedule;
@ -60,8 +65,7 @@ public class VMScheduleVO implements VMSchedule {
String timeZone;
@Column(name = "action", nullable = false)
@Enumerated(value = EnumType.STRING)
Action action;
String actionName;
@Column(name = "enabled", nullable = false)
boolean enabled;
@ -80,17 +84,19 @@ public class VMScheduleVO implements VMSchedule {
@Column(name = GenericDao.REMOVED_COLUMN)
Date removed;
public VMScheduleVO() {
public ResourceScheduleVO() {
uuid = UUID.randomUUID().toString();
}
public VMScheduleVO(long vmId, String description, String schedule, String timeZone, Action action, Date startDate, Date endDate, boolean enabled) {
public ResourceScheduleVO(ApiCommandResourceType resourceType, long resourceId, String description, String schedule,
String timeZone, String action, Date startDate, Date endDate, boolean enabled) {
uuid = UUID.randomUUID().toString();
this.vmId = vmId;
this.resourceType = resourceType;
this.resourceId = resourceId;
this.description = description;
this.schedule = schedule;
this.timeZone = timeZone;
this.action = action;
this.actionName = action;
this.startDate = startDate;
this.endDate = endDate;
this.enabled = enabled;
@ -98,7 +104,8 @@ public class VMScheduleVO implements VMSchedule {
@Override
public String toString() {
return String.format("VMSchedule %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "uuid", "action", "description"));
return String.format("ResourceSchedule %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
this, "id", "uuid", "resourceType", "actionName", "description"));
}
@Override
@ -111,14 +118,25 @@ public class VMScheduleVO implements VMSchedule {
return id;
}
public long getVmId() {
return vmId;
@Override
public ApiCommandResourceType getResourceType() {
return resourceType;
}
public void setVmId(long vmId) {
this.vmId = vmId;
public void setResourceType(ApiCommandResourceType resourceType) {
this.resourceType = resourceType;
}
@Override
public long getResourceId() {
return resourceId;
}
public void setResourceId(long resourceId) {
this.resourceId = resourceId;
}
@Override
public String getDescription() {
return description;
}
@ -127,6 +145,7 @@ public class VMScheduleVO implements VMSchedule {
this.description = description;
}
@Override
public String getSchedule() {
return schedule.substring(2);
}
@ -144,14 +163,16 @@ public class VMScheduleVO implements VMSchedule {
this.timeZone = timeZone;
}
public Action getAction() {
return action;
@Override
public String getActionName() {
return actionName;
}
public void setAction(Action action) {
this.action = action;
public void setActionName(String action) {
this.actionName = action;
}
@Override
public boolean getEnabled() {
return enabled;
}
@ -183,6 +204,7 @@ public class VMScheduleVO implements VMSchedule {
return TimeZone.getTimeZone(getTimeZone()).toZoneId();
}
@Override
public Date getCreated() {
return created;
}

View File

@ -16,8 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cloudstack.vm.schedule;
package org.apache.cloudstack.schedule;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import javax.persistence.Column;
@ -34,8 +35,8 @@ import java.util.Date;
import java.util.UUID;
@Entity
@Table(name = "vm_scheduled_job")
public class VMScheduledJobVO implements VMScheduledJob {
@Table(name = "resource_scheduled_job")
public class ResourceScheduledJobVO implements ResourceScheduledJob {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@ -44,41 +45,44 @@ public class VMScheduledJobVO implements VMScheduledJob {
@Column(name = "uuid", nullable = false)
String uuid;
@Column(name = "vm_id", nullable = false)
long vmId;
@Enumerated(value = EnumType.STRING)
@Column(name = "resource_type", nullable = false)
ApiCommandResourceType resourceType;
@Column(name = "vm_schedule_id", nullable = false)
long vmScheduleId;
@Column(name = "resource_id", nullable = false)
long resourceId;
@Column(name = "schedule_id", nullable = false)
long scheduleId;
@Column(name = "async_job_id")
Long asyncJobId;
@Column(name = "action", nullable = false)
@Enumerated(value = EnumType.STRING)
VMSchedule.Action action;
String actionName;
@Column(name = "scheduled_timestamp")
@Temporal(value = TemporalType.TIMESTAMP)
Date scheduledTime;
public VMScheduledJobVO() {
public ResourceScheduledJobVO() {
uuid = UUID.randomUUID().toString();
}
public VMScheduledJobVO(long vmId, long vmScheduleId, VMSchedule.Action action, Date scheduledTime) {
public ResourceScheduledJobVO(ApiCommandResourceType resourceType, long resourceId, long scheduleId, String action, Date scheduledTime) {
uuid = UUID.randomUUID().toString();
this.vmId = vmId;
this.vmScheduleId = vmScheduleId;
this.action = action;
this.resourceType = resourceType;
this.resourceId = resourceId;
this.scheduleId = scheduleId;
this.actionName = action;
this.scheduledTime = scheduledTime;
}
@Override
public String toString() {
return String.format("VMScheduledJob %s",
return String.format("ResourceScheduledJob %s",
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
this, "id", "uuid", "action", "vmScheduleId", "vmId", "asyncJobId"));
this, "id", "uuid", "resourceType", "actionName", "scheduleId", "resourceId", "asyncJobId"));
}
@Override
@ -92,13 +96,18 @@ public class VMScheduledJobVO implements VMScheduledJob {
}
@Override
public long getVmId() {
return vmId;
public ApiCommandResourceType getResourceType() {
return resourceType;
}
@Override
public long getVmScheduleId() {
return vmScheduleId;
public long getResourceId() {
return resourceId;
}
@Override
public long getScheduleId() {
return scheduleId;
}
@Override
@ -112,8 +121,12 @@ public class VMScheduledJobVO implements VMScheduledJob {
}
@Override
public VMSchedule.Action getAction() {
return action;
public String getActionName() {
return actionName;
}
public void setActionName(String action) {
this.actionName = action;
}
@Override

View File

@ -0,0 +1,40 @@
/*
* 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.dao;
import com.cloud.utils.Pair;
import com.cloud.utils.db.GenericDao;
import com.cloud.utils.db.SearchCriteria;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.schedule.ResourceScheduleVO;
import java.util.List;
public interface ResourceScheduleDao extends GenericDao<ResourceScheduleVO, Long> {
List<ResourceScheduleVO> listAllActiveSchedules(ApiCommandResourceType resourceType);
long removeSchedulesForResourceAndIds(ApiCommandResourceType resourceType, long resourceId, List<Long> ids);
long removeAllSchedulesForResource(ApiCommandResourceType resourceType, long resourceId);
Pair<List<ResourceScheduleVO>, Integer> searchAndCount(List<Long> ids, ApiCommandResourceType resourceType, Long resourceId,
String action, Boolean enabled, Long offset, Long limit);
SearchCriteria<ResourceScheduleVO> getSearchCriteriaForResource(ApiCommandResourceType resourceType, long resourceId);
}

View File

@ -0,0 +1,114 @@
/*
* 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.dao;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.schedule.ResourceScheduleVO;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
@Component
public class ResourceScheduleDaoImpl extends GenericDaoBase<ResourceScheduleVO, Long> implements ResourceScheduleDao {
private final SearchBuilder<ResourceScheduleVO> activeScheduleSearch;
private final SearchBuilder<ResourceScheduleVO> allSearch;
static final String RESOURCE_TYPE = "resourceType";
static final String RESOURCE_ID = "resourceId";
public ResourceScheduleDaoImpl() {
super();
activeScheduleSearch = createSearchBuilder();
activeScheduleSearch.and(RESOURCE_TYPE, activeScheduleSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
activeScheduleSearch.and(ApiConstants.ENABLED, activeScheduleSearch.entity().getEnabled(), SearchCriteria.Op.EQ);
activeScheduleSearch.and().op(activeScheduleSearch.entity().getEndDate(), SearchCriteria.Op.NULL);
activeScheduleSearch.or(ApiConstants.END_DATE, activeScheduleSearch.entity().getEndDate(), SearchCriteria.Op.GT);
activeScheduleSearch.cp();
activeScheduleSearch.done();
allSearch = createSearchBuilder();
allSearch.and(ApiConstants.ID, allSearch.entity().getId(), SearchCriteria.Op.IN);
allSearch.and(RESOURCE_TYPE, allSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
allSearch.and(RESOURCE_ID, allSearch.entity().getResourceId(), SearchCriteria.Op.EQ);
allSearch.and(ApiConstants.ACTION, allSearch.entity().getActionName(), SearchCriteria.Op.EQ);
allSearch.and(ApiConstants.ENABLED, allSearch.entity().getEnabled(), SearchCriteria.Op.EQ);
allSearch.done();
}
@Override
public List<ResourceScheduleVO> listAllActiveSchedules(ApiCommandResourceType resourceType) {
SearchCriteria<ResourceScheduleVO> sc = activeScheduleSearch.create();
sc.setParameters(RESOURCE_TYPE, resourceType);
sc.setParameters(ApiConstants.ENABLED, true);
sc.setParameters(ApiConstants.END_DATE, new Date());
return search(sc, null);
}
@Override
public long removeSchedulesForResourceAndIds(ApiCommandResourceType resourceType, long resourceId, List<Long> ids) {
SearchCriteria<ResourceScheduleVO> sc = allSearch.create();
if (CollectionUtils.isNotEmpty(ids)) {
sc.setParameters(ApiConstants.ID, ids.toArray());
}
sc.setParameters(RESOURCE_TYPE, resourceType);
sc.setParameters(RESOURCE_ID, resourceId);
return remove(sc);
}
@Override
public long removeAllSchedulesForResource(ApiCommandResourceType resourceType, long resourceId) {
SearchCriteria<ResourceScheduleVO> sc = allSearch.create();
sc.setParameters(RESOURCE_TYPE, resourceType);
sc.setParameters(RESOURCE_ID, resourceId);
return remove(sc);
}
@Override
public Pair<List<ResourceScheduleVO>, Integer> searchAndCount(List<Long> ids, ApiCommandResourceType resourceType, Long resourceId,
String action, Boolean enabled, Long offset, Long limit) {
SearchCriteria<ResourceScheduleVO> sc = allSearch.create();
if (CollectionUtils.isNotEmpty(ids)) {
sc.setParameters(ApiConstants.ID, ids.toArray());
}
sc.setParametersIfNotNull(ApiConstants.ENABLED, enabled);
sc.setParametersIfNotNull(ApiConstants.ACTION, action);
sc.setParametersIfNotNull(RESOURCE_TYPE, resourceType);
sc.setParametersIfNotNull(RESOURCE_ID, resourceId);
Filter filter = new Filter(ResourceScheduleVO.class, ApiConstants.ID, false, offset, limit);
return searchAndCount(sc, filter);
}
@Override
public SearchCriteria<ResourceScheduleVO> getSearchCriteriaForResource(ApiCommandResourceType resourceType, long resourceId) {
SearchCriteria<ResourceScheduleVO> sc = allSearch.create();
sc.setParameters(RESOURCE_TYPE, resourceType);
sc.setParameters(RESOURCE_ID, resourceId);
return sc;
}
}

View File

@ -0,0 +1,24 @@
// 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.dao;
import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.resourcedetail.ResourceDetailsDao;
import org.apache.cloudstack.schedule.ResourceScheduleDetailVO;
public interface ResourceScheduleDetailsDao extends GenericDao<ResourceScheduleDetailVO, Long>, ResourceDetailsDao<ResourceScheduleDetailVO> {
}

View File

@ -0,0 +1,28 @@
// 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.dao;
import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase;
import org.apache.cloudstack.schedule.ResourceScheduleDetailVO;
public class ResourceScheduleDetailsDaoImpl extends ResourceDetailsDaoBase<ResourceScheduleDetailVO> implements ResourceScheduleDetailsDao {
@Override
public void addDetail(long resourceId, String key, String value, boolean display) {
super.addDetail(new ResourceScheduleDetailVO(resourceId, key, value, display));
}
}

View File

@ -16,22 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cloudstack.vm.schedule.dao;
package org.apache.cloudstack.schedule.dao;
import com.cloud.utils.Pair;
import com.cloud.utils.db.GenericDao;
import com.cloud.utils.db.SearchCriteria;
import org.apache.cloudstack.vm.schedule.VMSchedule;
import org.apache.cloudstack.vm.schedule.VMScheduleVO;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.schedule.ResourceScheduledJobVO;
import java.util.Date;
import java.util.List;
public interface VMScheduleDao extends GenericDao<VMScheduleVO, Long> {
List<VMScheduleVO> listAllActiveSchedules();
public interface ResourceScheduledJobDao extends GenericDao<ResourceScheduledJobVO, Long> {
List<ResourceScheduledJobVO> listJobsToStart(ApiCommandResourceType resourceType, Date currentTimestamp);
long removeSchedulesForVmIdAndIds(Long vmId, List<Long> ids);
int expungeJobsForSchedules(List<Long> scheduleIds, Date dateAfter);
Pair<List<VMScheduleVO>, Integer> searchAndCount(Long id, Long vmId, VMSchedule.Action action, Boolean enabled, Long offset, Long limit);
int expungeJobsBefore(ApiCommandResourceType resourceType, Date currentTimestamp);
SearchCriteria<VMScheduleVO> getSearchCriteriaForVMId(Long vmId);
ResourceScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp);
}

View File

@ -16,13 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cloudstack.vm.schedule.dao;
package org.apache.cloudstack.schedule.dao;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import org.apache.cloudstack.vm.schedule.VMScheduledJobVO;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.schedule.ResourceScheduledJobVO;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.stereotype.Component;
@ -31,62 +32,59 @@ import java.util.Date;
import java.util.List;
@Component
public class VMScheduledJobDaoImpl extends GenericDaoBase<VMScheduledJobVO, Long> implements VMScheduledJobDao {
public class ResourceScheduledJobDaoImpl extends GenericDaoBase<ResourceScheduledJobVO, Long> implements ResourceScheduledJobDao {
private final SearchBuilder<VMScheduledJobVO> jobsToStartSearch;
private final SearchBuilder<ResourceScheduledJobVO> jobsToStartSearch;
private final SearchBuilder<ResourceScheduledJobVO> expungeJobsBeforeSearch;
private final SearchBuilder<ResourceScheduledJobVO> expungeJobForScheduleSearch;
private final SearchBuilder<ResourceScheduledJobVO> scheduleAndTimestampSearch;
private final SearchBuilder<VMScheduledJobVO> expungeJobsBeforeSearch;
static final String SCHEDULED_TIMESTAMP = "scheduledTimestamp";
static final String SCHEDULE_ID = "scheduleId";
static final String RESOURCE_TYPE = "resourceType";
private final SearchBuilder<VMScheduledJobVO> expungeJobForScheduleSearch;
private final SearchBuilder<VMScheduledJobVO> scheduleAndTimestampSearch;
static final String SCHEDULED_TIMESTAMP = "scheduled_timestamp";
static final String VM_SCHEDULE_ID = "vm_schedule_id";
public VMScheduledJobDaoImpl() {
public ResourceScheduledJobDaoImpl() {
super();
jobsToStartSearch = createSearchBuilder();
jobsToStartSearch.and(RESOURCE_TYPE, jobsToStartSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
jobsToStartSearch.and(SCHEDULED_TIMESTAMP, jobsToStartSearch.entity().getScheduledTime(), SearchCriteria.Op.EQ);
jobsToStartSearch.and("async_job_id", jobsToStartSearch.entity().getAsyncJobId(), SearchCriteria.Op.NULL);
jobsToStartSearch.done();
expungeJobsBeforeSearch = createSearchBuilder();
expungeJobsBeforeSearch.and(RESOURCE_TYPE, expungeJobsBeforeSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
expungeJobsBeforeSearch.and(SCHEDULED_TIMESTAMP, expungeJobsBeforeSearch.entity().getScheduledTime(), SearchCriteria.Op.LT);
expungeJobsBeforeSearch.done();
expungeJobForScheduleSearch = createSearchBuilder();
expungeJobForScheduleSearch.and(VM_SCHEDULE_ID, expungeJobForScheduleSearch.entity().getVmScheduleId(), SearchCriteria.Op.IN);
expungeJobForScheduleSearch.and(SCHEDULE_ID, expungeJobForScheduleSearch.entity().getScheduleId(), SearchCriteria.Op.IN);
expungeJobForScheduleSearch.and(SCHEDULED_TIMESTAMP, expungeJobForScheduleSearch.entity().getScheduledTime(), SearchCriteria.Op.GTEQ);
expungeJobForScheduleSearch.done();
scheduleAndTimestampSearch = createSearchBuilder();
scheduleAndTimestampSearch.and(VM_SCHEDULE_ID, scheduleAndTimestampSearch.entity().getVmScheduleId(), SearchCriteria.Op.EQ);
scheduleAndTimestampSearch.and(SCHEDULE_ID, scheduleAndTimestampSearch.entity().getScheduleId(), SearchCriteria.Op.EQ);
scheduleAndTimestampSearch.and(SCHEDULED_TIMESTAMP, scheduleAndTimestampSearch.entity().getScheduledTime(), SearchCriteria.Op.EQ);
scheduleAndTimestampSearch.done();
}
/**
* Execution of job wouldn't be at exact seconds. So, we round off and then execute.
*/
@Override
public List<VMScheduledJobVO> listJobsToStart(Date currentTimestamp) {
public List<ResourceScheduledJobVO> listJobsToStart(ApiCommandResourceType resourceType, Date currentTimestamp) {
if (currentTimestamp == null) {
currentTimestamp = new Date();
}
Date truncatedTs = DateUtils.round(currentTimestamp, Calendar.MINUTE);
SearchCriteria<VMScheduledJobVO> sc = jobsToStartSearch.create();
SearchCriteria<ResourceScheduledJobVO> sc = jobsToStartSearch.create();
sc.setParameters(RESOURCE_TYPE, resourceType);
sc.setParameters(SCHEDULED_TIMESTAMP, truncatedTs);
Filter filter = new Filter(VMScheduledJobVO.class, "vmScheduleId", true, null, null);
Filter filter = new Filter(ResourceScheduledJobVO.class, "scheduleId", true, null, null);
return search(sc, filter);
}
@Override
public int expungeJobsForSchedules(List<Long> vmScheduleIds, Date dateAfter) {
SearchCriteria<VMScheduledJobVO> sc = expungeJobForScheduleSearch.create();
sc.setParameters(VM_SCHEDULE_ID, vmScheduleIds.toArray());
public int expungeJobsForSchedules(List<Long> scheduleIds, Date dateAfter) {
SearchCriteria<ResourceScheduledJobVO> sc = expungeJobForScheduleSearch.create();
sc.setParameters(SCHEDULE_ID, scheduleIds.toArray());
if (dateAfter != null) {
sc.setParameters(SCHEDULED_TIMESTAMP, dateAfter);
}
@ -94,16 +92,17 @@ public class VMScheduledJobDaoImpl extends GenericDaoBase<VMScheduledJobVO, Long
}
@Override
public int expungeJobsBefore(Date date) {
SearchCriteria<VMScheduledJobVO> sc = expungeJobsBeforeSearch.create();
public int expungeJobsBefore(ApiCommandResourceType resourceType, Date date) {
SearchCriteria<ResourceScheduledJobVO> sc = expungeJobsBeforeSearch.create();
sc.setParameters(RESOURCE_TYPE, resourceType);
sc.setParameters(SCHEDULED_TIMESTAMP, date);
return expunge(sc);
}
@Override
public VMScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp) {
SearchCriteria<VMScheduledJobVO> sc = scheduleAndTimestampSearch.create();
sc.setParameters(VM_SCHEDULE_ID, scheduleId);
public ResourceScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp) {
SearchCriteria<ResourceScheduledJobVO> sc = scheduleAndTimestampSearch.create();
sc.setParameters(SCHEDULE_ID, scheduleId);
sc.setParameters(SCHEDULED_TIMESTAMP, scheduledTimestamp);
return findOneBy(sc);
}

View File

@ -1,108 +0,0 @@
/*
* 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.vm.schedule.dao;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.vm.schedule.VMSchedule;
import org.apache.cloudstack.vm.schedule.VMScheduleVO;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
@Component
public class VMScheduleDaoImpl extends GenericDaoBase<VMScheduleVO, Long> implements VMScheduleDao {
private final SearchBuilder<VMScheduleVO> activeScheduleSearch;
private final SearchBuilder<VMScheduleVO> scheduleSearchByVmIdAndIds;
private final SearchBuilder<VMScheduleVO> scheduleSearch;
public VMScheduleDaoImpl() {
super();
activeScheduleSearch = createSearchBuilder();
activeScheduleSearch.and(ApiConstants.ENABLED, activeScheduleSearch.entity().getEnabled(), SearchCriteria.Op.EQ);
activeScheduleSearch.and().op(activeScheduleSearch.entity().getEndDate(), SearchCriteria.Op.NULL);
activeScheduleSearch.or(ApiConstants.END_DATE, activeScheduleSearch.entity().getEndDate(), SearchCriteria.Op.GT);
activeScheduleSearch.cp();
activeScheduleSearch.done();
scheduleSearchByVmIdAndIds = createSearchBuilder();
scheduleSearchByVmIdAndIds.and(ApiConstants.ID, scheduleSearchByVmIdAndIds.entity().getId(), SearchCriteria.Op.IN);
scheduleSearchByVmIdAndIds.and(ApiConstants.VIRTUAL_MACHINE_ID, scheduleSearchByVmIdAndIds.entity().getVmId(), SearchCriteria.Op.EQ);
scheduleSearchByVmIdAndIds.done();
scheduleSearch = createSearchBuilder();
scheduleSearch.and(ApiConstants.ID, scheduleSearch.entity().getId(), SearchCriteria.Op.EQ);
scheduleSearch.and(ApiConstants.VIRTUAL_MACHINE_ID, scheduleSearch.entity().getVmId(), SearchCriteria.Op.EQ);
scheduleSearch.and(ApiConstants.ACTION, scheduleSearch.entity().getAction(), SearchCriteria.Op.EQ);
scheduleSearch.and(ApiConstants.ENABLED, scheduleSearch.entity().getEnabled(), SearchCriteria.Op.EQ);
scheduleSearch.done();
}
@Override
public List<VMScheduleVO> listAllActiveSchedules() {
// WHERE enabled = true AND (end_date IS NULL OR end_date > current_date)
SearchCriteria<VMScheduleVO> sc = activeScheduleSearch.create();
sc.setParameters(ApiConstants.ENABLED, true);
sc.setParameters(ApiConstants.END_DATE, new Date());
return search(sc, null);
}
@Override
public long removeSchedulesForVmIdAndIds(Long vmId, List<Long> ids) {
SearchCriteria<VMScheduleVO> sc = scheduleSearchByVmIdAndIds.create();
sc.setParameters(ApiConstants.ID, ids.toArray());
sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId);
return remove(sc);
}
@Override
public Pair<List<VMScheduleVO>, Integer> searchAndCount(Long id, Long vmId, VMSchedule.Action action, Boolean enabled, Long offset, Long limit) {
SearchCriteria<VMScheduleVO> sc = scheduleSearch.create();
if (id != null) {
sc.setParameters(ApiConstants.ID, id);
}
if (enabled != null) {
sc.setParameters(ApiConstants.ENABLED, enabled);
}
if (action != null) {
sc.setParameters(ApiConstants.ACTION, action);
}
sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId);
Filter filter = new Filter(VMScheduleVO.class, ApiConstants.ID, false, offset, limit);
return searchAndCount(sc, filter);
}
@Override
public SearchCriteria<VMScheduleVO> getSearchCriteriaForVMId(Long vmId) {
SearchCriteria<VMScheduleVO> sc = scheduleSearch.create();
sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId);
return sc;
}
}

View File

@ -282,8 +282,9 @@
<bean id="secondaryStorageHeuristicDaoImpl" class="org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDaoImpl" />
<bean id="heuristicRuleHelper" class="org.apache.cloudstack.storage.heuristics.HeuristicRuleHelper" />
<bean id="publicIpQuarantineDaoImpl" class="com.cloud.network.dao.PublicIpQuarantineDaoImpl" />
<bean id="VMScheduleDaoImpl" class="org.apache.cloudstack.vm.schedule.dao.VMScheduleDaoImpl" />
<bean id="VMScheduledJobDaoImpl" class="org.apache.cloudstack.vm.schedule.dao.VMScheduledJobDaoImpl" />
<bean id="ResourceScheduleDaoImpl" class="org.apache.cloudstack.schedule.dao.ResourceScheduleDaoImpl" />
<bean id="ResourceScheduleDetailsDaoImpl" class="org.apache.cloudstack.schedule.dao.ResourceScheduleDetailsDaoImpl" />
<bean id="ResourceScheduledJobDaoImpl" class="org.apache.cloudstack.schedule.dao.ResourceScheduledJobDaoImpl" />
<bean id="VmwareDatacenterDaoImpl" class="com.cloud.hypervisor.vmware.dao.VmwareDatacenterDaoImpl" />
<bean id="vnfTemplateDetailsDaoImpl" class="com.cloud.storage.dao.VnfTemplateDetailsDaoImpl" />
<bean id="vnfTemplateNicDaoImpl" class="com.cloud.storage.dao.VnfTemplateNicDaoImpl" />

View File

@ -131,3 +131,58 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` (
-- Add the 'keep_mac_address_on_public_nic' column to the 'cloud.networks' and 'cloud.vpc' tables
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.networks', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1');
-- Generalise VM schedule tables into resource-agnostic resource_schedule / resource_scheduled_job.
-- Step 1: rename vm_schedule → resource_schedule, rename vm_id → resource_id, add resource_type column.
ALTER TABLE `cloud`.`vm_schedule`
DROP FOREIGN KEY `fk_vm_schedule__vm_id`,
DROP INDEX `i_vm_schedule__vm_id`,
DROP INDEX `i_vm_schedule__enabled_end_date`,
CHANGE COLUMN `vm_id` `resource_id` bigint unsigned NOT NULL COMMENT 'id of the scheduled resource',
ADD COLUMN `resource_type` varchar(64) NOT NULL DEFAULT 'VirtualMachine' COMMENT 'type of the scheduled resource' AFTER `uuid`;
RENAME TABLE `cloud`.`vm_schedule` TO `cloud`.`resource_schedule`;
ALTER TABLE `cloud`.`resource_schedule`
ADD INDEX `i_resource_schedule__resource` (`resource_type`, `resource_id`),
ADD INDEX `i_resource_schedule__enabled_end_date` (`enabled`, `end_date`);
-- Step 2: rename vm_scheduled_job → resource_scheduled_job, rename columns.
ALTER TABLE `cloud`.`vm_scheduled_job`
DROP FOREIGN KEY `fk_vm_scheduled_job__vm_id`,
DROP FOREIGN KEY `fk_vm_scheduled_job__vm_schedule_id`,
DROP INDEX `i_vm_scheduled_job__vm_id`,
DROP INDEX `i_vm_scheduled_job__scheduled_timestamp`,
DROP INDEX `vm_schedule_id`,
CHANGE COLUMN `vm_id` `resource_id` bigint unsigned NOT NULL COMMENT 'id of the scheduled resource',
CHANGE COLUMN `vm_schedule_id` `schedule_id` bigint unsigned NOT NULL COMMENT 'id of the resource_schedule row',
ADD COLUMN `resource_type` varchar(64) NOT NULL DEFAULT 'VirtualMachine' COMMENT 'type of the scheduled resource' AFTER `uuid`;
RENAME TABLE `cloud`.`vm_scheduled_job` TO `cloud`.`resource_scheduled_job`;
ALTER TABLE `cloud`.`resource_scheduled_job`
ADD UNIQUE KEY `uc_resource_scheduled_job__schedule_timestamp` (`schedule_id`, `scheduled_timestamp`),
ADD INDEX `i_resource_scheduled_job__resource` (`resource_type`, `resource_id`),
ADD INDEX `i_resource_scheduled_job__scheduled_timestamp` (`scheduled_timestamp`),
ADD CONSTRAINT `fk_resource_scheduled_job__schedule_id` FOREIGN KEY (`schedule_id`) REFERENCES `resource_schedule`(`id`) ON DELETE CASCADE;
-- Step 3: details table for action-specific parameters (used by the generic resource schedule API in Commit 2).
CREATE TABLE IF NOT EXISTS `cloud`.`resource_schedule_details` (
`id` bigint unsigned NOT NULL auto_increment,
`schedule_id` bigint unsigned NOT NULL COMMENT 'id of the resource_schedule row',
`name` varchar(255) NOT NULL,
`value` varchar(1024) NOT NULL,
`display` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'should this detail be visible to the end user',
PRIMARY KEY (`id`),
INDEX `i_resource_schedule_details__schedule_id` (`schedule_id`),
CONSTRAINT `fk_resource_schedule_details__schedule_id` FOREIGN KEY (`schedule_id`) REFERENCES `resource_schedule`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Step 4: rename CRUD event types from VM.SCHEDULE.{CREATE,UPDATE,DELETE} to the new generic SCHEDULE.{CREATE,UPDATE,DELETE}.
-- Action-execution events (VM.SCHEDULE.START, .STOP, .REBOOT, .FORCE_STOP, .FORCE_REBOOT) keep their existing names.
UPDATE `cloud`.`event` SET `type` = 'SCHEDULE.CREATE' WHERE `type` = 'VM.SCHEDULE.CREATE';
UPDATE `cloud`.`event` SET `type` = 'SCHEDULE.UPDATE' WHERE `type` = 'VM.SCHEDULE.UPDATE';
UPDATE `cloud`.`event` SET `type` = 'SCHEDULE.DELETE' WHERE `type` = 'VM.SCHEDULE.DELETE';
-- Step 5: Rename the global configuration key for the scheduler
UPDATE `cloud`.`configuration` SET name='scheduler.jobs.expire.interval' WHERE name='vmscheduler.jobs.expire.interval';

View File

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

View File

@ -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)) {

View File

@ -108,6 +108,7 @@ import org.apache.cloudstack.backup.BackupScheduleVO;
import org.apache.cloudstack.backup.BackupVO;
import org.apache.cloudstack.backup.dao.BackupDao;
import org.apache.cloudstack.backup.dao.BackupScheduleDao;
import org.apache.cloudstack.schedule.ResourceScheduleManager;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.cloud.entity.api.VirtualMachineEntity;
import org.apache.cloudstack.engine.cloud.entity.api.db.dao.VMNetworkMapDao;
@ -427,6 +428,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
@Inject
private EntityManager _entityMgr;
@Inject
private ResourceScheduleManager resourceScheduleManager;
@Inject
private HostDao _hostDao;
@Inject
private ServiceOfferingDao serviceOfferingDao;
@ -2588,6 +2591,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
autoScaleManager.removeVmFromVmGroup(vm.getId());
resourceScheduleManager.removeSchedulesForResource(ApiCommandResourceType.VirtualMachine, vm.getId());
releaseNetworkResourcesOnExpunge(vm.getId());
List<VolumeVO> rootVol = _volsDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT);

View File

@ -0,0 +1,394 @@
/*
* 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;
import com.cloud.api.ApiGsonHelper;
import com.cloud.event.ActionEventUtils;
import com.cloud.user.User;
import com.cloud.utils.DateUtil;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.GlobalLock;
import com.google.common.primitives.Longs;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher;
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
import org.apache.cloudstack.schedule.dao.ResourceScheduleDao;
import org.apache.cloudstack.schedule.dao.ResourceScheduledJobDao;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.time.DateUtils;
import org.springframework.scheduling.support.CronExpression;
import javax.inject.Inject;
import javax.persistence.EntityExistsException;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
/**
* Base class for per-resource-type schedule workers.
* Each subclass owns a dedicated {@link Timer} and {@link GlobalLock} keyed by
* its resource type, so VM scheduling and AutoScale scheduling (for example) run
* independently and cannot block each other.
*/
public abstract class BaseScheduleWorker extends ManagerBase {
public static final ConfigKey<Integer> ScheduledJobExpireInterval = new ConfigKey<>(
ConfigKey.CATEGORY_ADVANCED, Integer.class,
"scheduler.jobs.expire.interval", "30",
"Scheduled job expiry interval in days (applies to all resource-type schedulers)", true);
@Inject
protected ResourceScheduleDao resourceScheduleDao;
@Inject
protected ResourceScheduledJobDao resourceScheduledJobDao;
@Inject
protected AsyncJobManager asyncJobManager;
protected AsyncJobDispatcher asyncJobDispatcher;
private Timer schedulerTimer;
protected Date currentTimestamp;
public AsyncJobDispatcher getAsyncJobDispatcher() {
return asyncJobDispatcher;
}
public void setAsyncJobDispatcher(final AsyncJobDispatcher dispatcher) {
asyncJobDispatcher = dispatcher;
}
/**
* The API resource type this worker handles (e.g. {@code ApiCommandResourceType.VirtualMachine}).
*/
public abstract ApiCommandResourceType getApiResourceType();
/**
* Convenience method returning {@code getApiResourceType().name()} for use in DAO queries, locks, and logging.
*/
protected final String getResourceTypeName() {
return getApiResourceType().name();
}
/**
* Execute the action described by {@code job} against the owning resource.
*
* @return the async-job id, or {@code null} if the job was skipped.
*/
protected abstract Long processJob(ResourceScheduledJobVO job);
// -------------------------------------------------------------------------
// Lifecycle
// -------------------------------------------------------------------------
@Override
public boolean start() {
currentTimestamp = DateUtils.addMinutes(new Date(), 1);
scheduleNextJobs(currentTimestamp);
final TimerTask pollTask = new ManagedContextTimerTask() {
@Override
protected void runInContext() {
try {
poll(new Date());
} catch (final Throwable t) {
logger.warn("Uncaught throwable in {} scheduler", getResourceTypeName(), t);
}
}
};
schedulerTimer = new Timer(getResourceTypeName() + "SchedulerPollTask");
schedulerTimer.scheduleAtFixedRate(pollTask, 5000L, 60 * 1000L);
return true;
}
@Override
public boolean stop() {
if (schedulerTimer != null) {
schedulerTimer.cancel();
}
return true;
}
// -------------------------------------------------------------------------
// Poll loop (identical structure for every resource type)
// -------------------------------------------------------------------------
public void poll(Date timestamp) {
currentTimestamp = DateUtils.round(timestamp, Calendar.MINUTE);
String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
logger.debug("{} scheduler poll at {}", getResourceTypeName(), displayTime);
GlobalLock scanLock = GlobalLock.getInternLock("resourceScheduler.poll." + getResourceTypeName());
try {
if (scanLock.lock(30)) {
try {
scheduleNextJobs(currentTimestamp);
} finally {
scanLock.unlock();
}
}
} finally {
scanLock.releaseRef();
}
scanLock = GlobalLock.getInternLock("resourceScheduler.poll." + getResourceTypeName());
try {
if (scanLock.lock(30)) {
try {
startJobs();
} finally {
scanLock.unlock();
}
}
} finally {
scanLock.releaseRef();
}
try {
cleanupScheduledJobs();
} catch (Exception e) {
logger.warn("Error cleaning up scheduled jobs for {}", getResourceTypeName(), e);
}
}
// -------------------------------------------------------------------------
// Scheduling helpers
// -------------------------------------------------------------------------
private void scheduleNextJobs(Date timestamp) {
for (ResourceScheduleVO schedule : resourceScheduleDao.listAllActiveSchedules(getApiResourceType())) {
try {
scheduleNextJob(schedule, timestamp);
} catch (Exception e) {
logger.warn("Error scheduling next job for schedule {}", schedule, e);
}
}
}
public Date scheduleNextJob(ResourceScheduleVO schedule, Date timestamp) {
if (!schedule.getEnabled()) {
logger.debug("Schedule {} is disabled. Skipping.", schedule);
return null;
}
CronExpression cron = DateUtil.parseSchedule(schedule.getSchedule());
Date startDate = schedule.getStartDate();
Date endDate = schedule.getEndDate();
if (!isResourceValid(schedule.getResourceId())) {
logger.info("Resource id={} is no longer valid. Disabling schedule {}.", schedule.getResourceId(), schedule);
schedule.setEnabled(false);
resourceScheduleDao.persist(schedule);
return null;
}
ZonedDateTime now = (timestamp != null)
? ZonedDateTime.ofInstant(timestamp.toInstant(), schedule.getTimeZoneId())
: ZonedDateTime.now(schedule.getTimeZoneId());
ZonedDateTime zonedStart = ZonedDateTime.ofInstant(startDate.toInstant(), schedule.getTimeZoneId());
ZonedDateTime zonedEnd = (endDate != null)
? ZonedDateTime.ofInstant(endDate.toInstant(), schedule.getTimeZoneId())
: null;
if (zonedEnd != null && now.isAfter(zonedEnd)) {
logger.info("End time has passed. Disabling schedule {}.", schedule);
schedule.setEnabled(false);
resourceScheduleDao.persist(schedule);
return null;
}
ZonedDateTime ts = zonedStart.isAfter(now) ? cron.next(zonedStart) : cron.next(now);
if (ts == null) {
logger.info("No next schedule time found. Disabling schedule {}.", schedule);
schedule.setEnabled(false);
resourceScheduleDao.persist(schedule);
return null;
}
Date scheduledDateTime = Date.from(ts.toInstant());
ResourceScheduledJobVO existingJob = resourceScheduledJobDao.findByScheduleAndTimestamp(schedule.getId(), scheduledDateTime);
if (existingJob != null) {
logger.trace("Job already scheduled for {} at {}", schedule, scheduledDateTime);
return scheduledDateTime;
}
ResourceScheduledJobVO job = new ResourceScheduledJobVO(
getApiResourceType(), schedule.getResourceId(), schedule.getId(),
schedule.getActionName(), scheduledDateTime);
try {
resourceScheduledJobDao.persist(job);
long accountId = getEntityOwnerId(schedule.getResourceId());
ActionEventUtils.onScheduledActionEvent(
User.UID_SYSTEM, accountId,
parseAction(schedule.getActionName()).getEventType(),
String.format("Scheduled action (%s) [resource: %d, schedule: %s] at %s",
schedule.getActionName(), schedule.getResourceId(), schedule, scheduledDateTime),
schedule.getResourceId(), getResourceTypeName(), true, 0);
} catch (EntityExistsException e) {
logger.debug("Job already scheduled (concurrent insert).");
}
return scheduledDateTime;
}
public void updateScheduledJob(ResourceScheduleVO schedule) {
removeScheduledJobs(Longs.asList(schedule.getId()));
scheduleNextJob(schedule, new Date());
}
public void removeScheduledJobs(List<Long> scheduleIds) {
if (CollectionUtils.isEmpty(scheduleIds)) {
return;
}
int removed = resourceScheduledJobDao.expungeJobsForSchedules(scheduleIds, new Date());
logger.debug("Removed {} scheduled jobs for schedules {}", removed, scheduleIds);
}
// -------------------------------------------------------------------------
// Job execution
// -------------------------------------------------------------------------
private void startJobs() {
String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
List<ResourceScheduledJobVO> jobs = resourceScheduledJobDao.listJobsToStart(getApiResourceType(), currentTimestamp);
logger.debug("Got {} scheduled jobs for {} at {}", jobs.size(), getResourceTypeName(), displayTime);
Map<Long, ResourceScheduledJobVO> toExecute = new HashMap<>();
Map<Long, List<ResourceScheduledJobVO>> toSkip = new HashMap<>();
for (ResourceScheduledJobVO job : jobs) {
long resourceId = job.getResourceId();
if (toExecute.get(resourceId) == null) {
toExecute.put(resourceId, job);
} else {
toSkip.computeIfAbsent(resourceId, k -> new ArrayList<>()).add(job);
}
}
executeJobs(toExecute);
logSkippedJobs(toExecute, toSkip);
}
public void executeJobs(Map<Long, ResourceScheduledJobVO> jobsToExecute) {
for (Map.Entry<Long, ResourceScheduledJobVO> entry : jobsToExecute.entrySet()) {
ResourceScheduledJobVO job = entry.getValue();
ResourceScheduledJobVO locked = null;
try {
locked = resourceScheduledJobDao.acquireInLockTable(job.getId());
Long jobId = processJob(job);
if (jobId != null) {
locked.setAsyncJobId(jobId);
resourceScheduledJobDao.update(job.getId(), locked);
}
} catch (Exception e) {
logger.warn("Failed executing scheduled job {}", job, e);
} finally {
if (locked != null) {
resourceScheduledJobDao.releaseFromLockTable(job.getId());
}
}
}
}
private void logSkippedJobs(Map<Long, ResourceScheduledJobVO> executed,
Map<Long, List<ResourceScheduledJobVO>> skipped) {
for (Map.Entry<Long, List<ResourceScheduledJobVO>> entry : skipped.entrySet()) {
long resourceId = entry.getKey();
ResourceScheduledJobVO running = executed.get(resourceId);
for (ResourceScheduledJobVO s : entry.getValue()) {
logger.info("Skipping job {} for resource {} — conflict with {}", s, resourceId, running);
}
}
}
private void cleanupScheduledJobs() {
Date deleteBeforeDate = DateUtils.addDays(currentTimestamp,
-1 * ScheduledJobExpireInterval.value());
int removed = resourceScheduledJobDao.expungeJobsBefore(getApiResourceType(), deleteBeforeDate);
logger.info("Cleaned up {} scheduled job entries for {}", removed, getResourceTypeName());
}
// -------------------------------------------------------------------------
// Subclass helpers
// -------------------------------------------------------------------------
public abstract boolean isResourceValid(long resourceId);
public abstract long getEntityOwnerId(long resourceId);
/**
* Parses an action string into the resource-type-specific typed action constant. Throws InvalidParameterValueException for unknown values.
*/
public abstract ResourceSchedule.Action parseAction(String action);
/**
* Validates action-specific detail parameters. Throws InvalidParameterValueException on failure.
*/
public abstract void validateDetails(ResourceSchedule.Action action, Map<String, String> details);
/**
* Submits an async job for the given command class.
*
* @param cmdClass the command to dispatch
* @param accountId account submitting the job
* @param resourceId primary resource id (written to AsyncJobVO.instanceId)
* @param eventId the start-event id for correlation
* @param extra additional parameters (e.g. "forced" -> "true")
* @return the async job id
*/
public <T extends BaseCmd> long submitAsyncJob(
Class<T> cmdClass, long accountId, long resourceId, long eventId,
Map<String, String> extra
) {
Map<String, String> params = new HashMap<>(extra);
params.put(ApiConstants.ID, String.valueOf(resourceId));
params.put("ctxUserId", "1");
params.put("ctxAccountId", String.valueOf(accountId));
params.put(ApiConstants.CTX_START_EVENT_ID, String.valueOf(eventId));
T cmd;
try {
cmd = cmdClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("Failed to instantiate " + cmdClass.getName(), e);
}
ComponentContext.inject(cmd);
AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, accountId,
cmdClass.getName(),
ApiGsonHelper.getBuilder().create().toJson(params),
resourceId,
cmd.getApiResourceType() != null ? cmd.getApiResourceType().toString() : null,
null);
job.setDispatcher(asyncJobDispatcher.getName());
return asyncJobManager.submitAsyncJob(job);
}
}

View File

@ -0,0 +1,444 @@
// 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;
import com.cloud.api.query.MutualExclusiveIdsManagerBase;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.AccountManager;
import com.cloud.utils.DateUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.component.PluggableService;
import com.cloud.utils.db.EntityManager;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.api.command.user.schedule.CreateResourceScheduleCmd;
import org.apache.cloudstack.api.command.user.schedule.DeleteResourceScheduleCmd;
import org.apache.cloudstack.api.command.user.schedule.ListResourceScheduleCmd;
import org.apache.cloudstack.api.command.user.schedule.UpdateResourceScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.CreateVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.DeleteVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.ListVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.UpdateVMScheduleCmd;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.schedule.dao.ResourceScheduleDao;
import org.apache.cloudstack.schedule.dao.ResourceScheduleDetailsDao;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.support.CronExpression;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
import java.util.stream.Collectors;
public class ResourceScheduleManagerImpl extends MutualExclusiveIdsManagerBase implements ResourceScheduleManager, PluggableService, Configurable {
@Inject
private ResourceScheduleDao resourceScheduleDao;
@Inject
private ResourceScheduleDetailsDao resourceScheduleDetailsDao;
@Inject
private AccountManager accountManager;
@Inject
private EntityManager entityManager;
@Inject
private List<BaseScheduleWorker> workerList;
private Map<ApiCommandResourceType, BaseScheduleWorker> workerMap;
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
workerMap = new HashMap<>();
if (workerList != null) {
for (BaseScheduleWorker worker : workerList) {
workerMap.put(worker.getApiResourceType(), worker);
}
}
return super.configure(name, params);
}
private BaseScheduleWorker getWorker(ApiCommandResourceType resourceType) {
BaseScheduleWorker worker = workerMap.get(resourceType);
if (worker == null) {
throw new InvalidParameterValueException("Scheduling is not supported for resource type: " + resourceType);
}
return worker;
}
@Override
public List<Class<?>> getCommands() {
final List<Class<?>> cmdList = new ArrayList<>();
cmdList.add(CreateVMScheduleCmd.class);
cmdList.add(ListVMScheduleCmd.class);
cmdList.add(UpdateVMScheduleCmd.class);
cmdList.add(DeleteVMScheduleCmd.class);
cmdList.add(CreateResourceScheduleCmd.class);
cmdList.add(ListResourceScheduleCmd.class);
cmdList.add(UpdateResourceScheduleCmd.class);
cmdList.add(DeleteResourceScheduleCmd.class);
return cmdList;
}
@Override
public String getConfigComponentName() {
return ResourceScheduleManager.class.getSimpleName();
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[]{
BaseScheduleWorker.ScheduledJobExpireInterval
};
}
// Helper to resolve UUID string to internal ID
private long resolveResourceId(String resourceIdStr, Class<?> entityClass) {
if (entityClass == null) {
throw new CloudRuntimeException("Entity class is required to resolve resource ID");
}
Object obj = entityManager.findByUuid(entityClass, resourceIdStr);
if (obj == null) {
try {
long id = Long.parseLong(resourceIdStr);
obj = entityManager.findById(entityClass, id);
if (obj == null) {
throw new InvalidParameterValueException("Unable to find resource by id " + resourceIdStr);
}
} catch (NumberFormatException e) {
throw new InvalidParameterValueException("Unable to find resource by id " + resourceIdStr);
}
}
return ((InternalIdentity) obj).getId();
}
private String getResourceUuid(long internalId, Class<?> entityClass) {
if (entityClass != null) {
Object obj = entityManager.findById(entityClass, internalId);
if (obj instanceof Identity) {
return ((Identity) obj).getUuid();
}
}
return String.valueOf(internalId);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SCHEDULE_CREATE, eventDescription = "Creating Resource Schedule", create = true)
public ResourceScheduleResponse createSchedule(ApiCommandResourceType resourceType, String resourceUuid, String description,
String schedule, String timeZoneStr, String action,
Date cmdStartDate, Date cmdEndDate, boolean enabled,
Map<String, String> details) {
BaseScheduleWorker worker = getWorker(resourceType);
long internalResourceId = resolveResourceId(resourceUuid, worker.getApiResourceType().getAssociatedClass());
if (!worker.isResourceValid(internalResourceId)) {
throw new InvalidParameterValueException("Invalid or non-existent resource: " + resourceUuid);
}
long ownerId = worker.getEntityOwnerId(internalResourceId);
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, accountManager.getAccount(ownerId));
ResourceSchedule.Action parsedAction = worker.parseAction(action);
worker.validateDetails(parsedAction, details);
TimeZone timeZone = TimeZone.getTimeZone(timeZoneStr);
String timeZoneId = timeZone.getID();
Date startDate = DateUtils.addMinutes(new Date(), 1);
if (cmdStartDate != null) {
startDate = Date.from(DateUtil.getZoneDateTime(cmdStartDate, timeZone.toZoneId()).toInstant());
}
Date endDate = null;
if (cmdEndDate != null) {
endDate = Date.from(DateUtil.getZoneDateTime(cmdEndDate, timeZone.toZoneId()).toInstant());
}
CronExpression cronExpression = DateUtil.parseSchedule(schedule);
validateStartDateEndDate(startDate, endDate, timeZone);
if (StringUtils.isBlank(description)) {
description = String.format("%s - %s", parsedAction.name(), DateUtil.getHumanReadableSchedule(cronExpression));
}
logger.warn("Using timezone [{}] for running the schedule for resource [{}], as an equivalent of [{}].", timeZoneId, resourceUuid, timeZoneStr);
String finalDescription = description;
String finalAction = parsedAction.name();
Date finalStartDate = startDate;
Date finalEndDate = endDate;
return Transaction.execute((TransactionCallback<ResourceScheduleResponse>) status -> {
ResourceScheduleVO scheduleVO = resourceScheduleDao.persist(new ResourceScheduleVO(
resourceType, internalResourceId,
finalDescription, cronExpression.toString(), timeZoneId,
finalAction, finalStartDate, finalEndDate, enabled));
if (details != null && !details.isEmpty()) {
List<ResourceScheduleDetailVO> detailVOs = new ArrayList<>();
for (Map.Entry<String, String> entry : details.entrySet()) {
detailVOs.add(new ResourceScheduleDetailVO(scheduleVO.getId(), entry.getKey(), entry.getValue(), true));
}
resourceScheduleDetailsDao.saveDetails(detailVOs);
}
worker.scheduleNextJob(scheduleVO, new Date());
CallContext.current().setEventResourceId(internalResourceId);
CallContext.current().setEventResourceType(worker.getApiResourceType());
return createResponse(scheduleVO, details);
});
}
ResourceScheduleResponse createResponse(ResourceSchedule schedule, Map<String, String> details) {
if (details == null || details.isEmpty()) {
details = resourceScheduleDetailsDao.listDetailsKeyPairs(schedule.getId(), true);
}
BaseScheduleWorker worker = getWorker(schedule.getResourceType());
ResourceScheduleResponse response = new ResourceScheduleResponse();
response.setObjectName("resourceschedule");
response.setId(schedule.getUuid());
response.setResourceType(schedule.getResourceType());
String uuid = getResourceUuid(schedule.getResourceId(), worker.getApiResourceType().getAssociatedClass());
response.setResourceId(uuid);
response.setDescription(schedule.getDescription());
response.setSchedule(schedule.getSchedule());
response.setTimeZone(schedule.getTimeZone());
response.setAction(worker.parseAction(schedule.getActionName()));
response.setEnabled(schedule.getEnabled());
response.setStartDate(schedule.getStartDate());
response.setEndDate(schedule.getEndDate());
response.setDetails(details);
response.setCreated(schedule.getCreated());
return response;
}
@Override
public ListResponse<ResourceScheduleResponse> listSchedule(Long id, List<Long> ids, ApiCommandResourceType resourceType,
String resourceUuid, String action, Boolean enabled,
Long startIndex, Long pageSize) {
Long internalResourceId = null;
BaseScheduleWorker worker = getWorker(resourceType);
if (StringUtils.isBlank(resourceUuid)) {
throw new InvalidParameterValueException("Resource ID must be specified");
} else {
internalResourceId = resolveResourceId(resourceUuid, worker.getApiResourceType().getAssociatedClass());
long ownerId = worker.getEntityOwnerId(internalResourceId);
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, accountManager.getAccount(ownerId));
}
List<Long> scheduleIds = getIdsListFromCmd(id, ids);
if (action != null) {
action = worker.parseAction(action).name();
}
Pair<List<ResourceScheduleVO>, Integer> result = resourceScheduleDao.searchAndCount(
scheduleIds, resourceType, internalResourceId, action, enabled, startIndex, pageSize);
ListResponse<ResourceScheduleResponse> response = new ListResponse<>();
List<ResourceScheduleResponse> responsesList = new ArrayList<>();
for (ResourceScheduleVO schedule : result.first()) {
responsesList.add(createResponse(schedule, null));
}
response.setResponses(responsesList, result.second());
return response;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SCHEDULE_UPDATE, eventDescription = "Updating Resource Schedule")
public ResourceScheduleResponse updateSchedule(Long id, String description, String schedule,
String timeZoneStr, Date cmdStartDate, Date cmdEndDate,
Boolean enabled, Map<String, String> details) {
ResourceScheduleVO scheduleVO = resourceScheduleDao.findById(id);
if (scheduleVO == null) {
throw new CloudRuntimeException("Resource schedule doesn't exist");
}
BaseScheduleWorker worker = getWorker(scheduleVO.getResourceType());
long ownerId = worker.getEntityOwnerId(scheduleVO.getResourceId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, accountManager.getAccount(ownerId));
if (details != null && !details.isEmpty()) {
worker.validateDetails(worker.parseAction(scheduleVO.getActionName()), details);
}
CronExpression cronExpression = Objects.requireNonNullElse(
DateUtil.parseSchedule(schedule),
DateUtil.parseSchedule(scheduleVO.getSchedule())
);
if (description == null && scheduleVO.getDescription() == null) {
description = String.format("%s - %s", scheduleVO.getActionName(), DateUtil.getHumanReadableSchedule(cronExpression));
}
final String originalTimeZone = scheduleVO.getTimeZone();
final Date originalStartDate = scheduleVO.getStartDate();
final Date originalEndDate = scheduleVO.getEndDate();
TimeZone timeZone;
String timeZoneId;
if (timeZoneStr != null) {
timeZone = TimeZone.getTimeZone(timeZoneStr);
timeZoneId = timeZone.getID();
if (!timeZoneId.equals(timeZoneStr)) {
logger.warn("Using timezone [{}] for running the schedule [{}] for resource {}, as an equivalent of [{}].",
timeZoneId, scheduleVO.getSchedule(), scheduleVO.getResourceId(), timeZoneStr);
}
scheduleVO.setTimeZone(timeZoneId);
} else {
timeZoneId = scheduleVO.getTimeZone();
timeZone = TimeZone.getTimeZone(timeZoneId);
}
Date startDate = scheduleVO.getStartDate().before(DateUtils.addMinutes(new Date(), 1)) ? DateUtils.addMinutes(new Date(), 1) : scheduleVO.getStartDate();
Date endDate = scheduleVO.getEndDate();
if (cmdEndDate != null) {
endDate = Date.from(DateUtil.getZoneDateTime(cmdEndDate, timeZone.toZoneId()).toInstant());
}
if (cmdStartDate != null) {
startDate = Date.from(DateUtil.getZoneDateTime(cmdStartDate, timeZone.toZoneId()).toInstant());
}
if (ObjectUtils.anyNotNull(cmdStartDate, cmdEndDate, timeZoneStr) &&
(!Objects.equals(originalTimeZone, timeZoneId) ||
!Objects.equals(originalStartDate, startDate) ||
!Objects.equals(originalEndDate, endDate))) {
validateStartDateEndDate(Objects.requireNonNullElse(startDate, DateUtils.addMinutes(new Date(), 1)),
endDate, timeZone);
}
if (enabled != null) {
scheduleVO.setEnabled(enabled);
}
if (description != null) {
scheduleVO.setDescription(description);
}
if (cmdEndDate != null) {
scheduleVO.setEndDate(endDate);
}
if (cmdStartDate != null) {
scheduleVO.setStartDate(startDate);
}
scheduleVO.setSchedule(cronExpression.toString());
return Transaction.execute((TransactionCallback<ResourceScheduleResponse>) status -> {
resourceScheduleDao.update(id, scheduleVO);
if (details != null) {
if (details.isEmpty()) {
resourceScheduleDetailsDao.removeDetails(id);
} else {
List<ResourceScheduleDetailVO> detailVOs = new ArrayList<>();
for (Map.Entry<String, String> entry : details.entrySet()) {
detailVOs.add(new ResourceScheduleDetailVO(id, entry.getKey(), entry.getValue(), true));
}
resourceScheduleDetailsDao.saveDetails(detailVOs);
}
}
worker.updateScheduledJob(scheduleVO);
CallContext.current().setEventResourceId(scheduleVO.getResourceId());
CallContext.current().setEventResourceType(worker.getApiResourceType());
// Re-load details if they weren't fully replaced
Map<String, String> currentDetails = resourceScheduleDetailsDao.listDetailsKeyPairs(id, true);
return createResponse(scheduleVO, currentDetails);
});
}
void validateStartDateEndDate(Date startDate, Date endDate, TimeZone tz) {
ZonedDateTime now = ZonedDateTime.now(tz.toZoneId());
ZonedDateTime zonedStartDate = ZonedDateTime.ofInstant(startDate.toInstant(), tz.toZoneId());
if (zonedStartDate.isBefore(now)) {
throw new InvalidParameterValueException(String.format("Invalid value for start date. Start date [%s] can't be before current time [%s].", zonedStartDate, now));
}
if (endDate != null) {
ZonedDateTime zonedEndDate = ZonedDateTime.ofInstant(endDate.toInstant(), tz.toZoneId());
if (zonedEndDate.isBefore(now)) {
throw new InvalidParameterValueException(String.format("Invalid value for end date. End date [%s] can't be before current time [%s].", zonedEndDate, now));
}
if (zonedEndDate.isBefore(zonedStartDate)) {
throw new InvalidParameterValueException(String.format("Invalid value for end date. End date [%s] can't be before start date [%s].", zonedEndDate, zonedStartDate));
}
}
}
@Override
public void removeSchedulesForResource(ApiCommandResourceType resourceType, long resourceId) {
List<ResourceScheduleVO> schedules = resourceScheduleDao.search(
resourceScheduleDao.getSearchCriteriaForResource(resourceType, resourceId), null);
List<Long> ids = new ArrayList<>();
for (ResourceScheduleVO schedule : schedules) {
ids.add(schedule.getId());
}
getWorker(resourceType).removeScheduledJobs(ids);
resourceScheduleDao.removeAllSchedulesForResource(resourceType, resourceId);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SCHEDULE_DELETE, eventDescription = "Deleting Resource Schedule")
public Long removeSchedule(ApiCommandResourceType resourceType, String resourceUuid, Long id, List<Long> idsList) {
BaseScheduleWorker worker = getWorker(resourceType);
long internalResourceId = resolveResourceId(resourceUuid, worker.getApiResourceType().getAssociatedClass());
long ownerId = worker.getEntityOwnerId(internalResourceId);
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, accountManager.getAccount(ownerId));
List<Long> ids = getIdsListFromCmd(id, idsList);
Pair<List<ResourceScheduleVO>, Integer> result = resourceScheduleDao.searchAndCount(ids, resourceType, internalResourceId, null, null, null, null);
List<ResourceScheduleVO> schedulesToRemove = result.first();
List<Long> scheduleIdsToRemove = schedulesToRemove.stream().map(ResourceScheduleVO::getId).collect(Collectors.toList());
return Transaction.execute((TransactionCallback<Long>) status -> {
worker.removeScheduledJobs(scheduleIdsToRemove);
CallContext.current().setEventResourceId(internalResourceId);
CallContext.current().setEventResourceType(worker.getApiResourceType());
return resourceScheduleDao.removeSchedulesForResourceAndIds(resourceType, internalResourceId, scheduleIdsToRemove);
});
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,132 @@
/*
* 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.vm;
import com.cloud.event.ActionEventUtils;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.User;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.user.vm.RebootVMCmd;
import org.apache.cloudstack.api.command.user.vm.StartVMCmd;
import org.apache.cloudstack.api.command.user.vm.StopVMCmd;
import org.apache.cloudstack.schedule.BaseScheduleWorker;
import org.apache.cloudstack.schedule.ResourceSchedule;
import org.apache.cloudstack.schedule.ResourceScheduledJobVO;
import org.apache.commons.lang3.EnumUtils;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
public class VMScheduleWorker extends BaseScheduleWorker {
@Inject
private UserVmManager userVmManager;
@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.VirtualMachine;
}
@Override
public boolean isResourceValid(long resourceId) {
return userVmManager.getUserVm(resourceId) != null;
}
@Override
public long getEntityOwnerId(long resourceId) {
VirtualMachine vm = userVmManager.getUserVm(resourceId);
return vm != null ? vm.getAccountId() : User.UID_SYSTEM;
}
@Override
public VMScheduleAction parseAction(String actionName) {
VMScheduleAction action = EnumUtils.getEnumIgnoreCase(VMScheduleAction.class, actionName);
if (action == null) {
throw new InvalidParameterValueException(String.format(
"Invalid action for VirtualMachine schedule: %s. Supported actions: %s",
actionName, Arrays.toString(VMScheduleAction.values())));
}
return action;
}
@Override
public void validateDetails(ResourceSchedule.Action action, Map<String, String> details) {}
@Override
protected Long processJob(ResourceScheduledJobVO job) {
VirtualMachine vm = userVmManager.getUserVm(job.getResourceId());
if (vm == null) {
logger.warn("VM id={} not found; skipping scheduled job {}", job.getResourceId(), job);
return null;
}
if (!Arrays.asList(VirtualMachine.State.Running, VirtualMachine.State.Stopped).contains(vm.getState())) {
logger.info("Skipping action ({}) for [vm: {}, job: {}] — VM is in state: {}",
job.getActionName(), vm, job, vm.getState());
return null;
}
VMScheduleAction action = parseAction(job.getActionName());
final long eventId = ActionEventUtils.onCompletedActionEvent(
User.UID_SYSTEM, vm.getAccountId(), null,
action.getEventType(), true,
String.format("Executing action (%s) for VM: %s", action, vm),
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), 0);
if (vm.getState() == VirtualMachine.State.Running) {
switch (action) {
case STOP:
return submitStopVMJob(vm, false, eventId);
case FORCE_STOP:
return submitStopVMJob(vm, true, eventId);
case REBOOT:
return submitRebootVMJob(vm, false, eventId);
case FORCE_REBOOT:
return submitRebootVMJob(vm, true, eventId);
default:
break;
}
} else if (vm.getState() == VirtualMachine.State.Stopped && action == VMScheduleAction.START) {
return submitStartVMJob(vm, eventId);
}
logger.warn("Skipping action ({}) for [vm: {}, job: {}] — VM is in state: {}",
action, vm, job, vm.getState());
return null;
}
private long submitStartVMJob(VirtualMachine vm, long eventId) {
return submitAsyncJob(StartVMCmd.class, vm.getAccountId(), vm.getId(), eventId, Collections.emptyMap());
}
private long submitStopVMJob(VirtualMachine vm, boolean forced, long eventId) {
return submitAsyncJob(StopVMCmd.class, vm.getAccountId(), vm.getId(), eventId,
Map.of(ApiConstants.FORCED, String.valueOf(forced)));
}
private long submitRebootVMJob(VirtualMachine vm, boolean forced, long eventId) {
return submitAsyncJob(RebootVMCmd.class, vm.getAccountId(), vm.getId(), eventId,
Map.of(ApiConstants.FORCED, String.valueOf(forced)));
}
}

View File

@ -1,326 +0,0 @@
/*
* 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.vm.schedule;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.TimeZone;
import javax.inject.Inject;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.command.user.vm.CreateVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.DeleteVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.ListVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.UpdateVMScheduleCmd;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.vm.schedule.dao.VMScheduleDao;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.support.CronExpression;
import com.cloud.api.query.MutualExclusiveIdsManagerBase;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.AccountManager;
import com.cloud.utils.DateUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.component.PluggableService;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.VirtualMachine;
public class VMScheduleManagerImpl extends MutualExclusiveIdsManagerBase implements VMScheduleManager, PluggableService {
@Inject
private VMScheduleDao vmScheduleDao;
@Inject
private UserVmManager userVmManager;
@Inject
private VMScheduler vmScheduler;
@Inject
private AccountManager accountManager;
@Override
public List<Class<?>> getCommands() {
final List<Class<?>> cmdList = new ArrayList<>();
cmdList.add(CreateVMScheduleCmd.class);
cmdList.add(ListVMScheduleCmd.class);
cmdList.add(UpdateVMScheduleCmd.class);
cmdList.add(DeleteVMScheduleCmd.class);
return cmdList;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_SCHEDULE_CREATE, eventDescription = "Creating VM Schedule", create = true)
public VMScheduleResponse createSchedule(CreateVMScheduleCmd cmd) {
VirtualMachine vm = userVmManager.getUserVm(cmd.getVmId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, vm);
if (vm == null) {
throw new InvalidParameterValueException(String.format("Invalid value for vmId: %s", cmd.getVmId()));
}
VMSchedule.Action action = null;
if (cmd.getAction() != null) {
try {
action = VMSchedule.Action.valueOf(cmd.getAction().toUpperCase());
} catch (IllegalArgumentException exception) {
throw new InvalidParameterValueException(String.format("Invalid value for action: %s", cmd.getAction()));
}
}
Date cmdStartDate = cmd.getStartDate();
Date cmdEndDate = cmd.getEndDate();
String cmdTimeZone = cmd.getTimeZone();
TimeZone timeZone = TimeZone.getTimeZone(cmdTimeZone);
String timeZoneId = timeZone.getID();
Date startDate = DateUtils.addMinutes(new Date(), 1);
if (cmdStartDate != null) {
startDate = Date.from(DateUtil.getZoneDateTime(cmdStartDate, timeZone.toZoneId()).toInstant());
}
Date endDate = null;
if (cmdEndDate != null) {
endDate = Date.from(DateUtil.getZoneDateTime(cmdEndDate, timeZone.toZoneId()).toInstant());
}
CronExpression cronExpression = DateUtil.parseSchedule(cmd.getSchedule());
validateStartDateEndDate(startDate, endDate, timeZone);
String description = null;
if (StringUtils.isBlank(cmd.getDescription())) {
description = String.format("%s - %s", action, DateUtil.getHumanReadableSchedule(cronExpression));
} else description = cmd.getDescription();
logger.warn("Using timezone [{}] for running the schedule for VM [{}], as an equivalent of [{}].", timeZoneId, vm, cmdTimeZone);
String finalDescription = description;
VMSchedule.Action finalAction = action;
Date finalStartDate = startDate;
Date finalEndDate = endDate;
return Transaction.execute((TransactionCallback<VMScheduleResponse>) status -> {
VMScheduleVO vmSchedule = vmScheduleDao.persist(new VMScheduleVO(cmd.getVmId(), finalDescription, cronExpression.toString(), timeZoneId, finalAction, finalStartDate, finalEndDate, cmd.getEnabled()));
vmScheduler.scheduleNextJob(vmSchedule, new Date());
CallContext.current().setEventResourceId(vm.getId());
CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine);
return createResponse(vmSchedule);
});
}
@Override
public VMScheduleResponse createResponse(VMSchedule vmSchedule) {
VirtualMachine vm = userVmManager.getUserVm(vmSchedule.getVmId());
VMScheduleResponse response = new VMScheduleResponse();
response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
response.setId(vmSchedule.getUuid());
response.setVmId(vm.getUuid());
response.setDescription(vmSchedule.getDescription());
response.setSchedule(vmSchedule.getSchedule());
response.setTimeZone(vmSchedule.getTimeZone());
response.setAction(vmSchedule.getAction());
response.setEnabled(vmSchedule.getEnabled());
response.setStartDate(vmSchedule.getStartDate());
response.setEndDate(vmSchedule.getEndDate());
response.setCreated(vmSchedule.getCreated());
return response;
}
@Override
public ListResponse<VMScheduleResponse> listSchedule(ListVMScheduleCmd cmd) {
Long id = cmd.getId();
Boolean enabled = cmd.getEnabled();
Long vmId = cmd.getVmId();
VirtualMachine vm = userVmManager.getUserVm(vmId);
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, vm);
VMSchedule.Action action = null;
if (cmd.getAction() != null) {
try {
action = VMSchedule.Action.valueOf(cmd.getAction());
} catch (IllegalArgumentException exception) {
throw new InvalidParameterValueException("Invalid value for action: " + cmd.getAction());
}
}
Pair<List<VMScheduleVO>, Integer> result = vmScheduleDao.searchAndCount(id, vmId, action, enabled, cmd.getStartIndex(), cmd.getPageSizeVal());
ListResponse<VMScheduleResponse> response = new ListResponse<>();
List<VMScheduleResponse> responsesList = new ArrayList<>();
for (VMSchedule vmSchedule : result.first()) {
responsesList.add(createResponse(vmSchedule));
}
response.setResponses(responsesList, result.second());
return response;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_SCHEDULE_UPDATE, eventDescription = "Updating VM Schedule")
public VMScheduleResponse updateSchedule(UpdateVMScheduleCmd cmd) {
Long id = cmd.getId();
VMScheduleVO vmSchedule = vmScheduleDao.findById(id);
if (vmSchedule == null) {
throw new CloudRuntimeException("VM schedule doesn't exist");
}
VirtualMachine vm = userVmManager.getUserVm(vmSchedule.getVmId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, vm);
CronExpression cronExpression = Objects.requireNonNullElse(
DateUtil.parseSchedule(cmd.getSchedule()),
DateUtil.parseSchedule(vmSchedule.getSchedule())
);
String description = cmd.getDescription();
if (description == null && vmSchedule.getDescription() == null) {
description = String.format("%s - %s", vmSchedule.getAction(), DateUtil.getHumanReadableSchedule(cronExpression));
}
String cmdTimeZone = cmd.getTimeZone();
Date cmdStartDate = cmd.getStartDate();
Date cmdEndDate = cmd.getEndDate();
Boolean enabled = cmd.getEnabled();
final String originalTimeZone = vmSchedule.getTimeZone();
final Date originalStartDate = vmSchedule.getStartDate();
final Date originalEndDate = vmSchedule.getEndDate();
TimeZone timeZone;
String timeZoneId;
if (cmdTimeZone != null) {
timeZone = TimeZone.getTimeZone(cmdTimeZone);
timeZoneId = timeZone.getID();
if (!timeZoneId.equals(cmdTimeZone)) {
logger.warn("Using timezone [{}] for running the schedule [{}] for VM {}, as an equivalent of [{}].",
timeZoneId, vmSchedule.getSchedule(), userVmManager.getUserVm(vmSchedule.getVmId()), cmdTimeZone);
}
vmSchedule.setTimeZone(timeZoneId);
} else {
timeZoneId = vmSchedule.getTimeZone();
timeZone = TimeZone.getTimeZone(timeZoneId);
}
Date startDate = vmSchedule.getStartDate().before(DateUtils.addMinutes(new Date(), 1)) ? DateUtils.addMinutes(new Date(), 1) : vmSchedule.getStartDate();
Date endDate = vmSchedule.getEndDate();
if (cmdEndDate != null) {
endDate = Date.from(DateUtil.getZoneDateTime(cmdEndDate, timeZone.toZoneId()).toInstant());
}
if (cmdStartDate != null) {
startDate = Date.from(DateUtil.getZoneDateTime(cmdStartDate, timeZone.toZoneId()).toInstant());
}
if (ObjectUtils.anyNotNull(cmdStartDate, cmdEndDate, cmdTimeZone) &&
(!Objects.equals(originalTimeZone, timeZoneId) ||
!Objects.equals(originalStartDate, startDate) ||
!Objects.equals(originalEndDate, endDate))) {
validateStartDateEndDate(Objects.requireNonNullElse(startDate, DateUtils.addMinutes(new Date(), 1)),
endDate, timeZone);
}
if (enabled != null) {
vmSchedule.setEnabled(enabled);
}
if (description != null) {
vmSchedule.setDescription(description);
}
if (cmdEndDate != null) {
vmSchedule.setEndDate(endDate);
}
if (cmdStartDate != null) {
vmSchedule.setStartDate(startDate);
}
vmSchedule.setSchedule(cronExpression.toString());
return Transaction.execute((TransactionCallback<VMScheduleResponse>) status -> {
vmScheduleDao.update(cmd.getId(), vmSchedule);
vmScheduler.updateScheduledJob(vmSchedule);
CallContext.current().setEventResourceId(vm.getId());
CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine);
return createResponse(vmSchedule);
});
}
void validateStartDateEndDate(Date startDate, Date endDate, TimeZone tz) {
ZonedDateTime now = ZonedDateTime.now(tz.toZoneId());
ZonedDateTime zonedStartDate = ZonedDateTime.ofInstant(startDate.toInstant(), tz.toZoneId());
if (zonedStartDate.isBefore(now)) {
throw new InvalidParameterValueException(String.format("Invalid value for start date. Start date [%s] can't be before current time [%s].", zonedStartDate, now));
}
if (endDate != null) {
ZonedDateTime zonedEndDate = ZonedDateTime.ofInstant(endDate.toInstant(), tz.toZoneId());
if (zonedEndDate.isBefore(now)) {
throw new InvalidParameterValueException(String.format("Invalid value for end date. End date [%s] can't be before current time [%s].", zonedEndDate, now));
}
if (zonedEndDate.isBefore(zonedStartDate)) {
throw new InvalidParameterValueException(String.format("Invalid value for end date. End date [%s] can't be before start date [%s].", zonedEndDate, zonedStartDate));
}
}
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_SCHEDULE_DELETE, eventDescription = "Deleting VM Schedule for VM")
public long removeScheduleByVmId(long vmId, boolean expunge) {
SearchCriteria<VMScheduleVO> sc = vmScheduleDao.getSearchCriteriaForVMId(vmId);
List<VMScheduleVO> vmSchedules = vmScheduleDao.search(sc, null);
List<Long> ids = new ArrayList<>();
for (final VMScheduleVO vmSchedule : vmSchedules) {
ids.add(vmSchedule.getId());
}
vmScheduler.removeScheduledJobs(ids);
if (expunge) {
return vmScheduleDao.expunge(sc);
}
CallContext.current().setEventResourceId(vmId);
CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine);
return vmScheduleDao.remove(sc);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_SCHEDULE_DELETE, eventDescription = "Deleting VM Schedule")
public Long removeSchedule(DeleteVMScheduleCmd cmd) {
VirtualMachine vm = userVmManager.getUserVm(cmd.getVmId());
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, vm);
List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds());
if (ids.isEmpty()) {
throw new InvalidParameterValueException("Either id or ids parameter must be specified");
}
return Transaction.execute((TransactionCallback<Long>) status -> {
vmScheduler.removeScheduledJobs(ids);
CallContext.current().setEventResourceId(vm.getId());
CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine);
return vmScheduleDao.removeSchedulesForVmIdAndIds(vm.getId(), ids);
});
}
}

View File

@ -1,36 +0,0 @@
/*
* 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.vm.schedule;
import com.cloud.utils.component.Manager;
import com.cloud.utils.concurrency.Scheduler;
import org.apache.cloudstack.framework.config.ConfigKey;
import java.util.Date;
import java.util.List;
public interface VMScheduler extends Manager, Scheduler {
ConfigKey<Integer> VMScheduledJobExpireInterval = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class, "vmscheduler.jobs.expire.interval", "30", "VM Scheduler expire interval in days", true);
void removeScheduledJobs(List<Long> vmScheduleIds);
void updateScheduledJob(VMScheduleVO vmSchedule);
Date scheduleNextJob(VMScheduleVO vmSchedule, Date timestamp);
}

View File

@ -1,420 +0,0 @@
/*
* 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.vm.schedule;
import com.cloud.api.ApiGsonHelper;
import com.cloud.event.ActionEventUtils;
import com.cloud.event.EventTypes;
import com.cloud.user.User;
import com.cloud.utils.DateUtil;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.GlobalLock;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.VirtualMachine;
import com.google.common.primitives.Longs;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.user.vm.RebootVMCmd;
import org.apache.cloudstack.api.command.user.vm.StartVMCmd;
import org.apache.cloudstack.api.command.user.vm.StopVMCmd;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher;
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
import org.apache.cloudstack.vm.schedule.dao.VMScheduleDao;
import org.apache.cloudstack.vm.schedule.dao.VMScheduledJobDao;
import org.apache.commons.lang.time.DateUtils;
import org.springframework.scheduling.support.CronExpression;
import javax.inject.Inject;
import javax.persistence.EntityExistsException;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
public class VMSchedulerImpl extends ManagerBase implements VMScheduler, Configurable {
@Inject
private VMScheduledJobDao vmScheduledJobDao;
@Inject
private VMScheduleDao vmScheduleDao;
@Inject
private UserVmManager userVmManager;
@Inject
private AsyncJobManager asyncJobManager;
private AsyncJobDispatcher asyncJobDispatcher;
private Timer vmSchedulerTimer;
private Date currentTimestamp;
private EnumMap<VMSchedule.Action, String> actionEventMap = new EnumMap<>(VMSchedule.Action.class);
public AsyncJobDispatcher getAsyncJobDispatcher() {
return asyncJobDispatcher;
}
public void setAsyncJobDispatcher(final AsyncJobDispatcher dispatcher) {
asyncJobDispatcher = dispatcher;
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[]{VMScheduledJobExpireInterval};
}
@Override
public String getConfigComponentName() {
return VMScheduler.class.getSimpleName();
}
@Override
public void removeScheduledJobs(List<Long> vmScheduleIds) {
if (vmScheduleIds == null || vmScheduleIds.isEmpty()) {
logger.debug("Removed 0 scheduled jobs");
return;
}
Date now = new Date();
int rowsRemoved = vmScheduledJobDao.expungeJobsForSchedules(vmScheduleIds, now);
logger.debug(String.format("Removed %s VM scheduled jobs", rowsRemoved));
}
@Override
public void updateScheduledJob(VMScheduleVO vmSchedule) {
removeScheduledJobs(Longs.asList(vmSchedule.getId()));
scheduleNextJob(vmSchedule, new Date());
}
@Override
public Date scheduleNextJob(VMScheduleVO vmSchedule, Date timestamp) {
if (!vmSchedule.getEnabled()) {
logger.debug("VM Schedule {} for VM {} with id {} is disabled. Not scheduling next job.",
vmSchedule::toString, () -> userVmManager.getUserVm(vmSchedule.getVmId()), vmSchedule::getVmId);
return null;
}
CronExpression cron = DateUtil.parseSchedule(vmSchedule.getSchedule());
Date startDate = vmSchedule.getStartDate();
Date endDate = vmSchedule.getEndDate();
VirtualMachine vm = userVmManager.getUserVm(vmSchedule.getVmId());
if (vm == null) {
logger.info("VM id={} is removed. Disabling VM schedule {}.", vmSchedule.getVmId(), vmSchedule);
vmSchedule.setEnabled(false);
vmScheduleDao.persist(vmSchedule);
return null;
}
ZonedDateTime now;
if (timestamp != null) {
now = ZonedDateTime.ofInstant(timestamp.toInstant(), vmSchedule.getTimeZoneId());
} else {
now = ZonedDateTime.now(vmSchedule.getTimeZoneId());
}
ZonedDateTime zonedStartDate = ZonedDateTime.ofInstant(startDate.toInstant(), vmSchedule.getTimeZoneId());
ZonedDateTime zonedEndDate = null;
if (endDate != null) {
zonedEndDate = ZonedDateTime.ofInstant(endDate.toInstant(), vmSchedule.getTimeZoneId());
}
if (zonedEndDate != null && now.isAfter(zonedEndDate)) {
logger.info("End time is less than current time. Disabling VM schedule {} for VM {}.", vmSchedule, vm);
vmSchedule.setEnabled(false);
vmScheduleDao.persist(vmSchedule);
return null;
}
ZonedDateTime ts = null;
if (zonedStartDate.isAfter(now)) {
ts = cron.next(zonedStartDate);
} else {
ts = cron.next(now);
}
if (ts == null) {
logger.info("No next schedule found. Disabling VM schedule {} for VM {}.", vmSchedule, vm);
vmSchedule.setEnabled(false);
vmScheduleDao.persist(vmSchedule);
return null;
}
Date scheduledDateTime = Date.from(ts.toInstant());
VMScheduledJobVO scheduledJob = vmScheduledJobDao.findByScheduleAndTimestamp(vmSchedule.getId(), scheduledDateTime);
if (scheduledJob != null) {
logger.trace("Job is already scheduled for schedule {} at {}", vmSchedule, scheduledDateTime);
return scheduledDateTime;
}
scheduledJob = new VMScheduledJobVO(vmSchedule.getVmId(), vmSchedule.getId(), vmSchedule.getAction(), scheduledDateTime);
try {
vmScheduledJobDao.persist(scheduledJob);
ActionEventUtils.onScheduledActionEvent(User.UID_SYSTEM, vm.getAccountId(), actionEventMap.get(vmSchedule.getAction()),
String.format("Scheduled action (%s) [vm: %s, schedule: %s] at %s", vmSchedule.getAction(), vm, vmSchedule, scheduledDateTime),
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), true, 0);
} catch (EntityExistsException exception) {
logger.debug("Job is already scheduled.");
}
return scheduledDateTime;
}
@Override
public boolean start() {
actionEventMap.put(VMSchedule.Action.START, EventTypes.EVENT_VM_SCHEDULE_START);
actionEventMap.put(VMSchedule.Action.STOP, EventTypes.EVENT_VM_SCHEDULE_STOP);
actionEventMap.put(VMSchedule.Action.REBOOT, EventTypes.EVENT_VM_SCHEDULE_REBOOT);
actionEventMap.put(VMSchedule.Action.FORCE_STOP, EventTypes.EVENT_VM_SCHEDULE_FORCE_STOP);
actionEventMap.put(VMSchedule.Action.FORCE_REBOOT, EventTypes.EVENT_VM_SCHEDULE_FORCE_REBOOT);
// Adding 1 minute to currentTimestamp to ensure that
// jobs which were to be run at current time, doesn't cause issues
currentTimestamp = DateUtils.addMinutes(new Date(), 1);
scheduleNextJobs(currentTimestamp);
final TimerTask schedulerPollTask = new ManagedContextTimerTask() {
@Override
protected void runInContext() {
try {
poll(new Date());
} catch (final Throwable t) {
logger.warn("Catch throwable in VM scheduler ", t);
}
}
};
vmSchedulerTimer = new Timer("VMSchedulerPollTask");
vmSchedulerTimer.scheduleAtFixedRate(schedulerPollTask, 5000L, 60 * 1000L);
return true;
}
@Override
public void poll(Date timestamp) {
currentTimestamp = DateUtils.round(timestamp, Calendar.MINUTE);
String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
logger.debug(String.format("VM scheduler.poll is being called at %s", displayTime));
GlobalLock scanLock = GlobalLock.getInternLock("vmScheduler.poll");
try {
if (scanLock.lock(30)) {
try {
scheduleNextJobs(currentTimestamp);
} finally {
scanLock.unlock();
}
}
} finally {
scanLock.releaseRef();
}
scanLock = GlobalLock.getInternLock("vmScheduler.poll");
try {
if (scanLock.lock(30)) {
try {
startJobs(); // Create async job and update scheduled job
} finally {
scanLock.unlock();
}
}
} finally {
scanLock.releaseRef();
}
try {
cleanupVMScheduledJobs();
} catch (Exception e) {
logger.warn("Error in cleaning up vm scheduled jobs", e);
}
}
private void scheduleNextJobs(Date timestamp) {
for (final VMScheduleVO schedule : vmScheduleDao.listAllActiveSchedules()) {
try {
scheduleNextJob(schedule, timestamp);
} catch (Exception e) {
logger.warn("Error in scheduling next job for schedule {}", schedule, e);
}
}
}
/**
* Delete scheduled jobs before vm.scheduler.expire.interval days
*/
private void cleanupVMScheduledJobs() {
Date deleteBeforeDate = DateUtils.addDays(currentTimestamp, -1 * VMScheduledJobExpireInterval.value());
int rowsRemoved = vmScheduledJobDao.expungeJobsBefore(deleteBeforeDate);
logger.info(String.format("Cleaned up %d VM scheduled job entries", rowsRemoved));
}
void executeJobs(Map<Long, VMScheduledJob> jobsToExecute) {
String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
for (Map.Entry<Long, VMScheduledJob> entry : jobsToExecute.entrySet()) {
VMScheduledJob vmScheduledJob = entry.getValue();
VirtualMachine vm = userVmManager.getUserVm(vmScheduledJob.getVmId());
VMScheduledJobVO tmpVMScheduleJob = null;
try {
if (logger.isDebugEnabled()) {
final Date scheduledTimestamp = vmScheduledJob.getScheduledTime();
displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, scheduledTimestamp);
logger.debug("Executing {} for VM {} for scheduled job: {} at {}",
vmScheduledJob.getAction(), vm, vmScheduledJob, displayTime);
}
tmpVMScheduleJob = vmScheduledJobDao.acquireInLockTable(vmScheduledJob.getId());
Long jobId = processJob(vmScheduledJob, vm);
if (jobId != null) {
tmpVMScheduleJob.setAsyncJobId(jobId);
vmScheduledJobDao.update(vmScheduledJob.getId(), tmpVMScheduleJob);
}
} catch (final Exception e) {
logger.warn("Executing scheduled job {} failed due to {}", vmScheduledJob, e);
} finally {
if (tmpVMScheduleJob != null) {
vmScheduledJobDao.releaseFromLockTable(vmScheduledJob.getId());
}
}
}
}
Long processJob(VMScheduledJob vmScheduledJob, VirtualMachine vm) {
if (!Arrays.asList(VirtualMachine.State.Running, VirtualMachine.State.Stopped).contains(vm.getState())) {
logger.info("Skipping action ({}) for [vm: {}, scheduled job: {}] because VM is invalid state: {}",
vmScheduledJob.getAction(), vm, vmScheduledJob, vm.getState());
return null;
}
final Long eventId = ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), null,
actionEventMap.get(vmScheduledJob.getAction()), true,
String.format("Executing action (%s) for VM: %s", vmScheduledJob.getAction(), vm),
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), 0);
if (vm.getState() == VirtualMachine.State.Running) {
switch (vmScheduledJob.getAction()) {
case STOP:
return executeStopVMJob(vm, false, eventId);
case FORCE_STOP:
return executeStopVMJob(vm, true, eventId);
case REBOOT:
return executeRebootVMJob(vm, false, eventId);
case FORCE_REBOOT:
return executeRebootVMJob(vm, true, eventId);
}
} else if (vm.getState() == VirtualMachine.State.Stopped && vmScheduledJob.getAction() == VMSchedule.Action.START) {
return executeStartVMJob(vm, eventId);
}
logger.warn("Skipping action ({}) for [vm: {}, scheduled job: {}] because VM is in state: {}",
vmScheduledJob.getAction(), vm, vmScheduledJob, vm.getState());
return null;
}
private void skipJobs(Map<Long, VMScheduledJob> jobsToExecute, Map<Long, List<VMScheduledJob>> jobsNotToExecute) {
for (Map.Entry<Long, List<VMScheduledJob>> entry : jobsNotToExecute.entrySet()) {
Long vmId = entry.getKey();
List<VMScheduledJob> skippedVmScheduledJobVOS = entry.getValue();
VirtualMachine vm = userVmManager.getUserVm(vmId);
for (final VMScheduledJob skippedVmScheduledJobVO : skippedVmScheduledJobVOS) {
VMScheduledJob scheduledJob = jobsToExecute.get(vmId);
logger.info("Skipping scheduled job {} for vm {} because of conflict with another scheduled job {}",
skippedVmScheduledJobVO, vm, scheduledJob);
}
}
}
/**
* Create async jobs for VM scheduled jobs
*/
private void startJobs() {
String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
final List<VMScheduledJobVO> vmScheduledJobs = vmScheduledJobDao.listJobsToStart(currentTimestamp);
logger.debug(String.format("Got %d scheduled jobs to be executed at %s", vmScheduledJobs.size(), displayTime));
Map<Long, VMScheduledJob> jobsToExecute = new HashMap<>();
Map<Long, List<VMScheduledJob>> jobsNotToExecute = new HashMap<>();
for (final VMScheduledJobVO vmScheduledJobVO : vmScheduledJobs) {
long vmId = vmScheduledJobVO.getVmId();
if (jobsToExecute.get(vmId) == null) {
jobsToExecute.put(vmId, vmScheduledJobVO);
} else {
jobsNotToExecute.computeIfAbsent(vmId, k -> new ArrayList<>()).add(vmScheduledJobVO);
}
}
executeJobs(jobsToExecute);
skipJobs(jobsToExecute, jobsNotToExecute);
}
long executeStartVMJob(VirtualMachine vm, long eventId) {
final Map<String, String> params = new HashMap<>();
params.put(ApiConstants.ID, String.valueOf(vm.getId()));
params.put("ctxUserId", "1");
params.put("ctxAccountId", String.valueOf(vm.getAccountId()));
params.put(ApiConstants.CTX_START_EVENT_ID, String.valueOf(eventId));
final StartVMCmd cmd = new StartVMCmd();
ComponentContext.inject(cmd);
AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, vm.getAccountId(), StartVMCmd.class.getName(), ApiGsonHelper.getBuilder().create().toJson(params), vm.getId(), cmd.getApiResourceType() != null ? cmd.getApiResourceType().toString() : null, null);
job.setDispatcher(asyncJobDispatcher.getName());
return asyncJobManager.submitAsyncJob(job);
}
long executeStopVMJob(VirtualMachine vm, boolean isForced, long eventId) {
final Map<String, String> params = new HashMap<>();
params.put(ApiConstants.ID, String.valueOf(vm.getId()));
params.put("ctxUserId", "1");
params.put("ctxAccountId", String.valueOf(vm.getAccountId()));
params.put(ApiConstants.CTX_START_EVENT_ID, String.valueOf(eventId));
params.put(ApiConstants.FORCED, String.valueOf(isForced));
final StopVMCmd cmd = new StopVMCmd();
ComponentContext.inject(cmd);
AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, vm.getAccountId(), StopVMCmd.class.getName(), ApiGsonHelper.getBuilder().create().toJson(params), vm.getId(), cmd.getApiResourceType() != null ? cmd.getApiResourceType().toString() : null, null);
job.setDispatcher(asyncJobDispatcher.getName());
return asyncJobManager.submitAsyncJob(job);
}
long executeRebootVMJob(VirtualMachine vm, boolean isForced, long eventId) {
final Map<String, String> params = new HashMap<>();
params.put(ApiConstants.ID, String.valueOf(vm.getId()));
params.put("ctxUserId", "1");
params.put("ctxAccountId", String.valueOf(vm.getAccountId()));
params.put(ApiConstants.CTX_START_EVENT_ID, String.valueOf(eventId));
params.put(ApiConstants.FORCED, String.valueOf(isForced));
final RebootVMCmd cmd = new RebootVMCmd();
ComponentContext.inject(cmd);
AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, vm.getAccountId(), RebootVMCmd.class.getName(), ApiGsonHelper.getBuilder().create().toJson(params), vm.getId(), cmd.getApiResourceType() != null ? cmd.getApiResourceType().toString() : null, null);
job.setDispatcher(asyncJobDispatcher.getName());
return asyncJobManager.submitAsyncJob(job);
}
}

View File

@ -372,8 +372,11 @@
<bean id="bucketApiServiceImpl" class="org.apache.cloudstack.storage.object.BucketApiServiceImpl" />
<bean id="routedIpv4ManagerImpl" class="org.apache.cloudstack.network.RoutedIpv4ManagerImpl" />
<bean id="VMScheduleManagerImpl" class="org.apache.cloudstack.vm.schedule.VMScheduleManagerImpl" />
<bean id="VMSchedulerImpl" class="org.apache.cloudstack.vm.schedule.VMSchedulerImpl">
<bean id="ResourceScheduleManagerImpl" class="org.apache.cloudstack.schedule.ResourceScheduleManagerImpl" />
<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>

View File

@ -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);
}
}

View File

@ -0,0 +1,382 @@
/*
* 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;
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;
import com.cloud.utils.Pair;
import com.cloud.utils.db.EntityManager;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
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;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;
import static org.junit.Assert.assertNotNull;
public class ResourceScheduleManagerImplTest {
@Spy
@InjectMocks
ResourceScheduleManagerImpl resourceScheduleManager = new ResourceScheduleManagerImpl();
@Mock
ResourceScheduleDao resourceScheduleDao;
@Mock
ResourceScheduleDetailsDao resourceScheduleDetailsDao;
@Mock
VMScheduleWorker vmScheduleWorker;
@Mock
AutoScaleScheduleWorker autoScaleScheduleWorker;
@Mock
UserVmManager userVmManager;
@Mock
AccountManager accountManager;
@Mock
EntityManager entityManager;
private AutoCloseable closeable;
@Before
public void setUp() {
closeable = MockitoAnnotations.openMocks(this);
Account callingAccount = Mockito.mock(Account.class);
User callingUser = Mockito.mock(User.class);
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);
}
@After
public void tearDown() throws Exception {
closeable.close();
}
private void validateResponse(ResourceScheduleResponse response, ResourceScheduleVO schedule, VirtualMachine vm) {
assertNotNull(response);
Assert.assertEquals(schedule.getUuid(), ReflectionTestUtils.getField(response, "id"));
Assert.assertEquals(schedule.getResourceType(), ReflectionTestUtils.getField(response, "resourceType"));
Assert.assertEquals(vm.getUuid(), ReflectionTestUtils.getField(response, "resourceId"));
Assert.assertEquals(schedule.getSchedule(), ReflectionTestUtils.getField(response, "schedule"));
Assert.assertEquals(schedule.getTimeZone(), ReflectionTestUtils.getField(response, "timeZone"));
ResourceSchedule.Action actionField = (ResourceSchedule.Action) ReflectionTestUtils.getField(response, "action");
Assert.assertEquals(schedule.getActionName(), actionField == null ? null : actionField.name());
Assert.assertEquals(schedule.getStartDate(), ReflectionTestUtils.getField(response, "startDate"));
Assert.assertEquals(schedule.getEndDate(), ReflectionTestUtils.getField(response, "endDate"));
}
@Test
public void createSchedule() {
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
ResourceScheduleVO schedule = Mockito.mock(ResourceScheduleVO.class);
Account ownerAccount = Mockito.mock(Account.class);
Mockito.when(vm.getUuid()).thenReturn(UUID.randomUUID().toString());
Mockito.when(vm.getId()).thenReturn(1L);
Mockito.when(entityManager.findByUuid(VirtualMachine.class, "1")).thenReturn(null);
Mockito.when(entityManager.findById(VirtualMachine.class, 1L)).thenReturn(vm);
Mockito.when(vmScheduleWorker.isResourceValid(1L)).thenReturn(true);
Mockito.when(vmScheduleWorker.getEntityOwnerId(1L)).thenReturn(2L);
Mockito.when(vmScheduleWorker.parseAction("START")).thenReturn(VMScheduleAction.START);
Mockito.when(accountManager.getAccount(2L)).thenReturn(ownerAccount);
Mockito.when(resourceScheduleDao.persist(Mockito.any(ResourceScheduleVO.class))).thenReturn(schedule);
Mockito.when(schedule.getResourceType()).thenReturn(ApiCommandResourceType.VirtualMachine);
Mockito.when(schedule.getResourceId()).thenReturn(1L);
Mockito.when(schedule.getActionName()).thenReturn("START");
Mockito.when(vmScheduleWorker.parseAction((String) Mockito.isNull())).thenReturn(null);
ResourceScheduleResponse response = resourceScheduleManager.createSchedule(
ApiCommandResourceType.VirtualMachine, "1", null,
"0 0 * * *", "UTC", "START",
DateUtils.addDays(new Date(), 1), DateUtils.addDays(new Date(), 2),
true, null);
Mockito.verify(resourceScheduleDao, Mockito.times(1)).persist(Mockito.any(ResourceScheduleVO.class));
validateResponse(response, schedule, vm);
}
@Test
public void createResponse() {
ResourceScheduleVO schedule = Mockito.mock(ResourceScheduleVO.class);
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Mockito.when(vm.getUuid()).thenReturn(UUID.randomUUID().toString());
Mockito.when(schedule.getResourceId()).thenReturn(1L);
Mockito.when(schedule.getResourceType()).thenReturn(ApiCommandResourceType.VirtualMachine);
Mockito.when(schedule.getActionName()).thenReturn("START");
Mockito.when(vmScheduleWorker.parseAction("START")).thenReturn(VMScheduleAction.START);
Mockito.when(entityManager.findById(VirtualMachine.class, 1L)).thenReturn(vm);
ResourceScheduleResponse response = resourceScheduleManager.createResponse(schedule, null);
validateResponse(response, schedule, vm);
}
@Test
public void listSchedule() {
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
ResourceScheduleVO schedule1 = Mockito.mock(ResourceScheduleVO.class);
ResourceScheduleVO schedule2 = Mockito.mock(ResourceScheduleVO.class);
List<ResourceScheduleVO> scheduleList = new ArrayList<>();
scheduleList.add(schedule1);
scheduleList.add(schedule2);
Mockito.when(vm.getUuid()).thenReturn(UUID.randomUUID().toString());
Mockito.when(vm.getId()).thenReturn(1L);
Mockito.when(entityManager.findByUuid(VirtualMachine.class, "1")).thenReturn(null);
Mockito.when(entityManager.findById(VirtualMachine.class, 1L)).thenReturn(vm);
Mockito.when(vmScheduleWorker.getEntityOwnerId(1L)).thenReturn(2L);
Mockito.when(accountManager.getAccount(2L)).thenReturn(Mockito.mock(Account.class));
Mockito.when(resourceScheduleDao.searchAndCount(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any())
).thenReturn(new Pair<>(scheduleList, scheduleList.size()));
for (ResourceScheduleVO schedule : scheduleList) {
Mockito.when(schedule.getResourceId()).thenReturn(1L);
Mockito.when(schedule.getResourceType()).thenReturn(ApiCommandResourceType.VirtualMachine);
}
ListResponse<ResourceScheduleResponse> responseList = resourceScheduleManager.listSchedule(
null, null, ApiCommandResourceType.VirtualMachine, "1", null, null, 0L, 100L);
Mockito.verify(resourceScheduleDao, Mockito.times(1)).searchAndCount(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any());
assertNotNull(responseList);
Assert.assertEquals(2, (int) responseList.getCount());
Assert.assertEquals(2, responseList.getResponses().size());
for (int i = 0; i < responseList.getResponses().size(); i++) {
validateResponse(responseList.getResponses().get(i), scheduleList.get(i), vm);
}
}
@Test
public void updateSchedule() {
ResourceScheduleVO schedule = Mockito.mock(ResourceScheduleVO.class);
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Mockito.when(vm.getUuid()).thenReturn(UUID.randomUUID().toString());
Mockito.when(resourceScheduleDao.findById(Mockito.anyLong())).thenReturn(schedule);
Mockito.when(resourceScheduleDao.update(Mockito.eq(1L), Mockito.any(ResourceScheduleVO.class))).thenReturn(true);
Mockito.when(schedule.getResourceId()).thenReturn(1L);
Mockito.when(schedule.getResourceType()).thenReturn(ApiCommandResourceType.VirtualMachine);
Mockito.when(schedule.getStartDate()).thenReturn(DateUtils.addDays(new Date(), 1));
Mockito.when(schedule.getActionName()).thenReturn("START");
Mockito.when(vmScheduleWorker.getEntityOwnerId(1L)).thenReturn(2L);
Mockito.when(vmScheduleWorker.parseAction("START")).thenReturn(VMScheduleAction.START);
Mockito.when(accountManager.getAccount(2L)).thenReturn(Mockito.mock(Account.class));
Mockito.when(entityManager.findById(VirtualMachine.class, 1L)).thenReturn(vm);
ResourceScheduleResponse response = resourceScheduleManager.updateSchedule(
1L, null, "0 0 * * *", "UTC",
DateUtils.addDays(new Date(), 1), DateUtils.addDays(new Date(), 2), null, null);
Mockito.verify(resourceScheduleDao, Mockito.times(1)).update(Mockito.eq(1L), Mockito.any(ResourceScheduleVO.class));
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);
ResourceScheduleVO schedule2 = Mockito.mock(ResourceScheduleVO.class);
List<ResourceScheduleVO> scheduleList = new ArrayList<>();
scheduleList.add(schedule1);
scheduleList.add(schedule2);
SearchCriteria<ResourceScheduleVO> sc = Mockito.mock(SearchCriteria.class);
Mockito.when(resourceScheduleDao.getSearchCriteriaForResource(
ApiCommandResourceType.VirtualMachine, 1L)).thenReturn(sc);
Mockito.when(resourceScheduleDao.search(sc, null)).thenReturn(scheduleList);
Mockito.when(schedule1.getId()).thenReturn(1L);
Mockito.when(schedule2.getId()).thenReturn(2L);
Mockito.when(resourceScheduleDao.removeAllSchedulesForResource(Mockito.any(), Mockito.anyLong())).thenReturn(2L);
resourceScheduleManager.removeSchedulesForResource(ApiCommandResourceType.VirtualMachine, 1L);
Mockito.verify(resourceScheduleDao, Mockito.times(1)).removeAllSchedulesForResource(Mockito.any(), Mockito.anyLong());
Mockito.verify(vmScheduleWorker, Mockito.times(1)).removeScheduledJobs(Mockito.anyList());
}
@Test
public void removeSchedule() {
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Mockito.when(vm.getId()).thenReturn(1L);
Mockito.when(entityManager.findByUuid(VirtualMachine.class, "1")).thenReturn(null);
Mockito.when(entityManager.findById(VirtualMachine.class, 1L)).thenReturn(vm);
Mockito.when(vmScheduleWorker.getEntityOwnerId(1L)).thenReturn(2L);
Mockito.when(accountManager.getAccount(2L)).thenReturn(Mockito.mock(Account.class));
Mockito.when(resourceScheduleDao.removeSchedulesForResourceAndIds(
Mockito.any(), Mockito.anyLong(), Mockito.anyList())).thenReturn(1L);
ResourceScheduleVO schedule1 = Mockito.mock(ResourceScheduleVO.class);
ResourceScheduleVO schedule2 = Mockito.mock(ResourceScheduleVO.class);
List<ResourceScheduleVO> scheduleList = new ArrayList<>();
scheduleList.add(schedule1);
scheduleList.add(schedule2);
Mockito.when(resourceScheduleDao.searchAndCount(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any())
).thenReturn(new Pair<>(scheduleList, scheduleList.size()));
Long rowsRemoved = resourceScheduleManager.removeSchedule(
ApiCommandResourceType.VirtualMachine, "1", 10L, null);
Mockito.verify(resourceScheduleDao, Mockito.times(1)).removeSchedulesForResourceAndIds(
Mockito.any(), Mockito.anyLong(), Mockito.anyList());
Assert.assertEquals(1L, (long) rowsRemoved);
}
@Test
public void validateStartDateEndDate() {
// Valid scenario 1
// Start date is before end date
Date startDate = DateUtils.addDays(new Date(), 1);
Date endDate = DateUtils.addDays(new Date(), 2);
resourceScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
// Valid Scenario 2
// Start date is before current date and end date is null
endDate = null;
resourceScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
// Invalid Scenario 2
// Start date is before current date
startDate = DateUtils.addDays(new Date(), -1);
try {
resourceScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
Assert.fail("Should have thrown InvalidParameterValueException");
} catch (InvalidParameterValueException e) {
Assert.assertTrue(e.getMessage().contains("Invalid value for start date. Start date") &&
e.getMessage().contains("can't be before current time"));
}
// Invalid Scenario 2
// Start date is after end date
startDate = DateUtils.addDays(new Date(), 2);
endDate = DateUtils.addDays(new Date(), 1);
try {
resourceScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
Assert.fail("Should have thrown InvalidParameterValueException");
} catch (InvalidParameterValueException e) {
Assert.assertTrue(e.getMessage().contains("Invalid value for end date. End date") &&
e.getMessage().contains("can't be before start date"));
}
// Invalid Scenario 3
// End date is before current date
startDate = DateUtils.addDays(new Date(), 1);
endDate = DateUtils.addDays(new Date(), -1);
try {
resourceScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
Assert.fail("Should have thrown InvalidParameterValueException");
} catch (InvalidParameterValueException e) {
Assert.assertTrue(e.getMessage().contains("Invalid value for end date. End date") &&
e.getMessage().contains("can't be before current time"));
}
}
@Test
public void getConfigKeys() {
ConfigKey<?>[] configKeys = resourceScheduleManager.getConfigKeys();
Assert.assertEquals(1, configKeys.length);
Assert.assertEquals(BaseScheduleWorker.ScheduledJobExpireInterval.key(), configKeys[0].key());
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,371 @@
/*
* 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.vm;
import com.cloud.event.ActionEventUtils;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.uservm.UserVm;
import com.cloud.utils.component.ComponentContext;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.api.command.user.vm.RebootVMCmd;
import org.apache.cloudstack.api.command.user.vm.StartVMCmd;
import org.apache.cloudstack.api.command.user.vm.StopVMCmd;
import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher;
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.schedule.ResourceScheduleVO;
import org.apache.cloudstack.schedule.ResourceScheduledJobVO;
import org.apache.cloudstack.schedule.dao.ResourceScheduleDao;
import org.apache.cloudstack.schedule.dao.ResourceScheduledJobDao;
import org.apache.commons.lang.time.DateUtils;
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 org.springframework.test.util.ReflectionTestUtils;
import javax.persistence.EntityExistsException;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class VMScheduleWorkerTest {
@Spy
@InjectMocks
private VMScheduleWorker vmScheduleWorker = new VMScheduleWorker();
@Mock
private UserVmManager userVmManager;
@Mock
private ResourceScheduleDao resourceScheduleDao;
@Mock
private ResourceScheduledJobDao resourceScheduledJobDao;
@Mock
private AsyncJobManager asyncJobManager;
@Mock
private AsyncJobDispatcher asyncJobDispatcher;
private AutoCloseable closeable;
private MockedStatic<ActionEventUtils> actionEventUtilsMocked;
@Before
public void setUp() throws Exception {
closeable = MockitoAnnotations.openMocks(this);
actionEventUtilsMocked = Mockito.mockStatic(ActionEventUtils.class);
Mockito.when(ActionEventUtils.onScheduledActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(),
Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyBoolean(),
Mockito.anyLong()))
.thenReturn(1L);
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 testProcessJobRunning() {
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMScheduleAction.STOP);
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMScheduleAction.FORCE_STOP);
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMScheduleAction.REBOOT);
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMScheduleAction.FORCE_REBOOT);
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Stopped, VMScheduleAction.START);
}
private void executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State state, VMScheduleAction action) {
ResourceScheduledJobVO job = Mockito.mock(ResourceScheduledJobVO.class);
UserVm vm = Mockito.mock(UserVm.class);
Mockito.when(job.getResourceId()).thenReturn(1L);
Mockito.when(job.getActionName()).thenReturn(action.name());
Mockito.when(vm.getState()).thenReturn(state);
Mockito.when(userVmManager.getUserVm(1L)).thenReturn(vm);
Mockito.doReturn(1L).when(vmScheduleWorker).submitAsyncJob(
Mockito.any(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.any());
Long jobId = vmScheduleWorker.processJob(job);
Assert.assertNotNull(jobId);
Assert.assertEquals(1L, (long) jobId);
}
@Test
public void testProcessJobInvalidAction() {
ResourceScheduledJobVO job = Mockito.mock(ResourceScheduledJobVO.class);
UserVm vm = Mockito.mock(UserVm.class);
Mockito.when(job.getResourceId()).thenReturn(1L);
Mockito.when(job.getActionName()).thenReturn(VMScheduleAction.START.name());
Mockito.when(vm.getState()).thenReturn(VirtualMachine.State.Running);
Mockito.when(userVmManager.getUserVm(1L)).thenReturn(vm);
Long jobId = vmScheduleWorker.processJob(job);
Assert.assertNull(jobId);
}
@Test
public void testProcessJobVMInInvalidState() {
ResourceScheduledJobVO job = Mockito.mock(ResourceScheduledJobVO.class);
UserVm vm = Mockito.mock(UserVm.class);
Mockito.when(job.getResourceId()).thenReturn(1L);
Mockito.when(job.getActionName()).thenReturn(VMScheduleAction.START.name());
Mockito.when(vm.getState()).thenReturn(VirtualMachine.State.Unknown);
Mockito.when(userVmManager.getUserVm(1L)).thenReturn(vm);
Long jobId = vmScheduleWorker.processJob(job);
Assert.assertNull(jobId);
}
@Test
public void testScheduleNextJobScheduleScheduleExists() {
Date now = DateUtils.setSeconds(new Date(), 0);
Date startDate = DateUtils.addDays(now, 1);
Date expectedScheduledTime = DateUtils.round(DateUtils.addMinutes(startDate, 1), Calendar.MINUTE);
UserVm vm = Mockito.mock(UserVm.class);
ResourceScheduleVO schedule = Mockito.mock(ResourceScheduleVO.class);
Mockito.when(schedule.getEnabled()).thenReturn(true);
Mockito.when(schedule.getSchedule()).thenReturn("* * * * *");
Mockito.when(schedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("UTC").toZoneId());
Mockito.when(schedule.getStartDate()).thenReturn(startDate);
Mockito.when(schedule.getResourceId()).thenReturn(1L);
Mockito.when(schedule.getActionName()).thenReturn(VMScheduleAction.START.name());
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
Mockito.when(resourceScheduledJobDao.persist(Mockito.any())).thenThrow(EntityExistsException.class);
Date actualScheduledTime = vmScheduleWorker.scheduleNextJob(schedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleFutureSchedule() {
Date now = DateUtils.setSeconds(new Date(), 0);
Date startDate = DateUtils.addDays(now, 1);
Date expectedScheduledTime = DateUtils.round(DateUtils.addMinutes(startDate, 1), Calendar.MINUTE);
UserVm vm = Mockito.mock(UserVm.class);
ResourceScheduleVO schedule = Mockito.mock(ResourceScheduleVO.class);
Mockito.when(schedule.getEnabled()).thenReturn(true);
Mockito.when(schedule.getSchedule()).thenReturn("* * * * *");
Mockito.when(schedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("UTC").toZoneId());
Mockito.when(schedule.getStartDate()).thenReturn(startDate);
Mockito.when(schedule.getResourceId()).thenReturn(1L);
Mockito.when(schedule.getActionName()).thenReturn(VMScheduleAction.START.name());
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
Date actualScheduledTime = vmScheduleWorker.scheduleNextJob(schedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleFutureScheduleWithTimeZoneChecks() throws Exception {
String cron = "30 5 * * *";
Date now = DateUtils.setSeconds(new Date(), 0);
Date startDate = DateUtils.addDays(now, 1);
UserVm vm = Mockito.mock(UserVm.class);
ResourceScheduleVO schedule = Mockito.mock(ResourceScheduleVO.class);
Mockito.when(schedule.getEnabled()).thenReturn(true);
Mockito.when(schedule.getSchedule()).thenReturn(cron);
Mockito.when(schedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("EST").toZoneId());
Mockito.when(schedule.getStartDate()).thenReturn(startDate);
Mockito.when(schedule.getResourceId()).thenReturn(1L);
Mockito.when(schedule.getActionName()).thenReturn(VMScheduleAction.START.name());
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(startDate.toInstant(), schedule.getTimeZoneId());
zonedDateTime = zonedDateTime.withHour(5).withMinute(30).withSecond(0).withNano(0);
Date expectedScheduledTime = Date.from(zonedDateTime.toInstant());
if (expectedScheduledTime.before(startDate)) {
expectedScheduledTime = Date.from(zonedDateTime.plusDays(1).toInstant());
}
Date actualScheduledTime = vmScheduleWorker.scheduleNextJob(schedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleCurrentSchedule() {
Date now = DateUtils.setSeconds(new Date(), 0);
Date expectedScheduledTime = DateUtils.round(DateUtils.addMinutes(now, 1), Calendar.MINUTE);
UserVm vm = Mockito.mock(UserVm.class);
ResourceScheduleVO schedule = Mockito.mock(ResourceScheduleVO.class);
Mockito.when(schedule.getEnabled()).thenReturn(true);
Mockito.when(schedule.getSchedule()).thenReturn("* * * * *");
Mockito.when(schedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("UTC").toZoneId());
Mockito.when(schedule.getStartDate()).thenReturn(DateUtils.addDays(now, -1));
Mockito.when(schedule.getResourceId()).thenReturn(1L);
Mockito.when(schedule.getActionName()).thenReturn(VMScheduleAction.START.name());
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
Date actualScheduledTime = vmScheduleWorker.scheduleNextJob(schedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleCurrentScheduleWithTimeZoneChecks() throws Exception {
String cron = "30 5 * * *";
Date now = DateUtils.setSeconds(new Date(), 0);
UserVm vm = Mockito.mock(UserVm.class);
ResourceScheduleVO schedule = Mockito.mock(ResourceScheduleVO.class);
Mockito.when(schedule.getEnabled()).thenReturn(true);
Mockito.when(schedule.getSchedule()).thenReturn(cron);
Mockito.when(schedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("EST").toZoneId());
Mockito.when(schedule.getStartDate()).thenReturn(DateUtils.addDays(now, -1));
Mockito.when(schedule.getResourceId()).thenReturn(1L);
Mockito.when(schedule.getActionName()).thenReturn(VMScheduleAction.START.name());
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(now.toInstant(), schedule.getTimeZoneId());
zonedDateTime = zonedDateTime.withHour(5).withMinute(30).withSecond(0).withNano(0);
Date expectedScheduledTime = Date.from(zonedDateTime.toInstant());
if (expectedScheduledTime.before(now)) {
expectedScheduledTime = DateUtils.addDays(expectedScheduledTime, 1);
}
Date actualScheduledTime = vmScheduleWorker.scheduleNextJob(schedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleExpired() {
Date now = DateUtils.setSeconds(new Date(), 0);
ResourceScheduleVO schedule = Mockito.mock(ResourceScheduleVO.class);
Mockito.when(schedule.getEnabled()).thenReturn(true);
Mockito.when(schedule.getSchedule()).thenReturn("* * * * *");
Mockito.when(schedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("UTC").toZoneId());
Mockito.when(schedule.getStartDate()).thenReturn(DateUtils.addDays(now, -1));
Mockito.when(schedule.getEndDate()).thenReturn(DateUtils.addMinutes(now, -5));
Mockito.when(schedule.getResourceId()).thenReturn(1L);
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(Mockito.mock(UserVm.class));
Date actualDate = vmScheduleWorker.scheduleNextJob(schedule, new Date());
Assert.assertNull(actualDate);
}
@Test
public void testScheduleNextJobScheduleDisabled() {
ResourceScheduleVO schedule = Mockito.mock(ResourceScheduleVO.class);
Mockito.when(schedule.getEnabled()).thenReturn(false);
Date actualDate = vmScheduleWorker.scheduleNextJob(schedule, new Date());
Assert.assertNull(actualDate);
}
@Test
public void testExecuteJobs() {
ResourceScheduledJobVO job1 = Mockito.mock(ResourceScheduledJobVO.class);
ResourceScheduledJobVO job2 = Mockito.mock(ResourceScheduledJobVO.class);
Mockito.doReturn(null).when(vmScheduleWorker).processJob(job2);
Mockito.when(resourceScheduledJobDao.acquireInLockTable(job1.getId())).thenReturn(job1);
Mockito.when(resourceScheduledJobDao.acquireInLockTable(job2.getId())).thenReturn(job2);
Map<Long, ResourceScheduledJobVO> jobs = new HashMap<>();
jobs.put(1L, job1);
jobs.put(2L, job2);
ReflectionTestUtils.setField(vmScheduleWorker, "currentTimestamp", new Date());
vmScheduleWorker.executeJobs(jobs);
Mockito.verify(vmScheduleWorker, Mockito.times(2)).processJob(Mockito.any());
Mockito.verify(resourceScheduledJobDao, Mockito.times(2)).acquireInLockTable(Mockito.anyLong());
}
@Test
public void testSubmitStopVMJob() {
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Mockito.when(asyncJobManager.submitAsyncJob(Mockito.any(AsyncJobVO.class))).thenReturn(1L);
Mockito.when(asyncJobDispatcher.getName()).thenReturn("ApiAsyncJobDispatcher");
try (MockedStatic<ComponentContext> ignored = Mockito.mockStatic(ComponentContext.class)) {
when(ComponentContext.inject(Mockito.any(StopVMCmd.class))).thenReturn(Mockito.mock(StopVMCmd.class));
long jobId = vmScheduleWorker.submitAsyncJob(StopVMCmd.class, vm.getAccountId(), vm.getId(), 1L,
Map.of("forced", "false"));
Assert.assertEquals(1L, jobId);
}
}
@Test
public void testSubmitRebootVMJob() {
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Mockito.when(asyncJobManager.submitAsyncJob(Mockito.any(AsyncJobVO.class))).thenReturn(1L);
Mockito.when(asyncJobDispatcher.getName()).thenReturn("ApiAsyncJobDispatcher");
try (MockedStatic<ComponentContext> ignored = Mockito.mockStatic(ComponentContext.class)) {
when(ComponentContext.inject(Mockito.any(RebootVMCmd.class))).thenReturn(Mockito.mock(RebootVMCmd.class));
long jobId = vmScheduleWorker.submitAsyncJob(RebootVMCmd.class, vm.getAccountId(), vm.getId(), 1L,
Map.of("forced", "false"));
Assert.assertEquals(1L, jobId);
}
}
@Test
public void testSubmitStartVMJob() {
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Mockito.when(asyncJobManager.submitAsyncJob(Mockito.any(AsyncJobVO.class))).thenReturn(1L);
Mockito.when(asyncJobDispatcher.getName()).thenReturn("ApiAsyncJobDispatcher");
try (MockedStatic<ComponentContext> ignored = Mockito.mockStatic(ComponentContext.class)) {
when(ComponentContext.inject(Mockito.any(StartVMCmd.class))).thenReturn(Mockito.mock(StartVMCmd.class));
long jobId = vmScheduleWorker.submitAsyncJob(StartVMCmd.class, vm.getAccountId(), vm.getId(), 1L,
Map.of());
Assert.assertEquals(1L, jobId);
}
}
@Test
public void parseActionResolvesEnum() {
Assert.assertEquals(VMScheduleAction.START, vmScheduleWorker.parseAction("start"));
Assert.assertEquals(VMScheduleAction.STOP, vmScheduleWorker.parseAction("STOP"));
Assert.assertEquals(VMScheduleAction.FORCE_REBOOT, vmScheduleWorker.parseAction("Force_Reboot"));
}
@Test(expected = InvalidParameterValueException.class)
public void parseActionThrowsOnUnknown() {
vmScheduleWorker.parseAction("BOGUS");
}
}

View File

@ -1,282 +0,0 @@
/*
* 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.vm.schedule;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.User;
import com.cloud.uservm.UserVm;
import com.cloud.utils.Pair;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.api.command.user.vm.CreateVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.DeleteVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.ListVMScheduleCmd;
import org.apache.cloudstack.api.command.user.vm.UpdateVMScheduleCmd;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.VMScheduleResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.vm.schedule.dao.VMScheduleDao;
import org.apache.commons.lang.time.DateUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;
import static org.junit.Assert.assertNotNull;
public class VMScheduleManagerImplTest {
@Spy
@InjectMocks
VMScheduleManagerImpl vmScheduleManager = new VMScheduleManagerImpl();
@Mock
VMScheduleDao vmScheduleDao;
@Mock
VMScheduler vmScheduler;
@Mock
UserVmManager userVmManager;
@Mock
AccountManager accountManager;
private AutoCloseable closeable;
@Before
public void setUp() {
closeable = MockitoAnnotations.openMocks(this);
Account callingAccount = Mockito.mock(Account.class);
User callingUser = Mockito.mock(User.class);
CallContext.register(callingUser, callingAccount);
}
@After
public void tearDown() throws Exception {
closeable.close();
}
private void validateResponse(VMScheduleResponse response, VMSchedule vmSchedule, VirtualMachine vm) {
assertNotNull(response);
Assert.assertEquals(ReflectionTestUtils.getField(response, "id"), vmSchedule.getUuid());
Assert.assertEquals(ReflectionTestUtils.getField(response, "vmId"), vm.getUuid());
Assert.assertEquals(ReflectionTestUtils.getField(response, "schedule"), vmSchedule.getSchedule());
Assert.assertEquals(ReflectionTestUtils.getField(response, "timeZone"), vmSchedule.getTimeZone());
Assert.assertEquals(ReflectionTestUtils.getField(response, "action"), vmSchedule.getAction());
Assert.assertEquals(ReflectionTestUtils.getField(response, "startDate"), vmSchedule.getStartDate());
Assert.assertEquals(ReflectionTestUtils.getField(response, "endDate"), vmSchedule.getEndDate());
}
@Test
public void createSchedule() {
UserVm vm = Mockito.mock(UserVm.class);
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
CreateVMScheduleCmd cmd = Mockito.mock(CreateVMScheduleCmd.class);
Mockito.when(cmd.getVmId()).thenReturn(1L);
Mockito.when(cmd.getSchedule()).thenReturn("0 0 * * *");
Mockito.when(cmd.getTimeZone()).thenReturn("UTC");
Mockito.when(cmd.getAction()).thenReturn("start");
Mockito.when(cmd.getStartDate()).thenReturn(DateUtils.addDays(new Date(), 1));
Mockito.when(cmd.getEndDate()).thenReturn(DateUtils.addDays(new Date(), 2));
Mockito.when(vm.getUuid()).thenReturn(UUID.randomUUID().toString());
Mockito.when(vmScheduleDao.persist(Mockito.any(VMScheduleVO.class))).thenReturn(vmSchedule);
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.eq(false), Mockito.eq(vm));
VMScheduleResponse response = vmScheduleManager.createSchedule(cmd);
Mockito.verify(vmScheduleDao, Mockito.times(1)).persist(Mockito.any(VMScheduleVO.class));
validateResponse(response, vmSchedule, vm);
}
@Test
public void createResponse() {
VMSchedule vmSchedule = Mockito.mock(VMSchedule.class);
UserVm vm = Mockito.mock(UserVm.class);
Mockito.when(vmSchedule.getVmId()).thenReturn(1L);
Mockito.when(userVmManager.getUserVm(vmSchedule.getVmId())).thenReturn(vm);
VMScheduleResponse response = vmScheduleManager.createResponse(vmSchedule);
validateResponse(response, vmSchedule, vm);
}
@Test
public void listSchedule() {
UserVm vm = Mockito.mock(UserVm.class);
VMScheduleVO vmSchedule1 = Mockito.mock(VMScheduleVO.class);
VMScheduleVO vmSchedule2 = Mockito.mock(VMScheduleVO.class);
List<VMScheduleVO> vmScheduleList = new ArrayList<>();
vmScheduleList.add(vmSchedule1);
vmScheduleList.add(vmSchedule2);
Mockito.when(vm.getUuid()).thenReturn(UUID.randomUUID().toString());
Mockito.when(userVmManager.getUserVm(1L)).thenReturn(vm);
Mockito.when(
vmScheduleDao.searchAndCount(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(),
Mockito.anyBoolean(), Mockito.anyLong(), Mockito.anyLong())
).thenReturn(new Pair<>(vmScheduleList, vmScheduleList.size()));
Mockito.when(vmSchedule1.getVmId()).thenReturn(1L);
Mockito.when(vmSchedule2.getVmId()).thenReturn(1L);
ListVMScheduleCmd cmd = Mockito.mock(ListVMScheduleCmd.class);
Mockito.when(cmd.getVmId()).thenReturn(1L);
ListResponse<VMScheduleResponse> responseList = vmScheduleManager.listSchedule(cmd);
Mockito.verify(vmScheduleDao, Mockito.times(1)).searchAndCount(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(),
Mockito.anyBoolean(), Mockito.anyLong(), Mockito.anyLong());
assertNotNull(responseList);
Assert.assertEquals(2, (int) responseList.getCount());
Assert.assertEquals(2, responseList.getResponses().size());
for (int i = 0; i < responseList.getResponses().size(); i++) {
VMScheduleResponse response = responseList.getResponses().get(i);
VMScheduleVO vmSchedule = vmScheduleList.get(i);
validateResponse(response, vmSchedule, vm);
}
}
@Test
public void updateSchedule() {
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
UpdateVMScheduleCmd cmd = Mockito.mock(UpdateVMScheduleCmd.class);
UserVm vm = Mockito.mock(UserVm.class);
Mockito.when(cmd.getId()).thenReturn(1L);
Mockito.when(cmd.getSchedule()).thenReturn("0 0 * * *");
Mockito.when(cmd.getTimeZone()).thenReturn("UTC");
Mockito.when(cmd.getStartDate()).thenReturn(DateUtils.addDays(new Date(), 1));
Mockito.when(cmd.getEndDate()).thenReturn(DateUtils.addDays(new Date(), 2));
Mockito.when(vmScheduleDao.findById(Mockito.anyLong())).thenReturn(vmSchedule);
Mockito.when(vmScheduleDao.update(Mockito.eq(cmd.getId()), Mockito.any(VMScheduleVO.class))).thenReturn(true);
Mockito.when(vmSchedule.getVmId()).thenReturn(1L);
Mockito.when(vmSchedule.getStartDate()).thenReturn(DateUtils.addDays(new Date(), 1));
Mockito.when(userVmManager.getUserVm(vmSchedule.getVmId())).thenReturn(vm);
VMScheduleResponse response = vmScheduleManager.updateSchedule(cmd);
Mockito.verify(vmScheduleDao, Mockito.times(1)).update(Mockito.eq(cmd.getId()), Mockito.any(VMScheduleVO.class));
validateResponse(response, vmSchedule, vm);
}
@Test
public void removeScheduleByVmId() {
UserVm vm = Mockito.mock(UserVm.class);
VMScheduleVO vmSchedule1 = Mockito.mock(VMScheduleVO.class);
VMScheduleVO vmSchedule2 = Mockito.mock(VMScheduleVO.class);
List<VMScheduleVO> vmScheduleList = new ArrayList<>();
vmScheduleList.add(vmSchedule1);
vmScheduleList.add(vmSchedule2);
SearchCriteria<VMScheduleVO> sc = Mockito.mock(SearchCriteria.class);
Mockito.when(vm.getId()).thenReturn(1L);
Mockito.when(vmScheduleDao.getSearchCriteriaForVMId(vm.getId())).thenReturn(sc);
Mockito.when(vmScheduleDao.search(sc, null)).thenReturn(vmScheduleList);
Mockito.when(vmSchedule1.getId()).thenReturn(1L);
Mockito.when(vmSchedule2.getId()).thenReturn(2L);
Mockito.when(vmScheduleDao.remove(sc)).thenReturn(2);
long rowsRemoved = vmScheduleManager.removeScheduleByVmId(vm.getId(), false);
Mockito.verify(vmScheduleDao, Mockito.times(1)).remove(sc);
Assert.assertEquals(2, rowsRemoved);
}
@Test
public void removeSchedule() {
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
UserVm vm = Mockito.mock(UserVm.class);
DeleteVMScheduleCmd cmd = Mockito.mock(DeleteVMScheduleCmd.class);
Mockito.when(cmd.getId()).thenReturn(1L);
Mockito.when(cmd.getVmId()).thenReturn(1L);
Mockito.when(vmSchedule.getVmId()).thenReturn(1L);
Mockito.when(userVmManager.getUserVm(cmd.getVmId())).thenReturn(vm);
Mockito.when(vmScheduleDao.findById(Mockito.anyLong())).thenReturn(vmSchedule);
Mockito.when(vmScheduleDao.removeSchedulesForVmIdAndIds(Mockito.anyLong(), Mockito.anyList())).thenReturn(1L);
Long rowsRemoved = vmScheduleManager.removeSchedule(cmd);
Mockito.verify(vmScheduleDao, Mockito.times(1)).removeSchedulesForVmIdAndIds(Mockito.anyLong(), Mockito.anyList());
Assert.assertEquals(1L, (long) rowsRemoved);
}
@Test
public void validateStartDateEndDate() {
// Valid scenario 1
// Start date is before end date
Date startDate = DateUtils.addDays(new Date(), 1);
Date endDate = DateUtils.addDays(new Date(), 2);
vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
// Valid Scenario 2
// Start date is before current date and end date is null
endDate = null;
vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
// Invalid Scenario 2
// Start date is before current date
startDate = DateUtils.addDays(new Date(), -1);
try {
vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
Assert.fail("Should have thrown InvalidParameterValueException");
} catch (InvalidParameterValueException e) {
Assert.assertTrue(e.getMessage().contains("Invalid value for start date. Start date") &&
e.getMessage().contains("can't be before current time"));
}
// Invalid Scenario 2
// Start date is after end date
startDate = DateUtils.addDays(new Date(), 2);
endDate = DateUtils.addDays(new Date(), 1);
try {
vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
Assert.fail("Should have thrown InvalidParameterValueException");
} catch (InvalidParameterValueException e) {
Assert.assertTrue(e.getMessage().contains("Invalid value for end date. End date") &&
e.getMessage().contains("can't be before start date"));
}
// Invalid Scenario 3
// End date is before current date
startDate = DateUtils.addDays(new Date(), 1);
endDate = DateUtils.addDays(new Date(), -1);
try {
vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
Assert.fail("Should have thrown InvalidParameterValueException");
} catch (InvalidParameterValueException e) {
Assert.assertTrue(e.getMessage().contains("Invalid value for end date. End date") &&
e.getMessage().contains("can't be before current time"));
}
}
}

View File

@ -1,388 +0,0 @@
/*
* 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.vm.schedule;
import com.cloud.event.ActionEventUtils;
import com.cloud.user.User;
import com.cloud.uservm.UserVm;
import com.cloud.utils.component.ComponentContext;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.command.user.vm.RebootVMCmd;
import org.apache.cloudstack.api.command.user.vm.StartVMCmd;
import org.apache.cloudstack.api.command.user.vm.StopVMCmd;
import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher;
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.vm.schedule.dao.VMScheduleDao;
import org.apache.cloudstack.vm.schedule.dao.VMScheduledJobDao;
import org.apache.commons.lang.time.DateUtils;
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 org.springframework.test.util.ReflectionTestUtils;
import javax.persistence.EntityExistsException;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class VMSchedulerImplTest {
@Spy
@InjectMocks
private VMSchedulerImpl vmScheduler = new VMSchedulerImpl();
@Mock
private UserVmManager userVmManager;
@Mock
private VMScheduleDao vmScheduleDao;
@Mock
private VMScheduledJobDao vmScheduledJobDao;
@Mock
private EnumMap<VMSchedule.Action, String> actionEventMap;
@Mock
private AsyncJobManager asyncJobManager;
@Mock
private AsyncJobDispatcher asyncJobDispatcher;
private AutoCloseable closeable;
private MockedStatic<ActionEventUtils> actionEventUtilsMocked;
@Before
public void setUp() throws Exception {
closeable = MockitoAnnotations.openMocks(this);
actionEventUtilsMocked = Mockito.mockStatic(ActionEventUtils.class);
Mockito.when(ActionEventUtils.onScheduledActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(),
Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyBoolean(),
Mockito.anyLong()))
.thenReturn(1L);
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 testProcessJobRunning() {
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMSchedule.Action.STOP);
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMSchedule.Action.FORCE_STOP);
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMSchedule.Action.REBOOT);
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMSchedule.Action.FORCE_REBOOT);
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Stopped, VMSchedule.Action.START);
}
private void executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State state, VMSchedule.Action action) {
VMScheduledJob vmScheduledJob = Mockito.mock(VMScheduledJob.class);
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Long expectedValue = 1L;
prepareMocksForProcessJob(vm, vmScheduledJob, state, action, expectedValue);
Long jobId = vmScheduler.processJob(vmScheduledJob, vm);
actionEventUtilsMocked.verify(() -> ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), null,
actionEventMap.get(action), true,
String.format("Executing action (%s) for VM: %s", vmScheduledJob.getAction(), vm),
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), 0));
Assert.assertEquals(expectedValue, jobId);
}
private void prepareMocksForProcessJob(VirtualMachine vm, VMScheduledJob vmScheduledJob,
VirtualMachine.State vmState, VMSchedule.Action action,
Long executeJobReturnValue) {
Mockito.when(vm.getState()).thenReturn(vmState);
Mockito.when(vmScheduledJob.getAction()).thenReturn(action);
if (executeJobReturnValue != null) {
Mockito.doReturn(executeJobReturnValue).when(vmScheduler).executeStartVMJob(
Mockito.any(VirtualMachine.class), Mockito.anyLong());
Mockito.doReturn(executeJobReturnValue).when(vmScheduler).executeStopVMJob(
Mockito.any(VirtualMachine.class), Mockito.anyBoolean(), Mockito.anyLong());
Mockito.doReturn(executeJobReturnValue).when(vmScheduler).executeRebootVMJob(
Mockito.any(VirtualMachine.class), Mockito.anyBoolean(), Mockito.anyLong());
}
}
@Test
public void testProcessJobInvalidAction() {
VMScheduledJob vmScheduledJob = Mockito.mock(VMScheduledJob.class);
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
prepareMocksForProcessJob(vm, vmScheduledJob, VirtualMachine.State.Running, VMSchedule.Action.START, null);
Long jobId = vmScheduler.processJob(vmScheduledJob, vm);
Assert.assertNull(jobId);
}
@Test
public void testProcessJobVMInInvalidState() {
VMScheduledJob vmScheduledJob = Mockito.mock(VMScheduledJob.class);
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
prepareMocksForProcessJob(vm, vmScheduledJob, VirtualMachine.State.Unknown, VMSchedule.Action.START, null);
Long jobId = vmScheduler.processJob(vmScheduledJob, vm);
Assert.assertNull(jobId);
}
@Test
public void testScheduleNextJobScheduleScheduleExists() {
Date now = DateUtils.setSeconds(new Date(), 0);
Date startDate = DateUtils.addDays(now, 1);
Date expectedScheduledTime = DateUtils.round(DateUtils.addMinutes(startDate, 1), Calendar.MINUTE);
UserVm vm = Mockito.mock(UserVm.class);
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
Mockito.when(vmSchedule.getSchedule()).thenReturn("* * * * *");
Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("UTC").toZoneId());
Mockito.when(vmSchedule.getStartDate()).thenReturn(startDate);
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
Mockito.when(vmScheduledJobDao.persist(Mockito.any())).thenThrow(EntityExistsException.class);
Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleFutureSchedule() {
Date now = DateUtils.setSeconds(new Date(), 0);
Date startDate = DateUtils.addDays(now, 1);
Date expectedScheduledTime = DateUtils.round(DateUtils.addMinutes(startDate, 1), Calendar.MINUTE);
UserVm vm = Mockito.mock(UserVm.class);
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
Mockito.when(vmSchedule.getSchedule()).thenReturn("* * * * *");
Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("UTC").toZoneId());
Mockito.when(vmSchedule.getStartDate()).thenReturn(startDate);
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleFutureScheduleWithTimeZoneChecks() throws Exception {
// Ensure that Date vmSchedulerImpl.scheduleNextJob(VMScheduleVO vmSchedule) generates
// the correct scheduled time on basis of schedule(cron), start date
// and the timezone of the user. The system running the test can have any timezone.
String cron = "30 5 * * *";
Date now = DateUtils.setSeconds(new Date(), 0);
Date startDate = DateUtils.addDays(now, 1);
UserVm vm = Mockito.mock(UserVm.class);
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
Mockito.when(vmSchedule.getSchedule()).thenReturn(cron);
Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("EST").toZoneId());
Mockito.when(vmSchedule.getStartDate()).thenReturn(startDate);
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
// The timezone of the user is EST. The cron expression is 30 5 * * *.
// The start date is 1 day from now. The expected scheduled time is 5:30 AM EST.
// The actual scheduled time is 10:30 AM UTC.
// The actual scheduled time is 5:30 AM EST.
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(startDate.toInstant(), vmSchedule.getTimeZoneId());
zonedDateTime = zonedDateTime.withHour(5).withMinute(30).withSecond(0).withNano(0);
Date expectedScheduledTime = Date.from(zonedDateTime.toInstant());
if (expectedScheduledTime.before(startDate)) {
expectedScheduledTime = Date.from(zonedDateTime.plusDays(1).toInstant());
}
Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleCurrentSchedule() {
Date now = DateUtils.setSeconds(new Date(), 0);
Date expectedScheduledTime = DateUtils.round(DateUtils.addMinutes(now, 1), Calendar.MINUTE);
UserVm vm = Mockito.mock(UserVm.class);
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
Mockito.when(vmSchedule.getSchedule()).thenReturn("* * * * *");
Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("UTC").toZoneId());
Mockito.when(vmSchedule.getStartDate()).thenReturn(DateUtils.addDays(now, -1));
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleCurrentScheduleWithTimeZoneChecks() throws Exception {
// Ensure that Date vmSchedulerImpl.scheduleNextJob(VMScheduleVO vmSchedule) generates
// the correct scheduled time on basis of schedule(cron), start date
// and the timezone of the user. The system running the test can have any timezone.
String cron = "30 5 * * *";
Date now = DateUtils.setSeconds(new Date(), 0);
UserVm vm = Mockito.mock(UserVm.class);
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
Mockito.when(vmSchedule.getSchedule()).thenReturn(cron);
Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("EST").toZoneId());
Mockito.when(vmSchedule.getStartDate()).thenReturn(DateUtils.addDays(now, -1));
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
// The timezone of the user is EST. The cron expression is 30 5 * * *.
// The start date is 1 day ago. The expected scheduled time is 5:30 AM EST.
// The actual scheduled time is 10:30 AM UTC.
// The actual scheduled time is 5:30 AM EST.
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(now.toInstant(), vmSchedule.getTimeZoneId());
zonedDateTime = zonedDateTime.withHour(5).withMinute(30).withSecond(0).withNano(0);
Date expectedScheduledTime = Date.from(zonedDateTime.toInstant());
if (expectedScheduledTime.before(now)) {
expectedScheduledTime = DateUtils.addDays(expectedScheduledTime, 1);
}
Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleExpired() {
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
Mockito.when(vmSchedule.getEndDate()).thenReturn(DateUtils.addMinutes(new Date(), -5));
Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
Date actualDate = vmScheduler.scheduleNextJob(vmSchedule, new Date());
Assert.assertNull(actualDate);
}
@Test
public void testScheduleNextJobScheduleDisabled() {
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
Mockito.when(vmSchedule.getEnabled()).thenReturn(false);
Date actualDate = vmScheduler.scheduleNextJob(vmSchedule, new Date());
Assert.assertNull(actualDate);
}
@Test
public void testExecuteJobs() {
/*
Test VMSchedulerImpl.executeJobs() method with a map of VMScheduledJob objects
covering all the possible scenarios
1. When the job is executed successfully
2. When the job is skipped (processJob returns null)
*/
VMScheduledJobVO job1 = Mockito.mock(VMScheduledJobVO.class);
VMScheduledJobVO job2 = Mockito.mock(VMScheduledJobVO.class);
UserVm vm1 = Mockito.mock(UserVm.class);
UserVm vm2 = Mockito.mock(UserVm.class);
Mockito.when(job2.getVmId()).thenReturn(2L);
Mockito.when(userVmManager.getUserVm(2L)).thenReturn(vm2);
Mockito.doReturn(null).when(vmScheduler).processJob(job2, vm2);
Mockito.when(vmScheduledJobDao.acquireInLockTable(job1.getId())).thenReturn(job1);
Mockito.when(vmScheduledJobDao.acquireInLockTable(job2.getId())).thenReturn(job2);
Map<Long, VMScheduledJob> jobs = new HashMap<>();
jobs.put(1L, job1);
jobs.put(2L, job2);
ReflectionTestUtils.setField(vmScheduler, "currentTimestamp", new Date());
vmScheduler.executeJobs(jobs);
Mockito.verify(vmScheduler, Mockito.times(2)).processJob(Mockito.any(), Mockito.any());
Mockito.verify(vmScheduledJobDao, Mockito.times(2)).acquireInLockTable(Mockito.anyLong());
}
@Test
public void testExecuteStopVMJob() {
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Mockito.when(asyncJobManager.submitAsyncJob(Mockito.any(AsyncJobVO.class))).thenReturn(1L);
try (MockedStatic<ComponentContext> ignored = Mockito.mockStatic(ComponentContext.class)) {
when(ComponentContext.inject(StopVMCmd.class)).thenReturn(Mockito.mock(StopVMCmd.class));
long jobId = vmScheduler.executeStopVMJob(vm, false, 1L);
Assert.assertEquals(1L, jobId);
}
}
@Test
public void testExecuteRebootVMJob() {
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Mockito.when(asyncJobManager.submitAsyncJob(Mockito.any(AsyncJobVO.class))).thenReturn(1L);
try (MockedStatic<ComponentContext> ignored = Mockito.mockStatic(ComponentContext.class)) {
when(ComponentContext.inject(RebootVMCmd.class)).thenReturn(Mockito.mock(RebootVMCmd.class));
long jobId = vmScheduler.executeRebootVMJob(vm, false, 1L);
Assert.assertEquals(1L, jobId);
}
}
@Test
public void testExecuteStartVMJob() {
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Mockito.when(asyncJobManager.submitAsyncJob(Mockito.any(AsyncJobVO.class))).thenReturn(1L);
try (MockedStatic<ComponentContext> ignored = Mockito.mockStatic(ComponentContext.class)) {
when(ComponentContext.inject(StartVMCmd.class)).thenReturn(Mockito.mock(StartVMCmd.class));
long jobId = vmScheduler.executeStartVMJob(vm, 1L);
Assert.assertEquals(1L, jobId);
}
}
}

View File

@ -17,7 +17,7 @@
""" P1 tests for VM Schedule
"""
from marvin.cloudstackTestCase import cloudstackTestCase
from marvin.lib.base import Account, ServiceOffering, VirtualMachine, VMSchedule
from marvin.lib.base import Account, ServiceOffering, VirtualMachine, ResourceSchedule
from marvin.lib.common import get_domain, get_zone, get_template
from marvin.lib.utils import cleanup_resources
@ -28,6 +28,8 @@ import datetime
import time
RESOURCE_TYPE = "VirtualMachine"
class Services:
"""Test Snapshots Services"""
@ -148,8 +150,9 @@ class TestVMSchedule(cloudstackTestCase):
# Create VM Schedule
schedule = "0 0 1 * *"
vmschedule = VMSchedule.create(
vmschedule = ResourceSchedule.create(
self.apiclient,
RESOURCE_TYPE,
self.virtual_machine.id,
"start",
schedule,
@ -166,8 +169,8 @@ class TestVMSchedule(cloudstackTestCase):
self.debug("Created VM Schedule with ID: %s" % vmschedule.id)
# List VM Schedule
vmschedules = VMSchedule.list(
self.apiclient, self.virtual_machine.id, id=vmschedule.id
vmschedules = ResourceSchedule.list(
self.apiclient, RESOURCE_TYPE, self.virtual_machine.id, id=vmschedule.id
)
self.assertEqual(
@ -187,9 +190,15 @@ class TestVMSchedule(cloudstackTestCase):
)
self.assertEqual(
vmschedules[0].virtualmachineid,
vmschedules[0].resourceid,
self.virtual_machine.id,
"Check VM ID in list resources call",
"Check resource ID in list resources call",
)
self.assertEqual(
vmschedules[0].resourcetype,
RESOURCE_TYPE,
"Check resource type in list resources call",
)
self.assertEqual(
@ -204,9 +213,9 @@ class TestVMSchedule(cloudstackTestCase):
"Check VM Schedule timezone in list resources call",
)
# Check for entry in vm_scheduled_job in db
# Check for entry in resource_scheduled_job in db
vmscheduled_job = self.dbclient.execute(
"select * from vm_scheduled_job where vm_schedule_id IN (SELECT id FROM vm_schedule WHERE uuid = '%s')"
"select * from resource_scheduled_job where schedule_id IN (SELECT id FROM resource_schedule WHERE uuid = '%s')"
% vmschedule.id,
db="cloud",
)
@ -214,13 +223,13 @@ class TestVMSchedule(cloudstackTestCase):
self.assertIsInstance(
vmscheduled_job,
list,
"Check if VM Schedule exists in vm_scheduled_job table",
"Check if VM Schedule exists in resource_scheduled_job table",
)
self.assertGreater(
len(vmscheduled_job),
0,
"Check if VM Schedule exists in vm_scheduled_job table",
"Check if VM Schedule exists in resource_scheduled_job table",
)
return
@ -237,8 +246,9 @@ class TestVMSchedule(cloudstackTestCase):
# Create VM Schedule with invalid virtual machine ID
with self.assertRaises(Exception):
VMSchedule.create(
ResourceSchedule.create(
self.apiclient,
RESOURCE_TYPE,
"invalid",
"start",
"0 0 1 * *",
@ -251,8 +261,9 @@ class TestVMSchedule(cloudstackTestCase):
# Create VM Schedule with invalid schedule
with self.assertRaises(Exception):
VMSchedule.create(
ResourceSchedule.create(
self.apiclient,
RESOURCE_TYPE,
self.virtual_machine.id,
"start",
"invalid",
@ -265,8 +276,9 @@ class TestVMSchedule(cloudstackTestCase):
# Create VM Schedule with invalid start date
with self.assertRaises(Exception):
VMSchedule.create(
ResourceSchedule.create(
self.apiclient,
RESOURCE_TYPE,
self.virtual_machine.id,
"start",
"0 0 1 * *",
@ -277,8 +289,9 @@ class TestVMSchedule(cloudstackTestCase):
# Create VM Schedule with invalid action
with self.assertRaises(Exception):
VMSchedule.create(
ResourceSchedule.create(
self.apiclient,
RESOURCE_TYPE,
self.virtual_machine.id,
"invalid",
"0 0 1 * *",
@ -291,8 +304,9 @@ class TestVMSchedule(cloudstackTestCase):
# test invalid end date
with self.assertRaises(Exception):
VMSchedule.create(
ResourceSchedule.create(
self.apiclient,
RESOURCE_TYPE,
self.virtual_machine.id,
"start",
"0 0 1 * *",
@ -315,8 +329,9 @@ class TestVMSchedule(cloudstackTestCase):
# Create VM Schedule
schedule = "0 0 1 * *"
vmschedule = VMSchedule.create(
vmschedule = ResourceSchedule.create(
self.apiclient,
RESOURCE_TYPE,
self.virtual_machine.id,
"start",
schedule,
@ -351,8 +366,8 @@ class TestVMSchedule(cloudstackTestCase):
self.debug("Updated VM Schedule with ID: %s" % vmschedule.id)
# List VM Schedule
vmschedules = VMSchedule.list(
self.apiclient, self.virtual_machine.id, id=vmschedule.id
vmschedules = ResourceSchedule.list(
self.apiclient, RESOURCE_TYPE, self.virtual_machine.id, id=vmschedule.id
)
self.assertEqual(
isinstance(vmschedules, list),
@ -371,11 +386,17 @@ class TestVMSchedule(cloudstackTestCase):
)
self.assertEqual(
vmschedules[0].virtualmachineid,
vmschedules[0].resourceid,
self.virtual_machine.id,
"Check VM ID in list resources call",
)
self.assertEqual(
vmschedules[0].resourcetype,
RESOURCE_TYPE,
"Check VM ID in list resources call",
)
self.assertEqual(
vmschedules[0].schedule,
new_schedule,
@ -395,8 +416,9 @@ class TestVMSchedule(cloudstackTestCase):
# Create VM Schedule
schedule = "0 0 1 * *"
vmschedule = VMSchedule.create(
vmschedule = ResourceSchedule.create(
self.apiclient,
RESOURCE_TYPE,
self.virtual_machine.id,
"start",
schedule,
@ -486,8 +508,9 @@ class TestVMSchedule(cloudstackTestCase):
# Create VM Schedule - start
start_schedule = "*/2 * * * *"
start_vmschedule = VMSchedule.create(
start_vmschedule = ResourceSchedule.create(
self.apiclient,
RESOURCE_TYPE,
self.virtual_machine.id,
"start",
start_schedule,
@ -503,8 +526,9 @@ class TestVMSchedule(cloudstackTestCase):
# Create VM Schedule - stop
stop_schedule = "*/1 * * * *"
stop_vmschedule = VMSchedule.create(
stop_vmschedule = ResourceSchedule.create(
self.apiclient,
RESOURCE_TYPE,
self.virtual_machine.id,
"stop",
stop_schedule,
@ -519,8 +543,8 @@ class TestVMSchedule(cloudstackTestCase):
self.debug("Created VM Schedule with ID: %s" % stop_vmschedule.id)
# Verify VM Schedule is created
vmschedules = VMSchedule.list(
self.apiclient, self.virtual_machine.id, id=start_vmschedule.id
vmschedules = ResourceSchedule.list(
self.apiclient, RESOURCE_TYPE, self.virtual_machine.id, id=start_vmschedule.id
)
self.assertEqual(
@ -575,15 +599,15 @@ class TestVMSchedule(cloudstackTestCase):
# Verify VM Schedule is deleted
self.assertEqual(
VMSchedule.list(
self.apiclient, self.virtual_machine.id, id=start_vmschedule.id
ResourceSchedule.list(
self.apiclient, RESOURCE_TYPE, self.virtual_machine.id, id=start_vmschedule.id
),
None,
"Check VM Schedule is deleted",
)
self.assertEqual(
VMSchedule.list(
self.apiclient, self.virtual_machine.id, id=stop_vmschedule.id
ResourceSchedule.list(
self.apiclient, RESOURCE_TYPE, self.virtual_machine.id, id=stop_vmschedule.id
),
None,
"Check VM Schedule is deleted",

View File

@ -238,6 +238,7 @@ known_categories = {
'removeQuarantinedIp': 'IP Quarantine',
'Shutdown': 'Maintenance',
'Maintenance': 'Maintenance',
'ResourceSchedule': 'Resource Schedule',
'addObjectStoragePool': 'Object Store',
'listObjectStoragePools': 'Object Store',
'deleteObjectStoragePool': 'Object Store',

View File

@ -7052,6 +7052,51 @@ class VMSchedule:
cmd.virtualmachineid = self.virtualmachineid
return (apiclient.deleteVMSchedule(cmd))
class ResourceSchedule:
def __init__(self, items):
self.__dict__.update(items)
@classmethod
def create(cls, apiclient, resourcetype, resourceid, action, schedule, timezone, startdate, enddate=None, enabled=False, description=None, details=None):
cmd = createResourceSchedule.createResourceScheduleCmd()
cmd.resourcetype = resourcetype
cmd.resourceid = resourceid
cmd.description = description
cmd.action = action
cmd.schedule = schedule
cmd.timezone = timezone
cmd.startdate = startdate
cmd.enddate = enddate
cmd.enabled = enabled
cmd.details = details
return ResourceSchedule(apiclient.createResourceSchedule(cmd).__dict__)
@classmethod
def list(cls, apiclient, resourcetype, resourceid, id=None, enabled=None, action=None):
cmd = listResourceSchedule.listResourceScheduleCmd()
cmd.resourcetype = resourcetype
cmd.resourceid = resourceid
cmd.id = id
cmd.enabled = enabled
cmd.action = action
return apiclient.listResourceSchedule(cmd)
def update(self, apiclient, **kwargs):
cmd = updateResourceSchedule.updateResourceScheduleCmd()
cmd.id = self.id
[setattr(cmd, k, v) for k, v in list(kwargs.items())]
return apiclient.updateResourceSchedule(cmd)
def delete(self, apiclient):
cmd = deleteResourceSchedule.deleteResourceScheduleCmd()
cmd.id = self.id
cmd.resourcetype = self.resourcetype
cmd.resourceid = self.resourceid
return apiclient.deleteResourceSchedule(cmd)
class VnfTemplate:
"""Manage VNF template life cycle"""

View File

@ -2241,6 +2241,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",
@ -2780,7 +2781,8 @@
"label.vmlimit": "Instance limits",
"label.vmname": "Instance name",
"label.vms": "Instances",
"label.vmscheduleactions": "Actions",
"label.scheduleactions": "Actions",
"label.vmstate": "Instance state",
"label.vmtotal": "Total of Instances",
"label.vmware": "VMware",
@ -3536,7 +3538,7 @@
"message.error.remove.nic": "There was an error",
"message.error.remove.secondary.ipaddress": "There was an error removing the secondary IP Address",
"message.error.remove.tungsten.routing.policy": "Removing Tungsten-Fabric Routing Policy from Network failed",
"message.error.remove.vm.schedule": "Removing Instance Schedule failed",
"message.error.remove.resource.schedule": "Removing Schedule for the resource failed",
"message.error.required.input": "Please enter input",
"message.error.reset.config": "Unable to reset config to default value",
"message.error.retrieve.kubeconfig": "Unable to retrieve Kubernetes Cluster config",
@ -3931,7 +3933,7 @@
"message.success.config.backup.schedule": "Successfully configured Instance backup schedule",
"message.success.config.health.monitor": "Successfully Configure Health Monitor",
"message.success.config.sticky.policy": "Successfully configured sticky policy",
"message.success.config.vm.schedule": "Successfully configured Instance schedule",
"message.success.config.resource.schedule": "Successfully configured schedule for the resource",
"message.success.copy.clipboard": "Successfully copied to clipboard",
"message.success.create.account": "Successfully created Account",
"message.success.create.asnrange": "Successfully created AS Range",

View File

@ -2466,7 +2466,7 @@
"label.vmlimit": "ఉదాహరణ పరిమితులు",
"label.vmname": "ఉదాహరణ పేరు",
"label.vms": "సందర్భాలు",
"label.vmscheduleactions": "చర్యలు",
"label.scheduleactions": "చర్యలు",
"label.vmstate": "ఉదాహరణ స్థితి",
"label.vmtotal": "మొత్తం సందర్భాలు",
"label.vmware": "VMware",
@ -3107,7 +3107,7 @@
"message.error.remove.nic": "లోపం ఏర్పడింది",
"message.error.remove.secondary.ipaddress": "ద్వితీయ IP చిరునామాను తీసివేయడంలో లోపం ఏర్పడింది",
"message.error.remove.tungsten.routing.policy": "నెట్‌వర్క్ నుండి టంగ్‌స్టన్-ఫాబ్రిక్ రూటింగ్ విధానాన్ని తీసివేయడం విఫలమైంది",
"message.error.remove.vm.schedule": "ఉదాహరణ షెడ్యూల్‌ని తీసివేయడం విఫలమైంది",
"message.error.remove.resource.schedule": "వనరు కోసం షెడ్యూల్‌ను తొలగించడం విఫలమైంది",
"message.error.required.input": "దయచేసి ఇన్‌పుట్‌ని నమోదు చేయండి",
"message.error.reset.config": "కాన్ఫిగర్‌ని డిఫాల్ట్ విలువకు రీసెట్ చేయడం సాధ్యపడలేదు",
"message.error.retrieve.kubeconfig": "Kubernetes క్లస్టర్ కాన్ఫిగరేషన్‌ని తిరిగి పొందడం సాధ్యం కాలేదు",
@ -3462,7 +3462,7 @@
"message.success.config.backup.schedule": "ఉదాహరణ బ్యాకప్ షెడ్యూల్ విజయవంతంగా కాన్ఫిగర్ చేయబడింది",
"message.success.config.health.monitor": "హెల్త్ మానిటర్‌ని విజయవంతంగా కాన్ఫిగర్ చేయండి",
"message.success.config.sticky.policy": "స్టిక్కీ పాలసీ విజయవంతంగా కాన్ఫిగర్ చేయబడింది",
"message.success.config.vm.schedule": "ఉదాహరణ షెడ్యూల్ విజయవంతంగా కాన్ఫిగర్ చేయబడింది",
"message.success.config.resource.schedule": "వనరు కోసం షెడ్యూల్ విజయవంతంగా కాన్ఫిగర్ చేయబడింది",
"message.success.copy.clipboard": "క్లిప్‌బోర్డ్‌కి విజయవంతంగా కాపీ చేయబడింది",
"message.success.create.account": "ఖాతా విజయవంతంగా సృష్టించబడింది",
"message.success.create.asnrange": "AS పరిధి విజయవంతంగా సృష్టించబడింది",

View File

@ -1037,21 +1037,11 @@
/>
<slot></slot>
</template>
<template v-if="column.key === 'vmScheduleActions'">
<tooltip-button
:tooltip="$t('label.edit')"
:disabled="!('updateVMSchedule' in $store.getters.apis)"
icon="edit-outlined"
@onClick="updateVMSchedule(record)"
/>
<tooltip-button
:tooltip="$t('label.remove')"
:disabled="!('deleteVMSchedule' in $store.getters.apis)"
icon="delete-outlined"
:danger="true"
type="primary"
@onClick="removeVMSchedule(record)"
/>
<template v-if="column.key === 'scheduleActions'">
<slot
name="scheduleActions"
:record="record"
></slot>
</template>
<template v-if="column.key === 'vgpuActions'">
<slot name="actionButtons" :record="record" :actions="actions"></slot>
@ -1397,12 +1387,6 @@ export default {
editTariffValue (record) {
this.$emit('edit-tariff-action', true, record)
},
updateVMSchedule (record) {
this.$emit('update-vm-schedule', record)
},
removeVMSchedule (record) {
this.$emit('remove-vm-schedule', record)
},
ipV6Address (text, record) {
if (!record || !record.nic || record.nic.length === 0) {
return ''

View File

@ -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',

View File

@ -87,9 +87,14 @@
:routerlinks="(record) => { return { name: '/securitygroups/' + record.id } }"
:showSearch="false"/>
</a-tab-pane>
<a-tab-pane :tab="$t('label.schedules')" key="schedules" v-if="'listVMSchedule' in $store.getters.apis">
<InstanceSchedules
:virtualmachine="vm"
<a-tab-pane
:tab="$t('label.schedules')"
key="schedules"
v-if="'listResourceSchedule' in $store.getters.apis"
>
<ResourceSchedules
:resource="vm"
resourceType="VirtualMachine"
:loading="loading"/>
</a-tab-pane>
<a-tab-pane :tab="$t('label.settings')" key="settings">
@ -145,7 +150,7 @@ import EventsTab from '@/components/view/EventsTab'
import DetailSettings from '@/components/view/DetailSettings'
import CreateVolume from '@/views/storage/CreateVolume'
import NicsTab from '@/views/network/NicsTab'
import InstanceSchedules from '@/views/compute/InstanceSchedules.vue'
import ResourceSchedules from '@/views/compute/ResourceSchedules.vue'
import ListResourceTable from '@/components/view/ListResourceTable'
import TooltipButton from '@/components/widgets/TooltipButton'
import ResourceIcon from '@/components/view/ResourceIcon'
@ -165,7 +170,7 @@ export default {
CreateVolume,
NicsTab,
GPUTab,
InstanceSchedules,
ResourceSchedules,
ListResourceTable,
SecurityGroupSelection,
TooltipButton,

View File

@ -22,7 +22,7 @@
style="width: 100%; margin-bottom: 10px"
@click="showAddModal"
:loading="loading"
:disabled="!('createVMSchedule' in $store.getters.apis)"
:disabled="!('createResourceSchedule' in $store.getters.apis)"
>
<template #icon><plus-outlined /></template> {{ $t('label.schedule.add') }}
</a-button>
@ -34,10 +34,25 @@
:selectedColumns="selectedColumnKeys"
ref="listview"
@update-selected-columns="updateSelectedColumns"
@update-vm-schedule="updateVMSchedule"
@remove-vm-schedule="removeVMSchedule"
@refresh="this.fetchData"
/>
>
<template #scheduleActions="{ record }">
<tooltip-button
:tooltip="$t('label.edit')"
:disabled="!('updateResourceSchedule' in $store.getters.apis)"
icon="edit-outlined"
@onClick="updateSchedule(record)"
/>
<tooltip-button
:tooltip="$t('label.remove')"
:disabled="!('deleteResourceSchedule' in $store.getters.apis)"
icon="delete-outlined"
:danger="true"
type="primary"
@onClick="removeSchedule(record)"
/>
</template>
</list-view>
<a-pagination
class="row-element"
style="margin-top: 10px"
@ -115,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"
@ -256,6 +306,7 @@ import { reactive, ref, toRaw } from 'vue'
import { getAPI, postAPI } from '@/api'
import ListView from '@/components/view/ListView'
import Status from '@/components/widgets/Status'
import TooltipButton from '@/components/widgets/TooltipButton'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import { mixinForm } from '@/utils/mixin'
import { timeZone } from '@/utils/timezone'
@ -269,39 +320,49 @@ dayjs.extend(utc)
dayjs.extend(timezone)
export default {
name: 'InstanceSchedules',
name: 'ResourceSchedules',
mixins: [mixinForm],
components: {
Status,
ListView,
TooltipButton,
TooltipLabel
},
props: {
virtualmachine: {
resource: {
type: Object,
required: true
},
resourceType: {
type: String,
required: true
},
loading: {
type: Boolean,
required: true
default: false
}
},
data () {
this.fetchTimeZone = debounce(this.fetchTimeZone, 800)
return {
tabLoading: false,
columnKeys: ['action', 'enabled', 'description', 'schedule', 'timezone', 'startdate', 'enddate', 'created', 'vmScheduleActions'],
columnKeys: ['action', 'enabled', 'description', 'schedule', 'timezone', 'startdate', 'enddate', 'created', 'scheduleActions'],
selectedColumnKeys: [],
columns: [],
schedules: [],
timeZoneMap: [],
actions: [
{ value: 'START', label: 'label.start' },
{ value: 'STOP', label: 'label.stop' },
{ value: 'REBOOT', label: 'label.reboot' },
{ value: 'FORCE_STOP', label: 'label.force.stop' },
{ value: 'FORCE_REBOOT', label: 'label.force.reboot' }
],
resourceActionsMap: {
VirtualMachine: [
{ value: 'START', label: 'label.start' },
{ value: 'STOP', label: 'label.stop' },
{ 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: [
{ id: 'year', value: ['month', 'day', 'dayOfWeek', 'hour', 'minute'] },
{ id: 'month', value: ['day', 'dayOfWeek', 'hour', 'minute'] },
@ -319,7 +380,7 @@ export default {
}
},
beforeCreate () {
this.apiParams = this.$getApiParams('createVMSchedule')
this.apiParams = this.$getApiParams('createResourceSchedule')
},
computed: {
pageSizeOptions () {
@ -330,6 +391,9 @@ export default {
return [...new Set(sizes)].sort(function (a, b) {
return a - b
}).map(String)
},
actions () {
return this.resourceActionsMap[this.resourceType] || []
}
},
created () {
@ -341,7 +405,8 @@ export default {
this.fetchTimeZone()
},
watch: {
virtualmachine: {
resource: {
deep: true,
handler () {
this.fetchSchedules()
}
@ -351,10 +416,12 @@ export default {
initForm () {
this.formRef = ref()
this.form = reactive({
action: 'START',
action: this.actions.length > 0 ? this.actions[0].value : null,
schedule: '* * * * *',
description: '',
timezone: 'UTC',
minMembers: null,
maxMembers: null,
startDate: '',
endDate: '',
enabled: true,
@ -363,19 +430,22 @@ 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')}` }]
})
},
createVMSchedule (schedule) {
createSchedule (schedule) {
this.resetForm()
this.showAddModal()
},
removeVMSchedule (schedule) {
postAPI('deleteVMSchedule', {
removeSchedule (schedule) {
postAPI('deleteResourceSchedule', {
id: schedule.id,
virtualmachineid: this.virtualmachine.id
resourceid: this.resource.id,
resourcetype: this.resourceType
}).then(() => {
if (this.totalCount - 1 === this.pageSize * (this.page - 1)) {
this.page = this.page - 1 > 0 ? this.page - 1 : 1
@ -384,19 +454,21 @@ export default {
this.$message.success(message)
}).catch(error => {
console.error(error)
this.$message.error(this.$t('message.error.remove.vm.schedule'))
this.$message.error(this.$t('message.error.remove.resource.schedule'))
this.$notification.error({
message: this.$t('label.error'),
description: this.$t('message.error.remove.vm.schedule')
description: this.$t('message.error.remove.resource.schedule')
})
}).finally(() => {
this.fetchData()
})
},
updateVMSchedule (schedule) {
updateSchedule (schedule) {
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
@ -416,23 +488,28 @@ export default {
schedule: values.schedule,
timezone: values.timezone,
action: values.action,
virtualmachineid: this.virtualmachine.id,
resourceid: this.resource.id,
resourcetype: this.resourceType,
enabled: values.enabled,
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 = 'createVMSchedule'
command = 'createResourceSchedule'
} else {
params.id = this.form.id
command = 'updateVMSchedule'
command = 'updateResourceSchedule'
}
postAPI(command, params).then(response => {
this.$notification.success({
message: this.$t('label.schedule'),
description: this.$t('message.success.config.vm.schedule')
description: this.$t('message.success.config.resource.schedule')
})
this.isSubmitted = false
this.fetchData()
@ -475,20 +552,25 @@ export default {
},
fetchSchedules () {
this.schedules = []
if (!this.virtualmachine.id) {
if (!this.resource.id) {
return
}
const params = {
page: this.page,
pagesize: this.pageSize,
virtualmachineid: this.virtualmachine.id,
resourceid: this.resource.id,
resourcetype: this.resourceType,
listall: true
}
this.tabLoading = true
getAPI('listVMSchedule', params).then(json => {
getAPI('listResourceSchedule', params).then(json => {
this.schedules = []
this.totalCount = json?.listvmscheduleresponse?.count || 0
this.schedules = json?.listvmscheduleresponse?.vmschedule || []
this.totalCount = json?.listresourcescheduleresponse?.count || 0
this.schedules = json?.listresourcescheduleresponse?.resourceschedule || []
}).catch(error => {
console.error(error)
this.$notifyError(error)
}).finally(() => {
this.tabLoading = false
})
},