diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 42395bf89992..cc230268c42b 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -43,7 +43,7 @@ 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"; @@ -888,15 +889,17 @@ 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); + + // 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); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/CreateResourceScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/CreateResourceScheduleCmd.java new file mode 100644 index 000000000000..3e24a2d56467 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/CreateResourceScheduleCmd.java @@ -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 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(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/DeleteResourceScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/DeleteResourceScheduleCmd.java new file mode 100644 index 000000000000..fe9a695df637 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/DeleteResourceScheduleCmd.java @@ -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 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 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(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/ListResourceScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/ListResourceScheduleCmd.java new file mode 100644 index 000000000000..1c8869eff9c3 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/ListResourceScheduleCmd.java @@ -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 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 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 response = resourceScheduleManager.listSchedule( + getId(), getIds(), getResourceType(), getResourceId(), getAction(), getEnabled(), + getStartIndex(), getPageSizeVal() + ); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/UpdateResourceScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/UpdateResourceScheduleCmd.java new file mode 100644 index 000000000000..5422588853e9 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/UpdateResourceScheduleCmd.java @@ -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 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(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java index 7e9bdd942ed7..7698d4beb074 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java @@ -22,23 +22,26 @@ 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 Boolean getEnabled() { 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); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java index f34d07b045d9..f40db4ee0487 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java @@ -22,6 +22,7 @@ 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.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 ids; - ///////////////////////////////////////////////////// - /////////////////// Accessors /////////////////////// - ///////////////////////////////////////////////////// + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// public Long getId() { return id; @@ -83,18 +86,21 @@ public Long getVmId() { 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"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java index be94315abe76..c7a9a75f2eac 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java @@ -20,23 +20,27 @@ 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 Boolean getEnabled() { return enabled; } - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// @Override public void execute() { - ListResponse response = vmScheduleManager.listSchedule(this); + String resourceIdStr = getVmId() != null ? String.valueOf(getVmId()) : null; + + ListResponse scheduleResponse = resourceScheduleManager.listSchedule( + getId(), null, ApiCommandResourceType.VirtualMachine, resourceIdStr, getAction(), getEnabled(), + getStartIndex(), getPageSizeVal() + ); + + List vmScheduleResponses = new ArrayList<>(); + for (ResourceScheduleResponse resourceScheduleResponse : scheduleResponse.getResponses()) { + vmScheduleResponses.add(new VMScheduleResponse(resourceScheduleResponse)); + } + ListResponse response = new ListResponse<>(); + response.setResponses(vmScheduleResponses, scheduleResponse.getCount()); response.setResponseName(getCommandName()); - response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase()); + response.setObjectName("vmschedule"); setResponseObject(response); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java index b7222944fe07..610c01e991b3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java @@ -22,22 +22,25 @@ 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 Boolean getEnabled() { 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(); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ResourceScheduleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ResourceScheduleResponse.java new file mode 100644 index 000000000000..ff799b467cff --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ResourceScheduleResponse.java @@ -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 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 details) { + this.details = details; + } + + public Map getDetails() { + return details; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getCreated() { + return created; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VMScheduleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VMScheduleResponse.java index 6800c25b0234..34455b3a0bf4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VMScheduleResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VMScheduleResponse.java @@ -23,11 +23,11 @@ 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 void setTimeZone(String timeZone) { this.timeZone = timeZone; } - public void setAction(VMSchedule.Action action) { + public void setAction(ResourceSchedule.Action action) { this.action = action; } @@ -105,5 +119,7 @@ public void setEndDate(Date endDate) { this.endDate = endDate; } - public void setCreated(Date created) {this.created = created;} + public void setCreated(Date created) { + this.created = created; + } } diff --git a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedule.java b/api/src/main/java/org/apache/cloudstack/schedule/ResourceSchedule.java similarity index 66% rename from api/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedule.java rename to api/src/main/java/org/apache/cloudstack/schedule/ResourceSchedule.java index b1e7df3c39cf..4bd39211a27f 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedule.java +++ b/api/src/main/java/org/apache/cloudstack/schedule/ResourceSchedule.java @@ -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 @@ enum Action { String getTimeZone(); - Action getAction(); + String getActionName(); boolean getEnabled(); diff --git a/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleManager.java b/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleManager.java new file mode 100644 index 000000000000..fb650a6cf68d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleManager.java @@ -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 details); + + ResourceScheduleResponse updateSchedule(Long id, String description, String schedule, String timeZone, + Date startDate, Date endDate, Boolean enabled, Map details); + + ListResponse listSchedule(Long id, List ids, ApiCommandResourceType resourceType, + String resourceUuid, String action, Boolean enabled, + Long startIndex, Long pageSize); + + Long removeSchedule(ApiCommandResourceType resourceType, String resourceUuid, Long id, List ids); + + void removeSchedulesForResource(ApiCommandResourceType resourceType, long resourceId); +} diff --git a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJob.java b/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJob.java similarity index 77% rename from api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJob.java rename to api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJob.java index d7a18b768279..a7bf4d9aa05e 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJob.java +++ b/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJob.java @@ -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(); } diff --git a/api/src/main/java/org/apache/cloudstack/schedule/vm/VMScheduleAction.java b/api/src/main/java/org/apache/cloudstack/schedule/vm/VMScheduleAction.java new file mode 100644 index 000000000000..1648305803ff --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/schedule/vm/VMScheduleAction.java @@ -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; } + }; +} diff --git a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManager.java b/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManager.java deleted file mode 100644 index 6aca05b58d5e..000000000000 --- a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManager.java +++ /dev/null @@ -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 listSchedule(ListVMScheduleCmd listVMScheduleCmd); - - VMScheduleResponse updateSchedule(UpdateVMScheduleCmd updateVMScheduleCmd); - - long removeScheduleByVmId(long vmId, boolean expunge); - - Long removeSchedule(DeleteVMScheduleCmd deleteVMScheduleCmd); -} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java index 99bc9d2b3fb7..ae848b2712b1 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java @@ -21,8 +21,9 @@ 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 @@ 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 void tearDown() throws Exception { */ @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 void testSuccessfulExecution() { */ @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(); } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java index 1f764a84365f..cec7503bd52b 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java @@ -23,8 +23,7 @@ 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 @@ public class DeleteVMScheduleCmdTest { @Mock - public VMScheduleManager vmScheduleManager; + public ResourceScheduleManager resourceScheduleManager; + @Mock public EntityManager entityManager; @@ -64,9 +64,11 @@ public void tearDown() throws Exception { 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 void testSuccessfulExecution() { */ @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 void testServerApiException() { */ @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(); } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java index f5434de3581c..5449be17a576 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java @@ -20,8 +20,9 @@ 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 @@ public class ListVMScheduleCmdTest { @Mock - public VMScheduleManager vmScheduleManager; + public ResourceScheduleManager resourceScheduleManager; + @InjectMocks private ListVMScheduleCmd listVMScheduleCmd = new ListVMScheduleCmd(); private AutoCloseable closeable; @@ -58,12 +60,14 @@ public void tearDown() throws Exception { */ @Test public void testEmptyResponse() { - ListResponse response = new ListResponse(); - response.setResponses(new ArrayList()); - Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenReturn(response); + ListResponse response = new ListResponse(); + response.setResponses(new ArrayList()); + 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 actualResponseObject = (ListResponse) listVMScheduleCmd.getResponseObject(); - Assert.assertEquals(response, actualResponseObject); Assert.assertEquals(0L, actualResponseObject.getResponses().size()); } @@ -74,15 +78,22 @@ public void testEmptyResponse() { */ @Test public void testNonEmptyResponse() { - ListResponse listResponse = new ListResponse(); - VMScheduleResponse response = Mockito.mock(VMScheduleResponse.class); + ListResponse listResponse = new ListResponse(); + 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 actualResponseObject = (ListResponse) 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 void testNonEmptyResponse() { */ @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 actualResponseObject = (ListResponse) listVMScheduleCmd.getResponseObject(); } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java index 2c6c485f25bf..a24252f4a1d0 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java @@ -21,9 +21,11 @@ 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 @@ 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 void tearDown() throws Exception { */ @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 void testSuccessfulExecution() { */ @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 void testInvalidParameterValueException() { */ @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 void testSuccessfulGetEntityOwnerId() { */ @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(); } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleDetailVO.java new file mode 100644 index 000000000000..16985e6bac30 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleDetailVO.java @@ -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; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleVO.java similarity index 72% rename from engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java rename to engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleVO.java index e0065db1e77a..e323b8cc449b 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleVO.java @@ -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.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 VMScheduleVO(long vmId, String description, String schedule, String timeZ @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 long getId() { return id; } - public long getVmId() { - return vmId; + @Override + public ApiCommandResourceType getResourceType() { + return resourceType; + } + + public void setResourceType(ApiCommandResourceType resourceType) { + this.resourceType = resourceType; + } + + @Override + public long getResourceId() { + return resourceId; } - public void setVmId(long vmId) { - this.vmId = vmId; + public void setResourceId(long resourceId) { + this.resourceId = resourceId; } + @Override public String getDescription() { return description; } @@ -127,6 +145,7 @@ public void setDescription(String description) { this.description = description; } + @Override public String getSchedule() { return schedule.substring(2); } @@ -144,14 +163,16 @@ public void setTimeZone(String timeZone) { 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 ZoneId getTimeZoneId() { return TimeZone.getTimeZone(getTimeZone()).toZoneId(); } + @Override public Date getCreated() { return created; } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJobVO.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJobVO.java similarity index 64% rename from engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJobVO.java rename to engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJobVO.java index 775e9cfe40cf..ca03056b67a8 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJobVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJobVO.java @@ -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.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 = "resource_id", nullable = false) + long resourceId; - @Column(name = "vm_schedule_id", nullable = false) - long vmScheduleId; + @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 long getId() { } @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 void setAsyncJobId(long asyncJobId) { } @Override - public VMSchedule.Action getAction() { - return action; + public String getActionName() { + return actionName; + } + + public void setActionName(String action) { + this.actionName = action; } @Override diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduleDao.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDao.java similarity index 51% rename from engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduleDao.java rename to engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDao.java index b8c808b5bfc9..ee56991fb9ac 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduleDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDao.java @@ -16,22 +16,25 @@ * 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.ResourceScheduleVO; import java.util.List; -public interface VMScheduleDao extends GenericDao { - List listAllActiveSchedules(); +public interface ResourceScheduleDao extends GenericDao { + List listAllActiveSchedules(ApiCommandResourceType resourceType); - long removeSchedulesForVmIdAndIds(Long vmId, List ids); + long removeSchedulesForResourceAndIds(ApiCommandResourceType resourceType, long resourceId, List ids); - Pair, Integer> searchAndCount(Long id, Long vmId, VMSchedule.Action action, Boolean enabled, Long offset, Long limit); + long removeAllSchedulesForResource(ApiCommandResourceType resourceType, long resourceId); - SearchCriteria getSearchCriteriaForVMId(Long vmId); + Pair, Integer> searchAndCount(List ids, ApiCommandResourceType resourceType, Long resourceId, + String action, Boolean enabled, Long offset, Long limit); + + SearchCriteria getSearchCriteriaForResource(ApiCommandResourceType resourceType, long resourceId); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDaoImpl.java new file mode 100644 index 000000000000..ace249cab62f --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDaoImpl.java @@ -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 implements ResourceScheduleDao { + + private final SearchBuilder activeScheduleSearch; + private final SearchBuilder 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 listAllActiveSchedules(ApiCommandResourceType resourceType) { + SearchCriteria 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 ids) { + SearchCriteria 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 sc = allSearch.create(); + sc.setParameters(RESOURCE_TYPE, resourceType); + sc.setParameters(RESOURCE_ID, resourceId); + return remove(sc); + } + + @Override + public Pair, Integer> searchAndCount(List ids, ApiCommandResourceType resourceType, Long resourceId, + String action, Boolean enabled, Long offset, Long limit) { + SearchCriteria 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 getSearchCriteriaForResource(ApiCommandResourceType resourceType, long resourceId) { + SearchCriteria sc = allSearch.create(); + sc.setParameters(RESOURCE_TYPE, resourceType); + sc.setParameters(RESOURCE_ID, resourceId); + return sc; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDetailsDao.java new file mode 100644 index 000000000000..f36ce8d0879c --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDetailsDao.java @@ -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, ResourceDetailsDao { +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDetailsDaoImpl.java new file mode 100644 index 000000000000..3757e41d0cfe --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDetailsDaoImpl.java @@ -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 implements ResourceScheduleDetailsDao { + + @Override + public void addDetail(long resourceId, String key, String value, boolean display) { + super.addDetail(new ResourceScheduleDetailVO(resourceId, key, value, display)); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDao.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduledJobDao.java similarity index 59% rename from engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDao.java rename to engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduledJobDao.java index 835ac696f26c..16210802235f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduledJobDao.java @@ -16,21 +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.db.GenericDao; -import org.apache.cloudstack.vm.schedule.VMScheduledJobVO; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.schedule.ResourceScheduledJobVO; import java.util.Date; import java.util.List; -public interface VMScheduledJobDao extends GenericDao { +public interface ResourceScheduledJobDao extends GenericDao { + List listJobsToStart(ApiCommandResourceType resourceType, Date currentTimestamp); - List listJobsToStart(Date currentTimestamp); + int expungeJobsForSchedules(List scheduleIds, Date dateAfter); - int expungeJobsForSchedules(List scheduleId, Date dateAfter); + int expungeJobsBefore(ApiCommandResourceType resourceType, Date currentTimestamp); - int expungeJobsBefore(Date currentTimestamp); - - VMScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp); + ResourceScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduledJobDaoImpl.java similarity index 55% rename from engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDaoImpl.java rename to engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduledJobDaoImpl.java index 2f08a41b92e4..02c1acb6c719 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduledJobDaoImpl.java @@ -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.List; @Component -public class VMScheduledJobDaoImpl extends GenericDaoBase implements VMScheduledJobDao { +public class ResourceScheduledJobDaoImpl extends GenericDaoBase implements ResourceScheduledJobDao { - private final SearchBuilder jobsToStartSearch; + private final SearchBuilder jobsToStartSearch; + private final SearchBuilder expungeJobsBeforeSearch; + private final SearchBuilder expungeJobForScheduleSearch; + private final SearchBuilder scheduleAndTimestampSearch; - private final SearchBuilder expungeJobsBeforeSearch; + static final String SCHEDULED_TIMESTAMP = "scheduledTimestamp"; + static final String SCHEDULE_ID = "scheduleId"; + static final String RESOURCE_TYPE = "resourceType"; - private final SearchBuilder expungeJobForScheduleSearch; - - private final SearchBuilder 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 listJobsToStart(Date currentTimestamp) { + public List listJobsToStart(ApiCommandResourceType resourceType, Date currentTimestamp) { if (currentTimestamp == null) { currentTimestamp = new Date(); } Date truncatedTs = DateUtils.round(currentTimestamp, Calendar.MINUTE); - - SearchCriteria sc = jobsToStartSearch.create(); + SearchCriteria 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 vmScheduleIds, Date dateAfter) { - SearchCriteria sc = expungeJobForScheduleSearch.create(); - sc.setParameters(VM_SCHEDULE_ID, vmScheduleIds.toArray()); + public int expungeJobsForSchedules(List scheduleIds, Date dateAfter) { + SearchCriteria sc = expungeJobForScheduleSearch.create(); + sc.setParameters(SCHEDULE_ID, scheduleIds.toArray()); if (dateAfter != null) { sc.setParameters(SCHEDULED_TIMESTAMP, dateAfter); } @@ -94,16 +92,17 @@ public int expungeJobsForSchedules(List vmScheduleIds, Date dateAfter) { } @Override - public int expungeJobsBefore(Date date) { - SearchCriteria sc = expungeJobsBeforeSearch.create(); + public int expungeJobsBefore(ApiCommandResourceType resourceType, Date date) { + SearchCriteria 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 sc = scheduleAndTimestampSearch.create(); - sc.setParameters(VM_SCHEDULE_ID, scheduleId); + public ResourceScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp) { + SearchCriteria sc = scheduleAndTimestampSearch.create(); + sc.setParameters(SCHEDULE_ID, scheduleId); sc.setParameters(SCHEDULED_TIMESTAMP, scheduledTimestamp); return findOneBy(sc); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduleDaoImpl.java deleted file mode 100644 index db8c0c3068f8..000000000000 --- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduleDaoImpl.java +++ /dev/null @@ -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 implements VMScheduleDao { - - private final SearchBuilder activeScheduleSearch; - - private final SearchBuilder scheduleSearchByVmIdAndIds; - - private final SearchBuilder 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 listAllActiveSchedules() { - // WHERE enabled = true AND (end_date IS NULL OR end_date > current_date) - SearchCriteria 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 ids) { - SearchCriteria sc = scheduleSearchByVmIdAndIds.create(); - sc.setParameters(ApiConstants.ID, ids.toArray()); - sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId); - return remove(sc); - } - - @Override - public Pair, Integer> searchAndCount(Long id, Long vmId, VMSchedule.Action action, Boolean enabled, Long offset, Long limit) { - SearchCriteria 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 getSearchCriteriaForVMId(Long vmId) { - SearchCriteria sc = scheduleSearch.create(); - sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId); - return sc; - } -} diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index ad3722577c27..5930092c2610 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -282,8 +282,9 @@ - - + + + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql index c99f798d3d56..070667092f76 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -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'; diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 60482c431ebb..886d97a00cbc 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -108,6 +108,7 @@ 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 boolean expunge(UserVmVO vm) { autoScaleManager.removeVmFromVmGroup(vm.getId()); + resourceScheduleManager.removeSchedulesForResource(ApiCommandResourceType.VirtualMachine, vm.getId()); + releaseNetworkResourcesOnExpunge(vm.getId()); List rootVol = _volsDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT); diff --git a/server/src/main/java/org/apache/cloudstack/schedule/BaseScheduleWorker.java b/server/src/main/java/org/apache/cloudstack/schedule/BaseScheduleWorker.java new file mode 100644 index 000000000000..6fd33fcd9495 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/schedule/BaseScheduleWorker.java @@ -0,0 +1,393 @@ +/* + * 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 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 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 jobs = resourceScheduledJobDao.listJobsToStart(getApiResourceType(), currentTimestamp); + logger.debug("Got {} scheduled jobs for {} at {}", jobs.size(), getResourceTypeName(), displayTime); + + Map toExecute = new HashMap<>(); + Map> 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 jobsToExecute) { + for (Map.Entry 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 executed, + Map> skipped) { + for (Map.Entry> 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 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 long submitAsyncJob( + Class cmdClass, long accountId, long resourceId, long eventId, + Map extra) { + Map 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); + } +} diff --git a/server/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleManagerImpl.java new file mode 100644 index 000000000000..0e4d58ba421d --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleManagerImpl.java @@ -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 workerList; + + private Map workerMap; + + @Override + public boolean configure(String name, Map 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> getCommands() { + final List> 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 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) status -> { + ResourceScheduleVO scheduleVO = resourceScheduleDao.persist(new ResourceScheduleVO( + resourceType, internalResourceId, + finalDescription, cronExpression.toString(), timeZoneId, + finalAction, finalStartDate, finalEndDate, enabled)); + + if (details != null && !details.isEmpty()) { + List detailVOs = new ArrayList<>(); + for (Map.Entry 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 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 listSchedule(Long id, List 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 scheduleIds = getIdsListFromCmd(id, ids); + if (action != null) { + action = worker.parseAction(action).name(); + } + + Pair, Integer> result = resourceScheduleDao.searchAndCount( + scheduleIds, resourceType, internalResourceId, action, enabled, startIndex, pageSize); + + ListResponse response = new ListResponse<>(); + List 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 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) status -> { + resourceScheduleDao.update(id, scheduleVO); + + if (details != null) { + if (details.isEmpty()) { + resourceScheduleDetailsDao.removeDetails(id); + } else { + List detailVOs = new ArrayList<>(); + for (Map.Entry 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 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 schedules = resourceScheduleDao.search( + resourceScheduleDao.getSearchCriteriaForResource(resourceType, resourceId), null); + List 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 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 ids = getIdsListFromCmd(id, idsList); + Pair, Integer> result = resourceScheduleDao.searchAndCount(ids, resourceType, internalResourceId, null, null, null, null); + List schedulesToRemove = result.first(); + List scheduleIdsToRemove = schedulesToRemove.stream().map(ResourceScheduleVO::getId).collect(Collectors.toList()); + return Transaction.execute((TransactionCallback) status -> { + worker.removeScheduledJobs(scheduleIdsToRemove); + + CallContext.current().setEventResourceId(internalResourceId); + CallContext.current().setEventResourceType(worker.getApiResourceType()); + return resourceScheduleDao.removeSchedulesForResourceAndIds(resourceType, internalResourceId, scheduleIdsToRemove); + }); + } +} diff --git a/server/src/main/java/org/apache/cloudstack/schedule/vm/VMScheduleWorker.java b/server/src/main/java/org/apache/cloudstack/schedule/vm/VMScheduleWorker.java new file mode 100644 index 000000000000..398a80844bb1 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/schedule/vm/VMScheduleWorker.java @@ -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 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))); + } +} diff --git a/server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManagerImpl.java deleted file mode 100644 index 866fca3f96e1..000000000000 --- a/server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManagerImpl.java +++ /dev/null @@ -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> getCommands() { - final List> 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) 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 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, Integer> result = vmScheduleDao.searchAndCount(id, vmId, action, enabled, cmd.getStartIndex(), cmd.getPageSizeVal()); - - ListResponse response = new ListResponse<>(); - List 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) 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 sc = vmScheduleDao.getSearchCriteriaForVMId(vmId); - List vmSchedules = vmScheduleDao.search(sc, null); - List 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 ids = getIdsListFromCmd(cmd.getId(), cmd.getIds()); - - if (ids.isEmpty()) { - throw new InvalidParameterValueException("Either id or ids parameter must be specified"); - } - return Transaction.execute((TransactionCallback) status -> { - vmScheduler.removeScheduledJobs(ids); - CallContext.current().setEventResourceId(vm.getId()); - CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine); - return vmScheduleDao.removeSchedulesForVmIdAndIds(vm.getId(), ids); - }); - } -} diff --git a/server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduler.java b/server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduler.java deleted file mode 100644 index fa96a1c97b93..000000000000 --- a/server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduler.java +++ /dev/null @@ -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 VMScheduledJobExpireInterval = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class, "vmscheduler.jobs.expire.interval", "30", "VM Scheduler expire interval in days", true); - - void removeScheduledJobs(List vmScheduleIds); - - void updateScheduledJob(VMScheduleVO vmSchedule); - - Date scheduleNextJob(VMScheduleVO vmSchedule, Date timestamp); -} diff --git a/server/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedulerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedulerImpl.java deleted file mode 100644 index 56d794fa5c2c..000000000000 --- a/server/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedulerImpl.java +++ /dev/null @@ -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 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 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 jobsToExecute) { - String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp); - - for (Map.Entry 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 jobsToExecute, Map> jobsNotToExecute) { - for (Map.Entry> entry : jobsNotToExecute.entrySet()) { - Long vmId = entry.getKey(); - List 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 vmScheduledJobs = vmScheduledJobDao.listJobsToStart(currentTimestamp); - logger.debug(String.format("Got %d scheduled jobs to be executed at %s", vmScheduledJobs.size(), displayTime)); - - Map jobsToExecute = new HashMap<>(); - Map> 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 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 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 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); - } -} diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index 37d32c0f3905..a9459b680eb7 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -372,8 +372,8 @@ - - + + diff --git a/server/src/test/java/org/apache/cloudstack/schedule/ResourceScheduleManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/schedule/ResourceScheduleManagerImplTest.java new file mode 100644 index 000000000000..e87459c2482e --- /dev/null +++ b/server/src/test/java/org/apache/cloudstack/schedule/ResourceScheduleManagerImplTest.java @@ -0,0 +1,337 @@ +/* + * 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.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.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 + 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); + Map workerMap = new HashMap<>(); + workerMap.put(ApiCommandResourceType.VirtualMachine, vmScheduleWorker); + 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 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 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 removeSchedulesForResource() { + ResourceScheduleVO schedule1 = Mockito.mock(ResourceScheduleVO.class); + ResourceScheduleVO schedule2 = Mockito.mock(ResourceScheduleVO.class); + List scheduleList = new ArrayList<>(); + scheduleList.add(schedule1); + scheduleList.add(schedule2); + SearchCriteria 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 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()); + } +} diff --git a/server/src/test/java/org/apache/cloudstack/schedule/vm/VMScheduleWorkerTest.java b/server/src/test/java/org/apache/cloudstack/schedule/vm/VMScheduleWorkerTest.java new file mode 100644 index 000000000000..e2b435d76c75 --- /dev/null +++ b/server/src/test/java/org/apache/cloudstack/schedule/vm/VMScheduleWorkerTest.java @@ -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 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 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 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 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 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"); + } +} diff --git a/server/src/test/java/org/apache/cloudstack/vm/schedule/VMScheduleManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/schedule/VMScheduleManagerImplTest.java deleted file mode 100644 index 84b1c577f6a2..000000000000 --- a/server/src/test/java/org/apache/cloudstack/vm/schedule/VMScheduleManagerImplTest.java +++ /dev/null @@ -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 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 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 vmScheduleList = new ArrayList<>(); - vmScheduleList.add(vmSchedule1); - vmScheduleList.add(vmSchedule2); - SearchCriteria 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")); - } - } -} diff --git a/server/src/test/java/org/apache/cloudstack/vm/schedule/VMSchedulerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/schedule/VMSchedulerImplTest.java deleted file mode 100644 index 3f9ffc714a1e..000000000000 --- a/server/src/test/java/org/apache/cloudstack/vm/schedule/VMSchedulerImplTest.java +++ /dev/null @@ -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 actionEventMap; - - @Mock - private AsyncJobManager asyncJobManager; - - @Mock - private AsyncJobDispatcher asyncJobDispatcher; - - private AutoCloseable closeable; - - private MockedStatic 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 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 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 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 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); - } - } -} diff --git a/test/integration/smoke/test_vm_schedule.py b/test/integration/smoke/test_vm_schedule.py index ee1354ff80ee..1e476c55fc87 100644 --- a/test/integration/smoke/test_vm_schedule.py +++ b/test/integration/smoke/test_vm_schedule.py @@ -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 time +RESOURCE_TYPE = "VirtualMachine" + class Services: """Test Snapshots Services""" @@ -148,8 +150,9 @@ def test_01_vmschedule_create(self): # 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 @@ def test_01_vmschedule_create(self): 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 @@ def test_01_vmschedule_create(self): ) 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 @@ def test_01_vmschedule_create(self): "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 @@ def test_01_vmschedule_create(self): 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 @@ def test_02_vmschedule_create_parameter_exceptions(self): # 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 @@ def test_02_vmschedule_create_parameter_exceptions(self): # 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 @@ def test_02_vmschedule_create_parameter_exceptions(self): # 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 @@ def test_02_vmschedule_create_parameter_exceptions(self): # 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 @@ def test_02_vmschedule_create_parameter_exceptions(self): # 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 @@ def test_03_vmschedule_update(self): # 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 @@ def test_03_vmschedule_update(self): 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 @@ def test_03_vmschedule_update(self): ) 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 @@ def test_04_vmschedule_update_parameter_exceptions(self): # 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 @@ def test_05_vmschedule_test_e2e(self): # 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 @@ def test_05_vmschedule_test_e2e(self): # 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 @@ def test_05_vmschedule_test_e2e(self): 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 @@ def test_05_vmschedule_test_e2e(self): # 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", diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index 292f52d809bf..95db9cdbc19b 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -238,6 +238,7 @@ 'removeQuarantinedIp': 'IP Quarantine', 'Shutdown': 'Maintenance', 'Maintenance': 'Maintenance', + 'ResourceSchedule': 'Resource Schedule', 'addObjectStoragePool': 'Object Store', 'listObjectStoragePools': 'Object Store', 'deleteObjectStoragePool': 'Object Store', diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 636c73209a3f..a78fe4eab4a4 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -7052,6 +7052,51 @@ def delete(self, apiclient): 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""" diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index f0a6ad3c79b9..bc029e732a20 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -2779,7 +2779,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", @@ -3535,7 +3536,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", @@ -3930,7 +3931,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", diff --git a/ui/public/locales/te.json b/ui/public/locales/te.json index ed72626e0a67..c3a04487ae9a 100644 --- a/ui/public/locales/te.json +++ b/ui/public/locales/te.json @@ -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 పరిధి విజయవంతంగా సృష్టించబడింది", diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index b03293efacae..733076122c1d 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -1037,21 +1037,11 @@ /> -