mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
feature: VM Scheduler (#7397)
This PR adds a feature to setup schedules to stop/start/restart your VMs. Documentation PR: apache/cloudstack-documentation#313 Related issue: #3387
This commit is contained in:
parent
409e3202a3
commit
fa3f2a75eb
@ -84,6 +84,7 @@ import com.cloud.user.User;
|
|||||||
import com.cloud.vm.Nic;
|
import com.cloud.vm.Nic;
|
||||||
import com.cloud.vm.NicSecondaryIp;
|
import com.cloud.vm.NicSecondaryIp;
|
||||||
import com.cloud.vm.VirtualMachine;
|
import com.cloud.vm.VirtualMachine;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||||
|
|
||||||
public class EventTypes {
|
public class EventTypes {
|
||||||
|
|
||||||
@ -111,6 +112,17 @@ public class EventTypes {
|
|||||||
public static final String EVENT_VM_UNMANAGE = "VM.UNMANAGE";
|
public static final String EVENT_VM_UNMANAGE = "VM.UNMANAGE";
|
||||||
public static final String EVENT_VM_RECOVER = "VM.RECOVER";
|
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";
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
// Domain Router
|
// Domain Router
|
||||||
public static final String EVENT_ROUTER_CREATE = "ROUTER.CREATE";
|
public static final String EVENT_ROUTER_CREATE = "ROUTER.CREATE";
|
||||||
public static final String EVENT_ROUTER_DESTROY = "ROUTER.DESTROY";
|
public static final String EVENT_ROUTER_DESTROY = "ROUTER.DESTROY";
|
||||||
@ -716,6 +728,16 @@ public class EventTypes {
|
|||||||
entityEventDetails.put(EVENT_VM_IMPORT, VirtualMachine.class);
|
entityEventDetails.put(EVENT_VM_IMPORT, VirtualMachine.class);
|
||||||
entityEventDetails.put(EVENT_VM_UNMANAGE, 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);
|
||||||
|
|
||||||
entityEventDetails.put(EVENT_ROUTER_CREATE, VirtualRouter.class);
|
entityEventDetails.put(EVENT_ROUTER_CREATE, VirtualRouter.class);
|
||||||
entityEventDetails.put(EVENT_ROUTER_DESTROY, VirtualRouter.class);
|
entityEventDetails.put(EVENT_ROUTER_DESTROY, VirtualRouter.class);
|
||||||
entityEventDetails.put(EVENT_ROUTER_START, VirtualRouter.class);
|
entityEventDetails.put(EVENT_ROUTER_START, VirtualRouter.class);
|
||||||
|
|||||||
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* 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.vm;
|
||||||
|
|
||||||
|
import com.cloud.exception.InvalidParameterValueException;
|
||||||
|
import com.cloud.vm.VirtualMachine;
|
||||||
|
import org.apache.cloudstack.acl.RoleType;
|
||||||
|
import org.apache.cloudstack.api.APICommand;
|
||||||
|
import org.apache.cloudstack.api.ApiConstants;
|
||||||
|
import org.apache.cloudstack.api.BaseCmd;
|
||||||
|
import org.apache.cloudstack.api.Parameter;
|
||||||
|
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||||
|
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@APICommand(name = "createVMSchedule", description = "Create VM 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;
|
||||||
|
|
||||||
|
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
|
||||||
|
type = CommandType.UUID,
|
||||||
|
entityType = UserVmResponse.class,
|
||||||
|
required = true,
|
||||||
|
description = "ID of the VM for which schedule is to be defined")
|
||||||
|
private Long vmId;
|
||||||
|
|
||||||
|
@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 VM 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 VM (start/stop/restart/force_stop/force_reboot).")
|
||||||
|
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 VM schedule. Defaults to true")
|
||||||
|
private Boolean enabled;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
/////////////////// Accessors ///////////////////////
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public Long getVmId() {
|
||||||
|
return vmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
/////////////// API Implementation///////////////////
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
VMScheduleResponse response = vmScheduleManager.createSchedule(this);
|
||||||
|
response.setResponseName(getCommandName());
|
||||||
|
setResponseObject(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getEntityOwnerId() {
|
||||||
|
VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, getVmId());
|
||||||
|
if (vm == null) {
|
||||||
|
throw new InvalidParameterValueException(String.format("Unable to find VM by id=%d", getVmId()));
|
||||||
|
}
|
||||||
|
return vm.getAccountId();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* 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.vm;
|
||||||
|
|
||||||
|
import com.cloud.exception.InvalidParameterValueException;
|
||||||
|
import com.cloud.vm.VirtualMachine;
|
||||||
|
import org.apache.cloudstack.acl.RoleType;
|
||||||
|
import org.apache.cloudstack.api.APICommand;
|
||||||
|
import org.apache.cloudstack.api.ApiConstants;
|
||||||
|
import org.apache.cloudstack.api.ApiErrorCode;
|
||||||
|
import org.apache.cloudstack.api.BaseCmd;
|
||||||
|
import org.apache.cloudstack.api.Parameter;
|
||||||
|
import org.apache.cloudstack.api.ServerApiException;
|
||||||
|
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||||
|
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||||
|
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@APICommand(name = "deleteVMSchedule", description = "Delete VM 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;
|
||||||
|
|
||||||
|
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
|
||||||
|
type = CommandType.UUID,
|
||||||
|
entityType = UserVmResponse.class,
|
||||||
|
required = true,
|
||||||
|
description = "ID of VM")
|
||||||
|
private Long vmId;
|
||||||
|
@Parameter(name = ApiConstants.ID,
|
||||||
|
type = CommandType.UUID,
|
||||||
|
entityType = VMScheduleResponse.class,
|
||||||
|
required = false,
|
||||||
|
description = "ID of VM schedule")
|
||||||
|
private Long id;
|
||||||
|
@Parameter(name = ApiConstants.IDS,
|
||||||
|
type = CommandType.LIST,
|
||||||
|
collectionType = CommandType.UUID,
|
||||||
|
entityType = VMScheduleResponse.class,
|
||||||
|
required = false,
|
||||||
|
description = "IDs of VM schedule")
|
||||||
|
private List<Long> ids;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
/////////////////// Accessors ///////////////////////
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Long> getIds() {
|
||||||
|
if (ids == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getVmId() {
|
||||||
|
return vmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
/////////////// API Implementation///////////////////
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
long rowsRemoved = vmScheduleManager.removeSchedule(this);
|
||||||
|
|
||||||
|
if (rowsRemoved > 0) {
|
||||||
|
final SuccessResponse response = new SuccessResponse();
|
||||||
|
response.setResponseName(getCommandName());
|
||||||
|
response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
|
||||||
|
setResponseObject(response);
|
||||||
|
} else {
|
||||||
|
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete VM Schedules");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getEntityOwnerId() {
|
||||||
|
VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, getVmId());
|
||||||
|
if (vm == null) {
|
||||||
|
throw new InvalidParameterValueException(String.format("Unable to find VM by id=%d", getVmId()));
|
||||||
|
}
|
||||||
|
return vm.getAccountId();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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.vm;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.acl.RoleType;
|
||||||
|
import org.apache.cloudstack.api.APICommand;
|
||||||
|
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.UserVmResponse;
|
||||||
|
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@APICommand(name = "listVMSchedule", description = "List VM 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;
|
||||||
|
|
||||||
|
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
|
||||||
|
type = CommandType.UUID,
|
||||||
|
entityType = UserVmResponse.class,
|
||||||
|
required = true,
|
||||||
|
description = "ID of the VM for which schedule is to be defined")
|
||||||
|
private Long vmId;
|
||||||
|
|
||||||
|
@Parameter(name = ApiConstants.ID,
|
||||||
|
type = CommandType.UUID,
|
||||||
|
entityType = VMScheduleResponse.class,
|
||||||
|
required = false,
|
||||||
|
description = "ID of VM schedule")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Parameter(name = ApiConstants.ACTION,
|
||||||
|
type = CommandType.STRING,
|
||||||
|
required = false,
|
||||||
|
description = "Action taken by schedule")
|
||||||
|
private String action;
|
||||||
|
|
||||||
|
@Parameter(name = ApiConstants.ENABLED,
|
||||||
|
type = CommandType.BOOLEAN,
|
||||||
|
required = false,
|
||||||
|
description = "ID of VM schedule")
|
||||||
|
private Boolean enabled;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
/////////////////// Accessors ///////////////////////
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public Long getVmId() {
|
||||||
|
return vmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
/////////////// API Implementation///////////////////
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
ListResponse<VMScheduleResponse> response = vmScheduleManager.listSchedule(this);
|
||||||
|
response.setResponseName(getCommandName());
|
||||||
|
response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
|
||||||
|
setResponseObject(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* 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.vm;
|
||||||
|
|
||||||
|
import com.cloud.exception.InvalidParameterValueException;
|
||||||
|
import com.cloud.vm.VirtualMachine;
|
||||||
|
import org.apache.cloudstack.acl.RoleType;
|
||||||
|
import org.apache.cloudstack.api.APICommand;
|
||||||
|
import org.apache.cloudstack.api.ApiConstants;
|
||||||
|
import org.apache.cloudstack.api.BaseCmd;
|
||||||
|
import org.apache.cloudstack.api.Parameter;
|
||||||
|
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@APICommand(name = "updateVMSchedule", description = "Update VM 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;
|
||||||
|
|
||||||
|
@Parameter(name = ApiConstants.ID,
|
||||||
|
type = CommandType.UUID,
|
||||||
|
entityType = VMScheduleResponse.class,
|
||||||
|
required = true,
|
||||||
|
description = "ID of VM schedule")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Parameter(name = ApiConstants.DESCRIPTION,
|
||||||
|
type = CommandType.STRING,
|
||||||
|
required = false,
|
||||||
|
description = "Name of the schedule")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Parameter(name = ApiConstants.SCHEDULE,
|
||||||
|
type = CommandType.STRING,
|
||||||
|
required = false,
|
||||||
|
description = "Schedule for action on VM 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 = false,
|
||||||
|
description = "Specifies a timezone for this command. For more information on the timezone parameter, see TimeZone Format.")
|
||||||
|
private String timeZone;
|
||||||
|
|
||||||
|
@Parameter(name = ApiConstants.START_DATE,
|
||||||
|
type = CommandType.DATE,
|
||||||
|
required = false,
|
||||||
|
description = "start date from which the schedule becomes active"
|
||||||
|
+ "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 VM schedule")
|
||||||
|
private Boolean enabled;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
/////////////////// Accessors ///////////////////////
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
/////////////// API Implementation///////////////////
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
VMScheduleResponse response = vmScheduleManager.updateSchedule(this);
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, vmSchedule.getVmId());
|
||||||
|
return vm.getAccountId();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.cloud.serializer.Param;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import org.apache.cloudstack.api.ApiConstants;
|
||||||
|
import org.apache.cloudstack.api.BaseResponse;
|
||||||
|
import org.apache.cloudstack.api.EntityReference;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@EntityReference(value = VMSchedule.class)
|
||||||
|
public class VMScheduleResponse extends BaseResponse {
|
||||||
|
@SerializedName(ApiConstants.ID)
|
||||||
|
@Param(description = "the ID of VM schedule")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@SerializedName(ApiConstants.VIRTUAL_MACHINE_ID)
|
||||||
|
@Param(description = "ID of virtual machine")
|
||||||
|
private String vmId;
|
||||||
|
|
||||||
|
@SerializedName(ApiConstants.DESCRIPTION)
|
||||||
|
@Param(description = "Description of VM schedule")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@SerializedName(ApiConstants.SCHEDULE)
|
||||||
|
@Param(description = "Cron formatted VM schedule")
|
||||||
|
private String schedule;
|
||||||
|
|
||||||
|
@SerializedName(ApiConstants.TIMEZONE)
|
||||||
|
@Param(description = "Timezone of the schedule")
|
||||||
|
private String timeZone;
|
||||||
|
|
||||||
|
@SerializedName(ApiConstants.ACTION)
|
||||||
|
@Param(description = "Action")
|
||||||
|
private VMSchedule.Action action;
|
||||||
|
|
||||||
|
@SerializedName(ApiConstants.ENABLED)
|
||||||
|
@Param(description = "VM 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.CREATED)
|
||||||
|
@Param(description = "Date when the schedule was created")
|
||||||
|
private Date created;
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVmId(String vmId) {
|
||||||
|
this.vmId = vmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSchedule(String schedule) {
|
||||||
|
this.schedule = schedule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeZone(String timeZone) {
|
||||||
|
this.timeZone = timeZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAction(VMSchedule.Action action) {
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartDate(Date startDate) {
|
||||||
|
this.startDate = startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEndDate(Date endDate) {
|
||||||
|
this.endDate = endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreated(Date created) {this.created = created;}
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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.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
|
||||||
|
}
|
||||||
|
|
||||||
|
long getVmId();
|
||||||
|
|
||||||
|
String getDescription();
|
||||||
|
|
||||||
|
String getSchedule();
|
||||||
|
|
||||||
|
String getTimeZone();
|
||||||
|
|
||||||
|
Action getAction();
|
||||||
|
|
||||||
|
boolean getEnabled();
|
||||||
|
|
||||||
|
Date getStartDate();
|
||||||
|
|
||||||
|
Date getEndDate();
|
||||||
|
|
||||||
|
ZoneId getTimeZoneId();
|
||||||
|
|
||||||
|
Date getCreated();
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cloudstack.vm.schedule;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.api.command.user.vm.CreateVMScheduleCmd;
|
||||||
|
import org.apache.cloudstack.api.command.user.vm.DeleteVMScheduleCmd;
|
||||||
|
import org.apache.cloudstack.api.command.user.vm.ListVMScheduleCmd;
|
||||||
|
import org.apache.cloudstack.api.command.user.vm.UpdateVMScheduleCmd;
|
||||||
|
import org.apache.cloudstack.api.response.ListResponse;
|
||||||
|
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||||
|
|
||||||
|
public interface VMScheduleManager {
|
||||||
|
VMScheduleResponse createSchedule(CreateVMScheduleCmd createVMScheduleCmd);
|
||||||
|
|
||||||
|
VMScheduleResponse createResponse(VMSchedule vmSchedule);
|
||||||
|
|
||||||
|
ListResponse<VMScheduleResponse> listSchedule(ListVMScheduleCmd listVMScheduleCmd);
|
||||||
|
|
||||||
|
VMScheduleResponse updateSchedule(UpdateVMScheduleCmd updateVMScheduleCmd);
|
||||||
|
|
||||||
|
long removeScheduleByVmId(long vmId, boolean expunge);
|
||||||
|
|
||||||
|
Long removeSchedule(DeleteVMScheduleCmd deleteVMScheduleCmd);
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.Identity;
|
||||||
|
import org.apache.cloudstack.api.InternalIdentity;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public interface VMScheduledJob extends Identity, InternalIdentity {
|
||||||
|
long getVmId();
|
||||||
|
|
||||||
|
long getVmScheduleId();
|
||||||
|
|
||||||
|
Long getAsyncJobId();
|
||||||
|
|
||||||
|
void setAsyncJobId(long asyncJobId);
|
||||||
|
|
||||||
|
VMSchedule.Action getAction();
|
||||||
|
|
||||||
|
Date getScheduledTime();
|
||||||
|
}
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* 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.vm;
|
||||||
|
|
||||||
|
import com.cloud.exception.InvalidParameterValueException;
|
||||||
|
import com.cloud.utils.db.EntityManager;
|
||||||
|
import com.cloud.vm.VirtualMachine;
|
||||||
|
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||||
|
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 java.security.InvalidParameterException;
|
||||||
|
|
||||||
|
public class CreateVMScheduleCmdTest {
|
||||||
|
@Mock
|
||||||
|
public VMScheduleManager vmScheduleManager;
|
||||||
|
@Mock
|
||||||
|
public EntityManager entityManager;
|
||||||
|
@InjectMocks
|
||||||
|
private CreateVMScheduleCmd createVMScheduleCmd = new CreateVMScheduleCmd();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have a VMScheduleManager and CreateVMScheduleCmd"
|
||||||
|
* when: "CreateVMScheduleCmd is executed successfully"
|
||||||
|
* then: "a VMSchedule response is created"
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSuccessfulExecution() {
|
||||||
|
VMScheduleResponse vmScheduleResponse = Mockito.mock(VMScheduleResponse.class);
|
||||||
|
Mockito.when(vmScheduleManager.createSchedule(createVMScheduleCmd)).thenReturn(vmScheduleResponse);
|
||||||
|
createVMScheduleCmd.execute();
|
||||||
|
Assert.assertEquals(vmScheduleResponse, createVMScheduleCmd.getResponseObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have a VMScheduleManager and CreateVMScheduleCmd"
|
||||||
|
* when: "CreateVMScheduleCmd is executed with an invalid parameter"
|
||||||
|
* then: "an InvalidParameterException is thrown"
|
||||||
|
*/
|
||||||
|
@Test(expected = InvalidParameterException.class)
|
||||||
|
public void testInvalidParameterException() {
|
||||||
|
Mockito.when(vmScheduleManager.createSchedule(createVMScheduleCmd)).thenThrow(InvalidParameterException.class);
|
||||||
|
createVMScheduleCmd.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have an EntityManager and CreateVMScheduleCmd"
|
||||||
|
* when: "CreateVMScheduleCmd.getEntityOwnerId is executed for a VM which does exist"
|
||||||
|
* then: "owner of that VM is returned"
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSuccessfulGetEntityOwnerId() {
|
||||||
|
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
|
||||||
|
Mockito.when(entityManager.findById(VirtualMachine.class, createVMScheduleCmd.getVmId())).thenReturn(vm);
|
||||||
|
long ownerId = createVMScheduleCmd.getEntityOwnerId();
|
||||||
|
Assert.assertEquals(vm.getAccountId(), ownerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have an EntityManager and CreateVMScheduleCmd"
|
||||||
|
* when: "CreateVMScheduleCmd.getEntityOwnerId is executed for a VM which doesn't exist"
|
||||||
|
* then: "an InvalidParameterException is thrown"
|
||||||
|
*/
|
||||||
|
@Test(expected = InvalidParameterValueException.class)
|
||||||
|
public void testFailureGetEntityOwnerId() {
|
||||||
|
Mockito.when(entityManager.findById(VirtualMachine.class, createVMScheduleCmd.getVmId())).thenReturn(null);
|
||||||
|
long ownerId = createVMScheduleCmd.getEntityOwnerId();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* 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.vm;
|
||||||
|
|
||||||
|
import com.cloud.exception.InvalidParameterValueException;
|
||||||
|
import com.cloud.utils.db.EntityManager;
|
||||||
|
import com.cloud.vm.VirtualMachine;
|
||||||
|
import org.apache.cloudstack.api.ServerApiException;
|
||||||
|
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||||
|
import org.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 java.security.InvalidParameterException;
|
||||||
|
|
||||||
|
public class DeleteVMScheduleCmdTest {
|
||||||
|
@Mock
|
||||||
|
public VMScheduleManager vmScheduleManager;
|
||||||
|
@Mock
|
||||||
|
public EntityManager entityManager;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private DeleteVMScheduleCmd deleteVMScheduleCmd = new DeleteVMScheduleCmd();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have a VMScheduleManager and DeleteVMScheduleCmd"
|
||||||
|
* when: "VMScheduleManager.removeSchedule() is executed returning 1 row"
|
||||||
|
* then: "a Success response is created"
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSuccessfulExecution() {
|
||||||
|
final SuccessResponse response = new SuccessResponse();
|
||||||
|
response.setResponseName(deleteVMScheduleCmd.getCommandName());
|
||||||
|
response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
|
||||||
|
|
||||||
|
Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenReturn(1L);
|
||||||
|
deleteVMScheduleCmd.execute();
|
||||||
|
SuccessResponse actualResponse = (SuccessResponse) deleteVMScheduleCmd.getResponseObject();
|
||||||
|
Assert.assertEquals(response.getResponseName(), actualResponse.getResponseName());
|
||||||
|
Assert.assertEquals(response.getObjectName(), actualResponse.getObjectName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have a VMScheduleManager and DeleteVMScheduleCmd"
|
||||||
|
* when: "VMScheduleManager.removeSchedule() is executed returning 0 row"
|
||||||
|
* then: "ServerApiException is thrown"
|
||||||
|
*/
|
||||||
|
@Test(expected = ServerApiException.class)
|
||||||
|
public void testServerApiException() {
|
||||||
|
Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenReturn(0L);
|
||||||
|
deleteVMScheduleCmd.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have a VMScheduleManager and DeleteVMScheduleCmd"
|
||||||
|
* when: "DeleteVMScheduleCmd is executed with an invalid parameter"
|
||||||
|
* then: "an InvalidParameterException is thrown"
|
||||||
|
*/
|
||||||
|
@Test(expected = InvalidParameterException.class)
|
||||||
|
public void testInvalidParameterException() {
|
||||||
|
Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenThrow(InvalidParameterException.class);
|
||||||
|
deleteVMScheduleCmd.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have an EntityManager and DeleteVMScheduleCmd"
|
||||||
|
* when: "DeleteVMScheduleCmd.getEntityOwnerId is executed for a VM which does exist"
|
||||||
|
* then: "owner of that VM is returned"
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSuccessfulGetEntityOwnerId() {
|
||||||
|
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
|
||||||
|
Mockito.when(entityManager.findById(VirtualMachine.class, deleteVMScheduleCmd.getVmId())).thenReturn(vm);
|
||||||
|
long ownerId = deleteVMScheduleCmd.getEntityOwnerId();
|
||||||
|
Assert.assertEquals(vm.getAccountId(), ownerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have an EntityManager and DeleteVMScheduleCmd"
|
||||||
|
* when: "DeleteVMScheduleCmd.getEntityOwnerId is executed for a VM which doesn't exist"
|
||||||
|
* then: "an InvalidParameterException is thrown"
|
||||||
|
*/
|
||||||
|
@Test(expected = InvalidParameterValueException.class)
|
||||||
|
public void testFailureGetEntityOwnerId() {
|
||||||
|
Mockito.when(entityManager.findById(VirtualMachine.class, deleteVMScheduleCmd.getVmId())).thenReturn(null);
|
||||||
|
long ownerId = deleteVMScheduleCmd.getEntityOwnerId();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* 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.vm;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.api.response.ListResponse;
|
||||||
|
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||||
|
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 java.security.InvalidParameterException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class ListVMScheduleCmdTest {
|
||||||
|
@Mock
|
||||||
|
public VMScheduleManager vmScheduleManager;
|
||||||
|
@InjectMocks
|
||||||
|
private ListVMScheduleCmd listVMScheduleCmd = new ListVMScheduleCmd();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have a VMScheduleManager with 0 schedules and ListVMScheduleCmd"
|
||||||
|
* when: "ListVMScheduleCmd is executed"
|
||||||
|
* then: "a list of size 0 is returned"
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testEmptyResponse() {
|
||||||
|
ListResponse<VMScheduleResponse> response = new ListResponse<VMScheduleResponse>();
|
||||||
|
response.setResponses(new ArrayList<VMScheduleResponse>());
|
||||||
|
Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenReturn(response);
|
||||||
|
listVMScheduleCmd.execute();
|
||||||
|
ListResponse<VMScheduleResponse> actualResponseObject = (ListResponse<VMScheduleResponse>) listVMScheduleCmd.getResponseObject();
|
||||||
|
Assert.assertEquals(response, actualResponseObject);
|
||||||
|
Assert.assertEquals(0L, actualResponseObject.getResponses().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have a VMScheduleManager with 1 schedule and ListVMScheduleCmd"
|
||||||
|
* when: "ListVMScheduleCmd is executed"
|
||||||
|
* then: "a list of size 1 is returned"
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testNonEmptyResponse() {
|
||||||
|
ListResponse<VMScheduleResponse> listResponse = new ListResponse<VMScheduleResponse>();
|
||||||
|
VMScheduleResponse response = Mockito.mock(VMScheduleResponse.class);
|
||||||
|
listResponse.setResponses(Collections.singletonList(response));
|
||||||
|
Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenReturn(listResponse);
|
||||||
|
listVMScheduleCmd.execute();
|
||||||
|
ListResponse<VMScheduleResponse> actualResponseObject = (ListResponse<VMScheduleResponse>) listVMScheduleCmd.getResponseObject();
|
||||||
|
Assert.assertEquals(listResponse, actualResponseObject);
|
||||||
|
Assert.assertEquals(1L, actualResponseObject.getResponses().size());
|
||||||
|
Assert.assertEquals(response, actualResponseObject.getResponses().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have a VMScheduleManager and ListVMScheduleCmd"
|
||||||
|
* when: "ListVMScheduleCmd is executed with an invalid parameter"
|
||||||
|
* then: "an InvalidParameterException is thrown"
|
||||||
|
*/
|
||||||
|
@Test(expected = InvalidParameterException.class)
|
||||||
|
public void testInvalidParameterException() {
|
||||||
|
Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenThrow(InvalidParameterException.class);
|
||||||
|
listVMScheduleCmd.execute();
|
||||||
|
ListResponse<VMScheduleResponse> actualResponseObject = (ListResponse<VMScheduleResponse>) listVMScheduleCmd.getResponseObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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.vm;
|
||||||
|
|
||||||
|
import com.cloud.exception.InvalidParameterValueException;
|
||||||
|
import com.cloud.utils.db.EntityManager;
|
||||||
|
import com.cloud.vm.VirtualMachine;
|
||||||
|
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||||
|
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 java.security.InvalidParameterException;
|
||||||
|
|
||||||
|
public class UpdateVMScheduleCmdTest {
|
||||||
|
@Mock
|
||||||
|
public VMScheduleManager vmScheduleManager;
|
||||||
|
@Mock
|
||||||
|
public EntityManager entityManager;
|
||||||
|
@InjectMocks
|
||||||
|
private UpdateVMScheduleCmd updateVMScheduleCmd = new UpdateVMScheduleCmd();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have a VMScheduleManager and UpdateVMScheduleCmd"
|
||||||
|
* when: "UpdateVMScheduleCmd is executed successfully"
|
||||||
|
* then: "a VMSchedule response is created"
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSuccessfulExecution() {
|
||||||
|
VMScheduleResponse vmScheduleResponse = Mockito.mock(VMScheduleResponse.class);
|
||||||
|
Mockito.when(vmScheduleManager.updateSchedule(updateVMScheduleCmd)).thenReturn(vmScheduleResponse);
|
||||||
|
updateVMScheduleCmd.execute();
|
||||||
|
Assert.assertEquals(vmScheduleResponse, updateVMScheduleCmd.getResponseObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have a VMScheduleManager and UpdateVMScheduleCmd"
|
||||||
|
* when: "UpdateVMScheduleCmd is executed with an invalid parameter"
|
||||||
|
* then: "an InvalidParameterException is thrown"
|
||||||
|
*/
|
||||||
|
@Test(expected = InvalidParameterException.class)
|
||||||
|
public void testInvalidParameterException() {
|
||||||
|
Mockito.when(vmScheduleManager.updateSchedule(updateVMScheduleCmd)).thenThrow(InvalidParameterException.class);
|
||||||
|
updateVMScheduleCmd.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have an EntityManager and UpdateVMScheduleCmd"
|
||||||
|
* when: "UpdateVMScheduleCmd.getEntityOwnerId is executed for a VM which does exist"
|
||||||
|
* then: "owner of that VM is returned"
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSuccessfulGetEntityOwnerId() {
|
||||||
|
VMSchedule vmSchedule = Mockito.mock(VMSchedule.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);
|
||||||
|
|
||||||
|
long ownerId = updateVMScheduleCmd.getEntityOwnerId();
|
||||||
|
Assert.assertEquals(vm.getAccountId(), ownerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given: "We have an EntityManager and UpdateVMScheduleCmd"
|
||||||
|
* when: "UpdateVMScheduleCmd.getEntityOwnerId is executed for a VM Schedule which doesn't exist"
|
||||||
|
* then: "an InvalidParameterException is thrown"
|
||||||
|
*/
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* 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.db.GenericDao;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.EnumType;
|
||||||
|
import javax.persistence.Enumerated;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.Temporal;
|
||||||
|
import javax.persistence.TemporalType;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "vm_schedule")
|
||||||
|
public class VMScheduleVO implements VMSchedule {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Column(name = "id", nullable = false)
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
@Column(name = "uuid", nullable = false)
|
||||||
|
String uuid;
|
||||||
|
|
||||||
|
@Column(name = "description")
|
||||||
|
String description;
|
||||||
|
|
||||||
|
@Column(name = "vm_id", nullable = false)
|
||||||
|
long vmId;
|
||||||
|
|
||||||
|
@Column(name = "schedule", nullable = false)
|
||||||
|
String schedule;
|
||||||
|
|
||||||
|
@Column(name = "timezone", nullable = false)
|
||||||
|
String timeZone;
|
||||||
|
|
||||||
|
@Column(name = "action", nullable = false)
|
||||||
|
@Enumerated(value = EnumType.STRING)
|
||||||
|
Action action;
|
||||||
|
|
||||||
|
@Column(name = "enabled", nullable = false)
|
||||||
|
boolean enabled;
|
||||||
|
|
||||||
|
@Column(name = "start_date", nullable = false)
|
||||||
|
@Temporal(value = TemporalType.TIMESTAMP)
|
||||||
|
Date startDate;
|
||||||
|
|
||||||
|
@Column(name = "end_date", nullable = true)
|
||||||
|
@Temporal(value = TemporalType.TIMESTAMP)
|
||||||
|
Date endDate;
|
||||||
|
|
||||||
|
@Column(name = GenericDao.CREATED_COLUMN)
|
||||||
|
Date created;
|
||||||
|
|
||||||
|
@Column(name = GenericDao.REMOVED_COLUMN)
|
||||||
|
Date removed;
|
||||||
|
|
||||||
|
public VMScheduleVO() {
|
||||||
|
uuid = UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public VMScheduleVO(long vmId, String description, String schedule, String timeZone, Action action, Date startDate, Date endDate, boolean enabled) {
|
||||||
|
uuid = UUID.randomUUID().toString();
|
||||||
|
this.vmId = vmId;
|
||||||
|
this.description = description;
|
||||||
|
this.schedule = schedule;
|
||||||
|
this.timeZone = timeZone;
|
||||||
|
this.action = action;
|
||||||
|
this.startDate = startDate;
|
||||||
|
this.endDate = endDate;
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUuid() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getVmId() {
|
||||||
|
return vmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVmId(long vmId) {
|
||||||
|
this.vmId = vmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSchedule() {
|
||||||
|
return schedule.substring(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSchedule(String schedule) {
|
||||||
|
this.schedule = schedule;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTimeZone() {
|
||||||
|
return timeZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeZone(String timeZone) {
|
||||||
|
this.timeZone = timeZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAction(Action action) {
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getStartDate() {
|
||||||
|
return startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartDate(Date startDate) {
|
||||||
|
this.startDate = startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getEndDate() {
|
||||||
|
return endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEndDate(Date endDate) {
|
||||||
|
this.endDate = endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ZoneId getTimeZoneId() {
|
||||||
|
return TimeZone.getTimeZone(getTimeZone()).toZoneId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* 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 javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.EnumType;
|
||||||
|
import javax.persistence.Enumerated;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.Temporal;
|
||||||
|
import javax.persistence.TemporalType;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "vm_scheduled_job")
|
||||||
|
public class VMScheduledJobVO implements VMScheduledJob {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Column(name = "id")
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
@Column(name = "uuid", nullable = false)
|
||||||
|
String uuid;
|
||||||
|
|
||||||
|
@Column(name = "vm_id", nullable = false)
|
||||||
|
long vmId;
|
||||||
|
|
||||||
|
@Column(name = "vm_schedule_id", nullable = false)
|
||||||
|
long vmScheduleId;
|
||||||
|
|
||||||
|
@Column(name = "async_job_id")
|
||||||
|
Long asyncJobId;
|
||||||
|
|
||||||
|
@Column(name = "action", nullable = false)
|
||||||
|
@Enumerated(value = EnumType.STRING)
|
||||||
|
VMSchedule.Action action;
|
||||||
|
|
||||||
|
@Column(name = "scheduled_timestamp")
|
||||||
|
@Temporal(value = TemporalType.TIMESTAMP)
|
||||||
|
Date scheduledTime;
|
||||||
|
|
||||||
|
public VMScheduledJobVO() {
|
||||||
|
uuid = UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public VMScheduledJobVO(long vmId, long vmScheduleId, VMSchedule.Action action, Date scheduledTime) {
|
||||||
|
uuid = UUID.randomUUID().toString();
|
||||||
|
this.vmId = vmId;
|
||||||
|
this.vmScheduleId = vmScheduleId;
|
||||||
|
this.action = action;
|
||||||
|
this.scheduledTime = scheduledTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUuid() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getVmId() {
|
||||||
|
return vmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getVmScheduleId() {
|
||||||
|
return vmScheduleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getAsyncJobId() {
|
||||||
|
return asyncJobId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAsyncJobId(long asyncJobId) {
|
||||||
|
this.asyncJobId = asyncJobId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VMSchedule.Action getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getScheduledTime() {
|
||||||
|
return scheduledTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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.GenericDao;
|
||||||
|
import com.cloud.utils.db.SearchCriteria;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMScheduleVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface VMScheduleDao extends GenericDao<VMScheduleVO, Long> {
|
||||||
|
List<VMScheduleVO> listAllActiveSchedules();
|
||||||
|
|
||||||
|
long removeSchedulesForVmIdAndIds(Long vmId, List<Long> ids);
|
||||||
|
|
||||||
|
Pair<List<VMScheduleVO>, Integer> searchAndCount(Long id, Long vmId, VMSchedule.Action action, Boolean enabled, Long offset, Long limit);
|
||||||
|
|
||||||
|
SearchCriteria<VMScheduleVO> getSearchCriteriaForVMId(Long vmId);
|
||||||
|
}
|
||||||
@ -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.vm.schedule.dao;
|
||||||
|
|
||||||
|
import com.cloud.utils.Pair;
|
||||||
|
import com.cloud.utils.db.Filter;
|
||||||
|
import com.cloud.utils.db.GenericDaoBase;
|
||||||
|
import com.cloud.utils.db.SearchBuilder;
|
||||||
|
import com.cloud.utils.db.SearchCriteria;
|
||||||
|
import org.apache.cloudstack.api.ApiConstants;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMScheduleVO;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class VMScheduleDaoImpl extends GenericDaoBase<VMScheduleVO, Long> implements VMScheduleDao {
|
||||||
|
|
||||||
|
private final SearchBuilder<VMScheduleVO> activeScheduleSearch;
|
||||||
|
|
||||||
|
private final SearchBuilder<VMScheduleVO> scheduleSearchByVmIdAndIds;
|
||||||
|
|
||||||
|
private final SearchBuilder<VMScheduleVO> scheduleSearch;
|
||||||
|
|
||||||
|
public VMScheduleDaoImpl() {
|
||||||
|
super();
|
||||||
|
activeScheduleSearch = createSearchBuilder();
|
||||||
|
activeScheduleSearch.and(ApiConstants.ENABLED, activeScheduleSearch.entity().getEnabled(), SearchCriteria.Op.EQ);
|
||||||
|
activeScheduleSearch.and().op(activeScheduleSearch.entity().getEndDate(), SearchCriteria.Op.NULL);
|
||||||
|
activeScheduleSearch.or(ApiConstants.END_DATE, activeScheduleSearch.entity().getEndDate(), SearchCriteria.Op.GT);
|
||||||
|
activeScheduleSearch.cp();
|
||||||
|
activeScheduleSearch.done();
|
||||||
|
|
||||||
|
scheduleSearchByVmIdAndIds = createSearchBuilder();
|
||||||
|
scheduleSearchByVmIdAndIds.and(ApiConstants.ID, scheduleSearchByVmIdAndIds.entity().getId(), SearchCriteria.Op.IN);
|
||||||
|
scheduleSearchByVmIdAndIds.and(ApiConstants.VIRTUAL_MACHINE_ID, scheduleSearchByVmIdAndIds.entity().getVmId(), SearchCriteria.Op.EQ);
|
||||||
|
scheduleSearchByVmIdAndIds.done();
|
||||||
|
|
||||||
|
scheduleSearch = createSearchBuilder();
|
||||||
|
scheduleSearch.and(ApiConstants.ID, scheduleSearch.entity().getId(), SearchCriteria.Op.EQ);
|
||||||
|
scheduleSearch.and(ApiConstants.VIRTUAL_MACHINE_ID, scheduleSearch.entity().getVmId(), SearchCriteria.Op.EQ);
|
||||||
|
scheduleSearch.and(ApiConstants.ACTION, scheduleSearch.entity().getAction(), SearchCriteria.Op.EQ);
|
||||||
|
scheduleSearch.and(ApiConstants.ENABLED, scheduleSearch.entity().getEnabled(), SearchCriteria.Op.EQ);
|
||||||
|
scheduleSearch.done();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<VMScheduleVO> listAllActiveSchedules() {
|
||||||
|
// WHERE enabled = true AND (end_date IS NULL OR end_date > current_date)
|
||||||
|
SearchCriteria<VMScheduleVO> sc = activeScheduleSearch.create();
|
||||||
|
sc.setParameters(ApiConstants.ENABLED, true);
|
||||||
|
sc.setParameters(ApiConstants.END_DATE, new Date());
|
||||||
|
return search(sc, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long removeSchedulesForVmIdAndIds(Long vmId, List<Long> ids) {
|
||||||
|
SearchCriteria<VMScheduleVO> sc = scheduleSearchByVmIdAndIds.create();
|
||||||
|
sc.setParameters(ApiConstants.ID, ids.toArray());
|
||||||
|
sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId);
|
||||||
|
return remove(sc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pair<List<VMScheduleVO>, Integer> searchAndCount(Long id, Long vmId, VMSchedule.Action action, Boolean enabled, Long offset, Long limit) {
|
||||||
|
SearchCriteria<VMScheduleVO> sc = scheduleSearch.create();
|
||||||
|
|
||||||
|
if (id != null) {
|
||||||
|
sc.setParameters(ApiConstants.ID, id);
|
||||||
|
}
|
||||||
|
if (enabled != null) {
|
||||||
|
sc.setParameters(ApiConstants.ENABLED, enabled);
|
||||||
|
}
|
||||||
|
if (action != null) {
|
||||||
|
sc.setParameters(ApiConstants.ACTION, action);
|
||||||
|
}
|
||||||
|
sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId);
|
||||||
|
|
||||||
|
Filter filter = new Filter(VMScheduleVO.class, ApiConstants.ID, false, offset, limit);
|
||||||
|
return searchAndCount(sc, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SearchCriteria<VMScheduleVO> getSearchCriteriaForVMId(Long vmId) {
|
||||||
|
SearchCriteria<VMScheduleVO> sc = scheduleSearch.create();
|
||||||
|
sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId);
|
||||||
|
return sc;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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.db.GenericDao;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMScheduledJobVO;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface VMScheduledJobDao extends GenericDao<VMScheduledJobVO, Long> {
|
||||||
|
|
||||||
|
List<VMScheduledJobVO> listJobsToStart(Date currentTimestamp);
|
||||||
|
|
||||||
|
int expungeJobsForSchedules(List<Long> scheduleId, Date dateAfter);
|
||||||
|
|
||||||
|
int expungeJobsBefore(Date currentTimestamp);
|
||||||
|
}
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.commons.lang3.time.DateUtils;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class VMScheduledJobDaoImpl extends GenericDaoBase<VMScheduledJobVO, Long> implements VMScheduledJobDao {
|
||||||
|
|
||||||
|
private final SearchBuilder<VMScheduledJobVO> jobsToStartSearch;
|
||||||
|
|
||||||
|
private final SearchBuilder<VMScheduledJobVO> expungeJobsBeforeSearch;
|
||||||
|
|
||||||
|
private final SearchBuilder<VMScheduledJobVO> expungeJobForScheduleSearch;
|
||||||
|
|
||||||
|
static final String SCHEDULED_TIMESTAMP = "scheduled_timestamp";
|
||||||
|
|
||||||
|
static final String VM_SCHEDULE_ID = "vm_schedule_id";
|
||||||
|
|
||||||
|
public VMScheduledJobDaoImpl() {
|
||||||
|
super();
|
||||||
|
jobsToStartSearch = createSearchBuilder();
|
||||||
|
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(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(SCHEDULED_TIMESTAMP, expungeJobForScheduleSearch.entity().getScheduledTime(), SearchCriteria.Op.GTEQ);
|
||||||
|
expungeJobForScheduleSearch.done();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execution of job wouldn't be at exact seconds. So, we round off and then execute.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<VMScheduledJobVO> listJobsToStart(Date currentTimestamp) {
|
||||||
|
if (currentTimestamp == null) {
|
||||||
|
currentTimestamp = new Date();
|
||||||
|
}
|
||||||
|
Date truncatedTs = DateUtils.round(currentTimestamp, Calendar.MINUTE);
|
||||||
|
|
||||||
|
SearchCriteria<VMScheduledJobVO> sc = jobsToStartSearch.create();
|
||||||
|
sc.setParameters(SCHEDULED_TIMESTAMP, truncatedTs);
|
||||||
|
Filter filter = new Filter(VMScheduledJobVO.class, "vmScheduleId", true, null, null);
|
||||||
|
return search(sc, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int expungeJobsForSchedules(List<Long> vmScheduleIds, Date dateAfter) {
|
||||||
|
SearchCriteria<VMScheduledJobVO> sc = expungeJobForScheduleSearch.create();
|
||||||
|
sc.setParameters(VM_SCHEDULE_ID, vmScheduleIds.toArray());
|
||||||
|
if (dateAfter != null) {
|
||||||
|
sc.setParameters(SCHEDULED_TIMESTAMP, dateAfter);
|
||||||
|
}
|
||||||
|
return expunge(sc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int expungeJobsBefore(Date date) {
|
||||||
|
SearchCriteria<VMScheduledJobVO> sc = expungeJobsBeforeSearch.create();
|
||||||
|
sc.setParameters(SCHEDULED_TIMESTAMP, date);
|
||||||
|
return expunge(sc);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -275,4 +275,6 @@
|
|||||||
<bean id="UserVmDeployAsIsDetailsDaoImpl" class="com.cloud.deployasis.dao.UserVmDeployAsIsDetailsDaoImpl" />
|
<bean id="UserVmDeployAsIsDetailsDaoImpl" class="com.cloud.deployasis.dao.UserVmDeployAsIsDetailsDaoImpl" />
|
||||||
<bean id="NetworkPermissionDaoImpl" class="org.apache.cloudstack.network.dao.NetworkPermissionDaoImpl" />
|
<bean id="NetworkPermissionDaoImpl" class="org.apache.cloudstack.network.dao.NetworkPermissionDaoImpl" />
|
||||||
<bean id="PassphraseDaoImpl" class="org.apache.cloudstack.secret.dao.PassphraseDaoImpl" />
|
<bean id="PassphraseDaoImpl" class="org.apache.cloudstack.secret.dao.PassphraseDaoImpl" />
|
||||||
|
<bean id="VMScheduleDaoImpl" class="org.apache.cloudstack.vm.schedule.dao.VMScheduleDaoImpl" />
|
||||||
|
<bean id="VMScheduledJobDaoImpl" class="org.apache.cloudstack.vm.schedule.dao.VMScheduledJobDaoImpl" />
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@ -135,5 +135,44 @@ CREATE VIEW `cloud`.`async_job_view` AS
|
|||||||
UPDATE `cloud`.`console_session` SET removed=now();
|
UPDATE `cloud`.`console_session` SET removed=now();
|
||||||
-- Modify acquired column in console_session to datetime type
|
-- Modify acquired column in console_session to datetime type
|
||||||
ALTER TABLE `cloud`.`console_session` DROP `acquired`, ADD `acquired` datetime COMMENT 'When the session was acquired' AFTER `host_id`;
|
ALTER TABLE `cloud`.`console_session` DROP `acquired`, ADD `acquired` datetime COMMENT 'When the session was acquired' AFTER `host_id`;
|
||||||
|
|
||||||
-- create_public_parameter_on_roles. #6960
|
-- create_public_parameter_on_roles. #6960
|
||||||
ALTER TABLE `cloud`.`roles` ADD COLUMN `public_role` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Indicates whether the role will be visible to all users (public) or only to root admins (private). If this parameter is not specified during the creation of the role its value will be defaulted to true (public).';
|
ALTER TABLE `cloud`.`roles` ADD COLUMN `public_role` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Indicates whether the role will be visible to all users (public) or only to root admins (private). If this parameter is not specified during the creation of the role its value will be defaulted to true (public).';
|
||||||
|
|
||||||
|
-- Add tables for VM Scheduler
|
||||||
|
DROP TABLE IF EXISTS `cloud`.`vm_schedule`;
|
||||||
|
CREATE TABLE `cloud`.`vm_schedule` (
|
||||||
|
`id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
|
||||||
|
`vm_id` bigint unsigned NOT NULL,
|
||||||
|
`uuid` varchar(40) NOT NULL COMMENT 'schedule uuid',
|
||||||
|
`description` varchar(1024) COMMENT 'description of the vm schedule',
|
||||||
|
`schedule` varchar(255) NOT NULL COMMENT 'schedule frequency in cron format',
|
||||||
|
`timezone` varchar(100) NOT NULL COMMENT 'the timezone in which the schedule time is specified',
|
||||||
|
`action` varchar(20) NOT NULL COMMENT 'action to perform',
|
||||||
|
`enabled` int(1) NOT NULL COMMENT 'Enabled or disabled',
|
||||||
|
`start_date` datetime NOT NULL COMMENT 'start time for this schedule',
|
||||||
|
`end_date` datetime COMMENT 'end time for this schedule',
|
||||||
|
`created` datetime NOT NULL COMMENT 'date created',
|
||||||
|
`removed` datetime COMMENT 'date removed if not null',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
INDEX `i_vm_schedule__vm_id`(`vm_id`),
|
||||||
|
INDEX `i_vm_schedule__enabled_end_date`(`enabled`, `end_date`),
|
||||||
|
CONSTRAINT `fk_vm_schedule__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance`(`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `cloud`.`vm_scheduled_job`;
|
||||||
|
CREATE TABLE `cloud`.`vm_scheduled_job` (
|
||||||
|
`id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
|
||||||
|
`vm_id` bigint unsigned NOT NULL,
|
||||||
|
`vm_schedule_id` bigint unsigned NOT NULL,
|
||||||
|
`uuid` varchar(40) NOT NULL COMMENT 'scheduled job uuid',
|
||||||
|
`action` varchar(20) NOT NULL COMMENT 'action to perform',
|
||||||
|
`scheduled_timestamp` datetime NOT NULL COMMENT 'Time at which the action is taken',
|
||||||
|
`async_job_id` bigint unsigned DEFAULT NULL COMMENT 'If this schedule is being executed, it is the id of the create aysnc_job. Before that it is null',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY (`vm_schedule_id`, `scheduled_timestamp`),
|
||||||
|
INDEX `i_vm_scheduled_job__scheduled_timestamp`(`scheduled_timestamp`),
|
||||||
|
INDEX `i_vm_scheduled_job__vm_id`(`vm_id`),
|
||||||
|
CONSTRAINT `fk_vm_scheduled_job__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance`(`id`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `fk_vm_scheduled_job__vm_schedule_id` FOREIGN KEY (`vm_schedule_id`) REFERENCES `vm_schedule`(`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
|||||||
1
pom.xml
1
pom.xml
@ -133,6 +133,7 @@
|
|||||||
<cs.bcprov.version>1.70</cs.bcprov.version>
|
<cs.bcprov.version>1.70</cs.bcprov.version>
|
||||||
<cs.cglib.version>3.3.0</cs.cglib.version>
|
<cs.cglib.version>3.3.0</cs.cglib.version>
|
||||||
<cs.checkstyle-lib.version>8.18</cs.checkstyle-lib.version>
|
<cs.checkstyle-lib.version>8.18</cs.checkstyle-lib.version>
|
||||||
|
<cs.cron-utils.version>9.2.0</cs.cron-utils.version>
|
||||||
<cs.cxf.version>3.2.14</cs.cxf.version>
|
<cs.cxf.version>3.2.14</cs.cxf.version>
|
||||||
<cs.ehcache.version>2.6.11</cs.ehcache.version>
|
<cs.ehcache.version>2.6.11</cs.ehcache.version>
|
||||||
<cs.globodns-client.version>0.0.27</cs.globodns-client.version>
|
<cs.globodns-client.version>0.0.27</cs.globodns-client.version>
|
||||||
|
|||||||
@ -125,6 +125,7 @@ import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
|
|||||||
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
|
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
|
||||||
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
|
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
|
||||||
import org.apache.cloudstack.utils.security.ParserUtils;
|
import org.apache.cloudstack.utils.security.ParserUtils;
|
||||||
|
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||||
import org.apache.commons.codec.binary.Base64;
|
import org.apache.commons.codec.binary.Base64;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.apache.commons.collections.MapUtils;
|
import org.apache.commons.collections.MapUtils;
|
||||||
@ -582,6 +583,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||||||
@Inject
|
@Inject
|
||||||
private AutoScaleManager autoScaleManager;
|
private AutoScaleManager autoScaleManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
VMScheduleManager vmScheduleManager;
|
||||||
|
|
||||||
private ScheduledExecutorService _executor = null;
|
private ScheduledExecutorService _executor = null;
|
||||||
private ScheduledExecutorService _vmIpFetchExecutor = null;
|
private ScheduledExecutorService _vmIpFetchExecutor = null;
|
||||||
private int _expungeInterval;
|
private int _expungeInterval;
|
||||||
@ -3272,6 +3276,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||||||
|
|
||||||
autoScaleManager.removeVmFromVmGroup(vmId);
|
autoScaleManager.removeVmFromVmGroup(vmId);
|
||||||
|
|
||||||
|
vmScheduleManager.removeScheduleByVmId(vmId, expunge);
|
||||||
|
|
||||||
deleteVolumesFromVm(volumesToBeDeleted, expunge);
|
deleteVolumesFromVm(volumesToBeDeleted, expunge);
|
||||||
|
|
||||||
if (getDestroyRootVolumeOnVmDestruction(vm.getDomainId())) {
|
if (getDestroyRootVolumeOnVmDestruction(vm.getDomainId())) {
|
||||||
|
|||||||
@ -0,0 +1,317 @@
|
|||||||
|
/*
|
||||||
|
* 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.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;
|
||||||
|
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.StringUtils;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
import org.springframework.scheduling.support.CronExpression;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
public class VMScheduleManagerImpl extends MutualExclusiveIdsManagerBase implements VMScheduleManager, PluggableService {
|
||||||
|
|
||||||
|
private static Logger LOGGER = Logger.getLogger(VMScheduleManagerImpl.class);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private VMScheduleDao vmScheduleDao;
|
||||||
|
@Inject
|
||||||
|
private UserVmManager userVmManager;
|
||||||
|
@Inject
|
||||||
|
private VMScheduler vmScheduler;
|
||||||
|
@Inject
|
||||||
|
private AccountManager accountManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Class<?>> getCommands() {
|
||||||
|
final List<Class<?>> cmdList = new ArrayList<>();
|
||||||
|
cmdList.add(CreateVMScheduleCmd.class);
|
||||||
|
cmdList.add(ListVMScheduleCmd.class);
|
||||||
|
cmdList.add(UpdateVMScheduleCmd.class);
|
||||||
|
cmdList.add(DeleteVMScheduleCmd.class);
|
||||||
|
return cmdList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@ActionEvent(eventType = EventTypes.EVENT_VM_SCHEDULE_CREATE, eventDescription = "Creating VM Schedule", create = true)
|
||||||
|
public VMScheduleResponse createSchedule(CreateVMScheduleCmd cmd) {
|
||||||
|
VirtualMachine vm = userVmManager.getUserVm(cmd.getVmId());
|
||||||
|
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, vm);
|
||||||
|
if (vm == null) {
|
||||||
|
throw new InvalidParameterValueException(String.format("Invalid value for vmId: %s", cmd.getVmId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
VMSchedule.Action action = null;
|
||||||
|
if (cmd.getAction() != null) {
|
||||||
|
try {
|
||||||
|
action = VMSchedule.Action.valueOf(cmd.getAction().toUpperCase());
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
throw new InvalidParameterValueException(String.format("Invalid value for action: %s", cmd.getAction()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Date cmdStartDate = cmd.getStartDate();
|
||||||
|
Date cmdEndDate = cmd.getEndDate();
|
||||||
|
String cmdTimeZone = cmd.getTimeZone();
|
||||||
|
TimeZone timeZone = TimeZone.getTimeZone(cmdTimeZone);
|
||||||
|
String timeZoneId = timeZone.getID();
|
||||||
|
Date startDate = DateUtils.addMinutes(new Date(), 1);
|
||||||
|
if (cmdStartDate != null) {
|
||||||
|
startDate = Date.from(DateUtil.getZoneDateTime(cmdStartDate, timeZone.toZoneId()).toInstant());
|
||||||
|
}
|
||||||
|
Date endDate = null;
|
||||||
|
if (cmdEndDate != null) {
|
||||||
|
endDate = Date.from(DateUtil.getZoneDateTime(cmdEndDate, timeZone.toZoneId()).toInstant());
|
||||||
|
}
|
||||||
|
|
||||||
|
CronExpression cronExpression = DateUtil.parseSchedule(cmd.getSchedule());
|
||||||
|
|
||||||
|
validateStartDateEndDate(startDate, endDate, timeZone);
|
||||||
|
|
||||||
|
String description = null;
|
||||||
|
if (StringUtils.isBlank(cmd.getDescription())) {
|
||||||
|
description = String.format("%s - %s", action, DateUtil.getHumanReadableSchedule(cronExpression));
|
||||||
|
} else description = cmd.getDescription();
|
||||||
|
|
||||||
|
LOGGER.warn(String.format("Using timezone [%s] for running the schedule for VM [%s], as an equivalent of [%s].", timeZoneId, vm.getUuid(), cmdTimeZone));
|
||||||
|
|
||||||
|
String finalDescription = description;
|
||||||
|
VMSchedule.Action finalAction = action;
|
||||||
|
Date finalStartDate = startDate;
|
||||||
|
Date finalEndDate = endDate;
|
||||||
|
|
||||||
|
return Transaction.execute((TransactionCallback<VMScheduleResponse>) status -> {
|
||||||
|
VMScheduleVO vmSchedule = vmScheduleDao.persist(new VMScheduleVO(cmd.getVmId(), finalDescription, cronExpression.toString(), timeZoneId, finalAction, finalStartDate, finalEndDate, cmd.getEnabled()));
|
||||||
|
vmScheduler.scheduleNextJob(vmSchedule);
|
||||||
|
CallContext.current().setEventResourceId(vm.getId());
|
||||||
|
CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine);
|
||||||
|
return createResponse(vmSchedule);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VMScheduleResponse createResponse(VMSchedule vmSchedule) {
|
||||||
|
VirtualMachine vm = userVmManager.getUserVm(vmSchedule.getVmId());
|
||||||
|
VMScheduleResponse response = new VMScheduleResponse();
|
||||||
|
|
||||||
|
response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
|
||||||
|
response.setId(vmSchedule.getUuid());
|
||||||
|
response.setVmId(vm.getUuid());
|
||||||
|
response.setDescription(vmSchedule.getDescription());
|
||||||
|
response.setSchedule(vmSchedule.getSchedule());
|
||||||
|
response.setTimeZone(vmSchedule.getTimeZone());
|
||||||
|
response.setAction(vmSchedule.getAction());
|
||||||
|
response.setEnabled(vmSchedule.getEnabled());
|
||||||
|
response.setStartDate(vmSchedule.getStartDate());
|
||||||
|
response.setEndDate(vmSchedule.getEndDate());
|
||||||
|
response.setCreated(vmSchedule.getCreated());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListResponse<VMScheduleResponse> listSchedule(ListVMScheduleCmd cmd) {
|
||||||
|
Long id = cmd.getId();
|
||||||
|
Boolean enabled = cmd.getEnabled();
|
||||||
|
Long vmId = cmd.getVmId();
|
||||||
|
|
||||||
|
VirtualMachine vm = userVmManager.getUserVm(vmId);
|
||||||
|
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, vm);
|
||||||
|
|
||||||
|
VMSchedule.Action action = null;
|
||||||
|
if (cmd.getAction() != null) {
|
||||||
|
try {
|
||||||
|
action = VMSchedule.Action.valueOf(cmd.getAction());
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
throw new InvalidParameterValueException("Invalid value for action: " + cmd.getAction());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pair<List<VMScheduleVO>, Integer> result = vmScheduleDao.searchAndCount(id, vmId, action, enabled, cmd.getStartIndex(), cmd.getPageSizeVal());
|
||||||
|
|
||||||
|
ListResponse<VMScheduleResponse> response = new ListResponse<>();
|
||||||
|
List<VMScheduleResponse> responsesList = new ArrayList<>();
|
||||||
|
for (VMSchedule vmSchedule : result.first()) {
|
||||||
|
responsesList.add(createResponse(vmSchedule));
|
||||||
|
}
|
||||||
|
response.setResponses(responsesList, result.second());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@ActionEvent(eventType = EventTypes.EVENT_VM_SCHEDULE_UPDATE, eventDescription = "Updating VM Schedule")
|
||||||
|
public VMScheduleResponse updateSchedule(UpdateVMScheduleCmd cmd) {
|
||||||
|
Long id = cmd.getId();
|
||||||
|
VMScheduleVO vmSchedule = vmScheduleDao.findById(id);
|
||||||
|
|
||||||
|
if (vmSchedule == null) {
|
||||||
|
throw new CloudRuntimeException("VM schedule doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
VirtualMachine vm = userVmManager.getUserVm(vmSchedule.getVmId());
|
||||||
|
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, vm);
|
||||||
|
|
||||||
|
CronExpression cronExpression = Objects.requireNonNullElse(
|
||||||
|
DateUtil.parseSchedule(cmd.getSchedule()),
|
||||||
|
DateUtil.parseSchedule(vmSchedule.getSchedule())
|
||||||
|
);
|
||||||
|
|
||||||
|
String description = cmd.getDescription();
|
||||||
|
if (description == null && vmSchedule.getDescription() == null) {
|
||||||
|
description = String.format("%s - %s", vmSchedule.getAction(), DateUtil.getHumanReadableSchedule(cronExpression));
|
||||||
|
}
|
||||||
|
String cmdTimeZone = cmd.getTimeZone();
|
||||||
|
Date cmdStartDate = cmd.getStartDate();
|
||||||
|
Date cmdEndDate = cmd.getEndDate();
|
||||||
|
Boolean enabled = cmd.getEnabled();
|
||||||
|
|
||||||
|
TimeZone timeZone;
|
||||||
|
String timeZoneId;
|
||||||
|
if (cmdTimeZone != null) {
|
||||||
|
timeZone = TimeZone.getTimeZone(cmdTimeZone);
|
||||||
|
timeZoneId = timeZone.getID();
|
||||||
|
if (!timeZoneId.equals(cmdTimeZone)) {
|
||||||
|
LOGGER.warn(String.format("Using timezone [%s] for running the schedule [%s] for VM %s, as an equivalent of [%s].",
|
||||||
|
timeZoneId, vmSchedule.getSchedule(), 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
validateStartDateEndDate(Objects.requireNonNullElse(startDate, DateUtils.addMinutes(new Date(), 1)), endDate, timeZone);
|
||||||
|
|
||||||
|
if (enabled != null) {
|
||||||
|
vmSchedule.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
if (description != null) {
|
||||||
|
vmSchedule.setDescription(description);
|
||||||
|
}
|
||||||
|
if (cmdEndDate != null) {
|
||||||
|
vmSchedule.setEndDate(endDate);
|
||||||
|
}
|
||||||
|
if (cmdStartDate != null) {
|
||||||
|
vmSchedule.setStartDate(startDate);
|
||||||
|
}
|
||||||
|
vmSchedule.setSchedule(cronExpression.toString());
|
||||||
|
|
||||||
|
return Transaction.execute((TransactionCallback<VMScheduleResponse>) status -> {
|
||||||
|
vmScheduleDao.update(cmd.getId(), vmSchedule);
|
||||||
|
vmScheduler.updateScheduledJob(vmSchedule);
|
||||||
|
CallContext.current().setEventResourceId(vm.getId());
|
||||||
|
CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine);
|
||||||
|
return createResponse(vmSchedule);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void validateStartDateEndDate(Date startDate, Date endDate, TimeZone tz) {
|
||||||
|
ZonedDateTime now = ZonedDateTime.now(tz.toZoneId());
|
||||||
|
ZonedDateTime zonedStartDate = ZonedDateTime.ofInstant(startDate.toInstant(), tz.toZoneId());
|
||||||
|
|
||||||
|
if (zonedStartDate.isBefore(now)) {
|
||||||
|
throw new InvalidParameterValueException(String.format("Invalid value for start date. Start date [%s] can't be before current time [%s].", zonedStartDate, now));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endDate != null) {
|
||||||
|
ZonedDateTime zonedEndDate = ZonedDateTime.ofInstant(endDate.toInstant(), tz.toZoneId());
|
||||||
|
if (zonedEndDate.isBefore(now)) {
|
||||||
|
throw new InvalidParameterValueException(String.format("Invalid value for end date. End date [%s] can't be before current time [%s].", zonedEndDate, now));
|
||||||
|
}
|
||||||
|
if (zonedEndDate.isBefore(zonedStartDate)) {
|
||||||
|
throw new InvalidParameterValueException(String.format("Invalid value for end date. End date [%s] can't be before start date [%s].", zonedEndDate, zonedStartDate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@ActionEvent(eventType = EventTypes.EVENT_VM_SCHEDULE_DELETE, eventDescription = "Deleting VM Schedule for VM")
|
||||||
|
public long removeScheduleByVmId(long vmId, boolean expunge) {
|
||||||
|
SearchCriteria<VMScheduleVO> sc = vmScheduleDao.getSearchCriteriaForVMId(vmId);
|
||||||
|
List<VMScheduleVO> vmSchedules = vmScheduleDao.search(sc, null);
|
||||||
|
List<Long> ids = new ArrayList<>();
|
||||||
|
for (final VMScheduleVO vmSchedule : vmSchedules) {
|
||||||
|
ids.add(vmSchedule.getId());
|
||||||
|
}
|
||||||
|
vmScheduler.removeScheduledJobs(ids);
|
||||||
|
if (expunge) {
|
||||||
|
return vmScheduleDao.expunge(sc);
|
||||||
|
}
|
||||||
|
CallContext.current().setEventResourceId(vmId);
|
||||||
|
CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine);
|
||||||
|
return vmScheduleDao.remove(sc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@ActionEvent(eventType = EventTypes.EVENT_VM_SCHEDULE_DELETE, eventDescription = "Deleting VM Schedule")
|
||||||
|
public Long removeSchedule(DeleteVMScheduleCmd cmd) {
|
||||||
|
VirtualMachine vm = userVmManager.getUserVm(cmd.getVmId());
|
||||||
|
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, false, vm);
|
||||||
|
|
||||||
|
List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds());
|
||||||
|
|
||||||
|
if (ids.isEmpty()) {
|
||||||
|
throw new InvalidParameterValueException("Either id or ids parameter must be specified");
|
||||||
|
}
|
||||||
|
return Transaction.execute((TransactionCallback<Long>) status -> {
|
||||||
|
vmScheduler.removeScheduledJobs(ids);
|
||||||
|
CallContext.current().setEventResourceId(vm.getId());
|
||||||
|
CallContext.current().setEventResourceType(ApiCommandResourceType.VirtualMachine);
|
||||||
|
return vmScheduleDao.removeSchedulesForVmIdAndIds(vm.getId(), ids);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cloudstack.vm.schedule;
|
||||||
|
|
||||||
|
import com.cloud.utils.component.Manager;
|
||||||
|
import com.cloud.utils.concurrency.Scheduler;
|
||||||
|
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface VMScheduler extends Manager, Scheduler {
|
||||||
|
ConfigKey<Integer> VMScheduledJobExpireInterval = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class, "vmscheduler.jobs.expire.interval", "30", "VM Scheduler expire interval in days", true);
|
||||||
|
|
||||||
|
void removeScheduledJobs(List<Long> vmScheduleIds);
|
||||||
|
|
||||||
|
void updateScheduledJob(VMScheduleVO vmSchedule);
|
||||||
|
|
||||||
|
Date scheduleNextJob(VMScheduleVO vmSchedule);
|
||||||
|
}
|
||||||
@ -0,0 +1,405 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.apache.log4j.Logger;
|
||||||
|
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 {
|
||||||
|
private static Logger LOGGER = Logger.getLogger(VMSchedulerImpl.class);
|
||||||
|
@Inject
|
||||||
|
private VMScheduledJobDao vmScheduledJobDao;
|
||||||
|
@Inject
|
||||||
|
private VMScheduleDao vmScheduleDao;
|
||||||
|
@Inject
|
||||||
|
private UserVmManager userVmManager;
|
||||||
|
@Inject
|
||||||
|
private AsyncJobManager asyncJobManager;
|
||||||
|
private AsyncJobDispatcher asyncJobDispatcher;
|
||||||
|
private Timer vmSchedulerTimer;
|
||||||
|
private Date currentTimestamp;
|
||||||
|
|
||||||
|
private EnumMap<VMSchedule.Action, String> actionEventMap = new EnumMap<>(VMSchedule.Action.class);
|
||||||
|
|
||||||
|
public AsyncJobDispatcher getAsyncJobDispatcher() {
|
||||||
|
return asyncJobDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAsyncJobDispatcher(final AsyncJobDispatcher dispatcher) {
|
||||||
|
asyncJobDispatcher = dispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeScheduledJobs(List<Long> vmScheduleIds) {
|
||||||
|
if (vmScheduleIds == null || vmScheduleIds.isEmpty()) {
|
||||||
|
LOGGER.debug("Removed 0 scheduled jobs");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Date now = new Date();
|
||||||
|
int rowsRemoved = vmScheduledJobDao.expungeJobsForSchedules(vmScheduleIds, now);
|
||||||
|
LOGGER.debug(String.format("Removed %s VM scheduled jobs", rowsRemoved));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateScheduledJob(VMScheduleVO vmSchedule) {
|
||||||
|
removeScheduledJobs(Longs.asList(vmSchedule.getId()));
|
||||||
|
scheduleNextJob(vmSchedule);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date scheduleNextJob(Long vmScheduleId) {
|
||||||
|
VMScheduleVO vmSchedule = vmScheduleDao.findById(vmScheduleId);
|
||||||
|
if (vmSchedule != null) {
|
||||||
|
return scheduleNextJob(vmSchedule);
|
||||||
|
}
|
||||||
|
LOGGER.debug(String.format("VM Schedule [id=%s] is removed. Not scheduling next job.", vmScheduleId));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date scheduleNextJob(VMScheduleVO vmSchedule) {
|
||||||
|
if (!vmSchedule.getEnabled()) {
|
||||||
|
LOGGER.debug(String.format("VM Schedule [id=%s] for VM [id=%s] is disabled. Not scheduling next job.", vmSchedule.getUuid(), 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(String.format("VM [id=%s] is removed. Disabling VM schedule [id=%s].", vmSchedule.getVmId(), vmSchedule.getUuid()));
|
||||||
|
vmSchedule.setEnabled(false);
|
||||||
|
vmScheduleDao.persist(vmSchedule);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ZonedDateTime 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(String.format("End time is less than current time. Disabling VM schedule [id=%s] for VM [id=%s].", vmSchedule.getUuid(), vmSchedule.getVmId()));
|
||||||
|
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(String.format("No next schedule found. Disabling VM schedule [id=%s] for VM [id=%s].", vmSchedule.getUuid(), vmSchedule.getVmId()));
|
||||||
|
vmSchedule.setEnabled(false);
|
||||||
|
vmScheduleDao.persist(vmSchedule);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Date scheduledDateTime = Date.from(ts.toInstant());
|
||||||
|
VMScheduledJobVO 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) [vmId: %s scheduleId: %s] at %s", vmSchedule.getAction(), vm.getUuid(), vmSchedule.getUuid(), 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();
|
||||||
|
|
||||||
|
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.schedule(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();
|
||||||
|
} 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() {
|
||||||
|
for (final VMScheduleVO schedule : vmScheduleDao.listAllActiveSchedules()) {
|
||||||
|
try {
|
||||||
|
scheduleNextJob(schedule);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.warn("Error in scheduling next job for schedule " + schedule.getUuid(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete scheduled jobs before vm.scheduler.expire.interval days
|
||||||
|
*/
|
||||||
|
private void cleanupVMScheduledJobs() {
|
||||||
|
Date deleteBeforeDate = DateUtils.addDays(currentTimestamp, -1 * VMScheduledJobExpireInterval.value());
|
||||||
|
int rowsRemoved = vmScheduledJobDao.expungeJobsBefore(deleteBeforeDate);
|
||||||
|
LOGGER.info(String.format("Cleaned up %d VM scheduled job entries", rowsRemoved));
|
||||||
|
}
|
||||||
|
|
||||||
|
void executeJobs(Map<Long, VMScheduledJob> jobsToExecute) {
|
||||||
|
String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
|
||||||
|
|
||||||
|
for (Map.Entry<Long, VMScheduledJob> entry : jobsToExecute.entrySet()) {
|
||||||
|
VMScheduledJob vmScheduledJob = entry.getValue();
|
||||||
|
VirtualMachine vm = userVmManager.getUserVm(vmScheduledJob.getVmId());
|
||||||
|
|
||||||
|
VMScheduledJobVO tmpVMScheduleJob = null;
|
||||||
|
try {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
final Date scheduledTimestamp = vmScheduledJob.getScheduledTime();
|
||||||
|
displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, scheduledTimestamp);
|
||||||
|
LOGGER.debug(String.format("Executing %s for VM id %d for schedule id: %d at %s", vmScheduledJob.getAction(), vmScheduledJob.getVmId(), vmScheduledJob.getVmScheduleId(), 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(String.format("Executing scheduled job id: %s failed due to %s", vmScheduledJob.getId(), 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(String.format("Skipping action (%s) for [vmId:%s scheduleId: %s] because VM is invalid state: %s", vmScheduledJob.getAction(), vm.getUuid(), vmScheduledJob.getVmScheduleId(), 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 Id:%s", vmScheduledJob.getAction(), vm.getUuid()),
|
||||||
|
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(String.format("Skipping action (%s) for [vmId:%s scheduleId: %s] because VM is in state: %s",
|
||||||
|
vmScheduledJob.getAction(), vm.getUuid(), vmScheduledJob.getVmScheduleId(), vm.getState()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void skipJobs(Map<Long, VMScheduledJob> jobsToExecute, Map<Long, List<VMScheduledJob>> jobsNotToExecute) {
|
||||||
|
for (Map.Entry<Long, List<VMScheduledJob>> entry : jobsNotToExecute.entrySet()) {
|
||||||
|
Long vmId = entry.getKey();
|
||||||
|
List<VMScheduledJob> skippedVmScheduledJobVOS = entry.getValue();
|
||||||
|
VirtualMachine vm = userVmManager.getUserVm(vmId);
|
||||||
|
for (final VMScheduledJob skippedVmScheduledJobVO : skippedVmScheduledJobVOS) {
|
||||||
|
VMScheduledJob scheduledJob = jobsToExecute.get(vmId);
|
||||||
|
LOGGER.info(String.format("Skipping scheduled job [id: %s, vmId: %s] because of conflict with another scheduled job [id: %s]", skippedVmScheduledJobVO.getUuid(), vm.getUuid(), scheduledJob.getUuid()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create async jobs for VM scheduled jobs
|
||||||
|
*/
|
||||||
|
private void startJobs() {
|
||||||
|
String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
|
||||||
|
|
||||||
|
final List<VMScheduledJobVO> vmScheduledJobs = vmScheduledJobDao.listJobsToStart(currentTimestamp);
|
||||||
|
LOGGER.debug(String.format("Got %d scheduled jobs to be executed at %s", vmScheduledJobs.size(), displayTime));
|
||||||
|
|
||||||
|
Map<Long, VMScheduledJob> jobsToExecute = new HashMap<>();
|
||||||
|
Map<Long, List<VMScheduledJob>> jobsNotToExecute = new HashMap<>();
|
||||||
|
for (final VMScheduledJobVO vmScheduledJobVO : vmScheduledJobs) {
|
||||||
|
long vmId = vmScheduledJobVO.getVmId();
|
||||||
|
if (jobsToExecute.get(vmId) == null) {
|
||||||
|
jobsToExecute.put(vmId, vmScheduledJobVO);
|
||||||
|
} else {
|
||||||
|
jobsNotToExecute.computeIfAbsent(vmId, k -> new ArrayList<>()).add(vmScheduledJobVO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executeJobs(jobsToExecute);
|
||||||
|
skipJobs(jobsToExecute, jobsNotToExecute);
|
||||||
|
}
|
||||||
|
|
||||||
|
long executeStartVMJob(VirtualMachine vm, long eventId) {
|
||||||
|
final Map<String, String> params = new HashMap<>();
|
||||||
|
params.put(ApiConstants.ID, String.valueOf(vm.getId()));
|
||||||
|
params.put("ctxUserId", "1");
|
||||||
|
params.put("ctxAccountId", String.valueOf(vm.getAccountId()));
|
||||||
|
params.put(ApiConstants.CTX_START_EVENT_ID, String.valueOf(eventId));
|
||||||
|
|
||||||
|
final StartVMCmd cmd = new StartVMCmd();
|
||||||
|
ComponentContext.inject(cmd);
|
||||||
|
|
||||||
|
AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, vm.getAccountId(), StartVMCmd.class.getName(), ApiGsonHelper.getBuilder().create().toJson(params), vm.getId(), cmd.getApiResourceType() != null ? cmd.getApiResourceType().toString() : null, null);
|
||||||
|
job.setDispatcher(asyncJobDispatcher.getName());
|
||||||
|
|
||||||
|
return asyncJobManager.submitAsyncJob(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
long executeStopVMJob(VirtualMachine vm, boolean isForced, long eventId) {
|
||||||
|
final Map<String, String> params = new HashMap<>();
|
||||||
|
params.put(ApiConstants.ID, String.valueOf(vm.getId()));
|
||||||
|
params.put("ctxUserId", "1");
|
||||||
|
params.put("ctxAccountId", String.valueOf(vm.getAccountId()));
|
||||||
|
params.put(ApiConstants.CTX_START_EVENT_ID, String.valueOf(eventId));
|
||||||
|
params.put(ApiConstants.FORCED, String.valueOf(isForced));
|
||||||
|
|
||||||
|
final StopVMCmd cmd = new StopVMCmd();
|
||||||
|
ComponentContext.inject(cmd);
|
||||||
|
|
||||||
|
AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, vm.getAccountId(), StopVMCmd.class.getName(), ApiGsonHelper.getBuilder().create().toJson(params), vm.getId(), cmd.getApiResourceType() != null ? cmd.getApiResourceType().toString() : null, null);
|
||||||
|
job.setDispatcher(asyncJobDispatcher.getName());
|
||||||
|
|
||||||
|
return asyncJobManager.submitAsyncJob(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
long executeRebootVMJob(VirtualMachine vm, boolean isForced, long eventId) {
|
||||||
|
final Map<String, String> params = new HashMap<>();
|
||||||
|
params.put(ApiConstants.ID, String.valueOf(vm.getId()));
|
||||||
|
params.put("ctxUserId", "1");
|
||||||
|
params.put("ctxAccountId", String.valueOf(vm.getAccountId()));
|
||||||
|
params.put(ApiConstants.CTX_START_EVENT_ID, String.valueOf(eventId));
|
||||||
|
params.put(ApiConstants.FORCED, String.valueOf(isForced));
|
||||||
|
|
||||||
|
final RebootVMCmd cmd = new RebootVMCmd();
|
||||||
|
ComponentContext.inject(cmd);
|
||||||
|
|
||||||
|
AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, vm.getAccountId(), RebootVMCmd.class.getName(), ApiGsonHelper.getBuilder().create().toJson(params), vm.getId(), cmd.getApiResourceType() != null ? cmd.getApiResourceType().toString() : null, null);
|
||||||
|
job.setDispatcher(asyncJobDispatcher.getName());
|
||||||
|
|
||||||
|
return asyncJobManager.submitAsyncJob(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -347,4 +347,9 @@
|
|||||||
|
|
||||||
<bean id="resourceIconManager" class="com.cloud.resourceicon.ResourceIconManagerImpl" />
|
<bean id="resourceIconManager" class="com.cloud.resourceicon.ResourceIconManagerImpl" />
|
||||||
|
|
||||||
|
<bean id="VMScheduleManagerImpl" class="org.apache.cloudstack.vm.schedule.VMScheduleManagerImpl" />
|
||||||
|
<bean id="VMSchedulerImpl" class="org.apache.cloudstack.vm.schedule.VMSchedulerImpl">
|
||||||
|
<property name="asyncJobDispatcher" ref="ApiAsyncJobDispatcher" />
|
||||||
|
</bean>
|
||||||
|
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@ -0,0 +1,274 @@
|
|||||||
|
/*
|
||||||
|
* 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.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;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
Account callingAccount = Mockito.mock(Account.class);
|
||||||
|
User callingUser = Mockito.mock(User.class);
|
||||||
|
CallContext.register(callingUser, callingAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateResponse(VMScheduleResponse response, VMSchedule vmSchedule, VirtualMachine vm) {
|
||||||
|
assertNotNull(response);
|
||||||
|
Assert.assertEquals(ReflectionTestUtils.getField(response, "id"), vmSchedule.getUuid());
|
||||||
|
Assert.assertEquals(ReflectionTestUtils.getField(response, "vmId"), vm.getUuid());
|
||||||
|
Assert.assertEquals(ReflectionTestUtils.getField(response, "schedule"), vmSchedule.getSchedule());
|
||||||
|
Assert.assertEquals(ReflectionTestUtils.getField(response, "timeZone"), vmSchedule.getTimeZone());
|
||||||
|
Assert.assertEquals(ReflectionTestUtils.getField(response, "action"), vmSchedule.getAction());
|
||||||
|
Assert.assertEquals(ReflectionTestUtils.getField(response, "startDate"), vmSchedule.getStartDate());
|
||||||
|
Assert.assertEquals(ReflectionTestUtils.getField(response, "endDate"), vmSchedule.getEndDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createSchedule() {
|
||||||
|
UserVm vm = Mockito.mock(UserVm.class);
|
||||||
|
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
|
||||||
|
CreateVMScheduleCmd cmd = Mockito.mock(CreateVMScheduleCmd.class);
|
||||||
|
|
||||||
|
Mockito.when(cmd.getVmId()).thenReturn(1L);
|
||||||
|
Mockito.when(cmd.getSchedule()).thenReturn("0 0 * * *");
|
||||||
|
Mockito.when(cmd.getTimeZone()).thenReturn("UTC");
|
||||||
|
Mockito.when(cmd.getAction()).thenReturn("start");
|
||||||
|
Mockito.when(cmd.getStartDate()).thenReturn(DateUtils.addDays(new Date(), 1));
|
||||||
|
Mockito.when(cmd.getEndDate()).thenReturn(DateUtils.addDays(new Date(), 2));
|
||||||
|
Mockito.when(vm.getUuid()).thenReturn(UUID.randomUUID().toString());
|
||||||
|
Mockito.when(vmScheduleDao.persist(Mockito.any(VMScheduleVO.class))).thenReturn(vmSchedule);
|
||||||
|
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
|
||||||
|
Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.eq(false), Mockito.eq(vm));
|
||||||
|
VMScheduleResponse response = vmScheduleManager.createSchedule(cmd);
|
||||||
|
Mockito.verify(vmScheduleDao, Mockito.times(1)).persist(Mockito.any(VMScheduleVO.class));
|
||||||
|
|
||||||
|
validateResponse(response, vmSchedule, vm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createResponse() {
|
||||||
|
VMSchedule vmSchedule = Mockito.mock(VMSchedule.class);
|
||||||
|
UserVm vm = Mockito.mock(UserVm.class);
|
||||||
|
Mockito.when(vmSchedule.getVmId()).thenReturn(1L);
|
||||||
|
Mockito.when(userVmManager.getUserVm(vmSchedule.getVmId())).thenReturn(vm);
|
||||||
|
|
||||||
|
VMScheduleResponse response = vmScheduleManager.createResponse(vmSchedule);
|
||||||
|
validateResponse(response, vmSchedule, vm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listSchedule() {
|
||||||
|
UserVm vm = Mockito.mock(UserVm.class);
|
||||||
|
VMScheduleVO vmSchedule1 = Mockito.mock(VMScheduleVO.class);
|
||||||
|
VMScheduleVO vmSchedule2 = Mockito.mock(VMScheduleVO.class);
|
||||||
|
List<VMScheduleVO> vmScheduleList = new ArrayList<>();
|
||||||
|
vmScheduleList.add(vmSchedule1);
|
||||||
|
vmScheduleList.add(vmSchedule2);
|
||||||
|
|
||||||
|
Mockito.when(vm.getUuid()).thenReturn(UUID.randomUUID().toString());
|
||||||
|
Mockito.when(userVmManager.getUserVm(1L)).thenReturn(vm);
|
||||||
|
Mockito.when(
|
||||||
|
vmScheduleDao.searchAndCount(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(),
|
||||||
|
Mockito.anyBoolean(), Mockito.anyLong(), Mockito.anyLong())
|
||||||
|
).thenReturn(new Pair<>(vmScheduleList, vmScheduleList.size()));
|
||||||
|
Mockito.when(vmSchedule1.getVmId()).thenReturn(1L);
|
||||||
|
Mockito.when(vmSchedule2.getVmId()).thenReturn(1L);
|
||||||
|
|
||||||
|
ListVMScheduleCmd cmd = Mockito.mock(ListVMScheduleCmd.class);
|
||||||
|
Mockito.when(cmd.getVmId()).thenReturn(1L);
|
||||||
|
|
||||||
|
ListResponse<VMScheduleResponse> responseList = vmScheduleManager.listSchedule(cmd);
|
||||||
|
Mockito.verify(vmScheduleDao, Mockito.times(1)).searchAndCount(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(),
|
||||||
|
Mockito.anyBoolean(), Mockito.anyLong(), Mockito.anyLong());
|
||||||
|
assertNotNull(responseList);
|
||||||
|
Assert.assertEquals(2, (int) responseList.getCount());
|
||||||
|
Assert.assertEquals(2, responseList.getResponses().size());
|
||||||
|
|
||||||
|
for (int i = 0; i < responseList.getResponses().size(); i++) {
|
||||||
|
VMScheduleResponse response = responseList.getResponses().get(i);
|
||||||
|
VMScheduleVO vmSchedule = vmScheduleList.get(i);
|
||||||
|
validateResponse(response, vmSchedule, vm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateSchedule() {
|
||||||
|
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
|
||||||
|
UpdateVMScheduleCmd cmd = Mockito.mock(UpdateVMScheduleCmd.class);
|
||||||
|
UserVm vm = Mockito.mock(UserVm.class);
|
||||||
|
Mockito.when(cmd.getId()).thenReturn(1L);
|
||||||
|
Mockito.when(cmd.getSchedule()).thenReturn("0 0 * * *");
|
||||||
|
Mockito.when(cmd.getTimeZone()).thenReturn("UTC");
|
||||||
|
Mockito.when(cmd.getStartDate()).thenReturn(DateUtils.addDays(new Date(), 1));
|
||||||
|
Mockito.when(cmd.getEndDate()).thenReturn(DateUtils.addDays(new Date(), 2));
|
||||||
|
Mockito.when(vmScheduleDao.findById(Mockito.anyLong())).thenReturn(vmSchedule);
|
||||||
|
Mockito.when(vmScheduleDao.update(Mockito.eq(cmd.getId()), Mockito.any(VMScheduleVO.class))).thenReturn(true);
|
||||||
|
Mockito.when(vmSchedule.getVmId()).thenReturn(1L);
|
||||||
|
Mockito.when(vmSchedule.getStartDate()).thenReturn(DateUtils.addDays(new Date(), 1));
|
||||||
|
Mockito.when(userVmManager.getUserVm(vmSchedule.getVmId())).thenReturn(vm);
|
||||||
|
|
||||||
|
VMScheduleResponse response = vmScheduleManager.updateSchedule(cmd);
|
||||||
|
Mockito.verify(vmScheduleDao, Mockito.times(1)).update(Mockito.eq(cmd.getId()), Mockito.any(VMScheduleVO.class));
|
||||||
|
|
||||||
|
validateResponse(response, vmSchedule, vm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeScheduleByVmId() {
|
||||||
|
UserVm vm = Mockito.mock(UserVm.class);
|
||||||
|
VMScheduleVO vmSchedule1 = Mockito.mock(VMScheduleVO.class);
|
||||||
|
VMScheduleVO vmSchedule2 = Mockito.mock(VMScheduleVO.class);
|
||||||
|
List<VMScheduleVO> vmScheduleList = new ArrayList<>();
|
||||||
|
vmScheduleList.add(vmSchedule1);
|
||||||
|
vmScheduleList.add(vmSchedule2);
|
||||||
|
SearchCriteria<VMScheduleVO> sc = Mockito.mock(SearchCriteria.class);
|
||||||
|
|
||||||
|
Mockito.when(vm.getId()).thenReturn(1L);
|
||||||
|
Mockito.when(vmScheduleDao.getSearchCriteriaForVMId(vm.getId())).thenReturn(sc);
|
||||||
|
Mockito.when(vmScheduleDao.search(sc, null)).thenReturn(vmScheduleList);
|
||||||
|
Mockito.when(vmSchedule1.getId()).thenReturn(1L);
|
||||||
|
Mockito.when(vmSchedule2.getId()).thenReturn(2L);
|
||||||
|
Mockito.when(vmScheduleDao.remove(sc)).thenReturn(2);
|
||||||
|
|
||||||
|
long rowsRemoved = vmScheduleManager.removeScheduleByVmId(vm.getId(), false);
|
||||||
|
|
||||||
|
Mockito.verify(vmScheduleDao, Mockito.times(1)).remove(sc);
|
||||||
|
Assert.assertEquals(2, rowsRemoved);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeSchedule() {
|
||||||
|
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
|
||||||
|
UserVm vm = Mockito.mock(UserVm.class);
|
||||||
|
DeleteVMScheduleCmd cmd = Mockito.mock(DeleteVMScheduleCmd.class);
|
||||||
|
|
||||||
|
Mockito.when(cmd.getId()).thenReturn(1L);
|
||||||
|
Mockito.when(cmd.getVmId()).thenReturn(1L);
|
||||||
|
Mockito.when(vmSchedule.getVmId()).thenReturn(1L);
|
||||||
|
Mockito.when(userVmManager.getUserVm(cmd.getVmId())).thenReturn(vm);
|
||||||
|
Mockito.when(vmScheduleDao.findById(Mockito.anyLong())).thenReturn(vmSchedule);
|
||||||
|
Mockito.when(vmScheduleDao.removeSchedulesForVmIdAndIds(Mockito.anyLong(), Mockito.anyList())).thenReturn(1L);
|
||||||
|
|
||||||
|
Long rowsRemoved = vmScheduleManager.removeSchedule(cmd);
|
||||||
|
|
||||||
|
Mockito.verify(vmScheduleDao, Mockito.times(1)).removeSchedulesForVmIdAndIds(Mockito.anyLong(), Mockito.anyList());
|
||||||
|
Assert.assertEquals(1L, (long) rowsRemoved);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateStartDateEndDate() {
|
||||||
|
// Valid scenario 1
|
||||||
|
// Start date is before end date
|
||||||
|
Date startDate = DateUtils.addDays(new Date(), 1);
|
||||||
|
Date endDate = DateUtils.addDays(new Date(), 2);
|
||||||
|
vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
|
||||||
|
|
||||||
|
// Valid Scenario 2
|
||||||
|
// Start date is before current date and end date is null
|
||||||
|
endDate = null;
|
||||||
|
vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
|
||||||
|
|
||||||
|
// Invalid Scenario 2
|
||||||
|
// Start date is before current date
|
||||||
|
startDate = DateUtils.addDays(new Date(), -1);
|
||||||
|
try {
|
||||||
|
vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
|
||||||
|
Assert.fail("Should have thrown InvalidParameterValueException");
|
||||||
|
} catch (InvalidParameterValueException e) {
|
||||||
|
Assert.assertTrue(e.getMessage().contains("Invalid value for start date. Start date") &&
|
||||||
|
e.getMessage().contains("can't be before current time"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid Scenario 2
|
||||||
|
// Start date is after end date
|
||||||
|
startDate = DateUtils.addDays(new Date(), 2);
|
||||||
|
endDate = DateUtils.addDays(new Date(), 1);
|
||||||
|
try {
|
||||||
|
vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
|
||||||
|
Assert.fail("Should have thrown InvalidParameterValueException");
|
||||||
|
} catch (InvalidParameterValueException e) {
|
||||||
|
Assert.assertTrue(e.getMessage().contains("Invalid value for end date. End date") &&
|
||||||
|
e.getMessage().contains("can't be before start date"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid Scenario 3
|
||||||
|
// End date is before current date
|
||||||
|
startDate = DateUtils.addDays(new Date(), 1);
|
||||||
|
endDate = DateUtils.addDays(new Date(), -1);
|
||||||
|
try {
|
||||||
|
vmScheduleManager.validateStartDateEndDate(startDate, endDate, TimeZone.getDefault());
|
||||||
|
Assert.fail("Should have thrown InvalidParameterValueException");
|
||||||
|
} catch (InvalidParameterValueException e) {
|
||||||
|
Assert.assertTrue(e.getMessage().contains("Invalid value for end date. End date") &&
|
||||||
|
e.getMessage().contains("can't be before current time"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,397 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.Mockito;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.mockito.Spy;
|
||||||
|
import org.powermock.api.mockito.PowerMockito;
|
||||||
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||||
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
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(PowerMockRunner.class)
|
||||||
|
@PrepareForTest({ActionEventUtils.class, ComponentContext.class})
|
||||||
|
public class VMSchedulerImplTest {
|
||||||
|
@Spy
|
||||||
|
@InjectMocks
|
||||||
|
private VMSchedulerImpl vmScheduler = new VMSchedulerImpl();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private UserVmManager userVmManager;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private VMScheduleDao vmScheduleDao;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private VMScheduledJobDao vmScheduledJobDao;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private EnumMap<VMSchedule.Action, String> actionEventMap;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private AsyncJobManager asyncJobManager;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private AsyncJobDispatcher asyncJobDispatcher;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
PowerMockito.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
PowerMockito.verifyStatic(ActionEventUtils.class);
|
||||||
|
ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), null,
|
||||||
|
actionEventMap.get(action), true,
|
||||||
|
String.format("Executing action (%s) for VM Id:%s", vmScheduledJob.getAction(), vm.getUuid()),
|
||||||
|
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), 0);
|
||||||
|
Assert.assertEquals(expectedValue, jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 = DateUtils.addDays(expectedScheduledTime, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule);
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
Assert.assertNull(actualDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScheduleNextJobScheduleDisabled() {
|
||||||
|
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
|
||||||
|
Mockito.when(vmSchedule.getEnabled()).thenReturn(false);
|
||||||
|
Date actualDate = vmScheduler.scheduleNextJob(vmSchedule);
|
||||||
|
Assert.assertNull(actualDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScheduleNextJobScheduleIdNotExists() {
|
||||||
|
long vmScheduleId = 1;
|
||||||
|
Mockito.when(vmScheduleDao.findById(vmScheduleId)).thenReturn(null);
|
||||||
|
Date actualDate = vmScheduler.scheduleNextJob(vmScheduleId);
|
||||||
|
Assert.assertNull(actualDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScheduleNextJobScheduleIdExists() {
|
||||||
|
long vmScheduleId = 1;
|
||||||
|
VMScheduleVO vmScheduleVO = Mockito.mock(VMScheduleVO.class);
|
||||||
|
Date date = Mockito.mock(Date.class);
|
||||||
|
Mockito.when(vmScheduleDao.findById(vmScheduleId)).thenReturn(vmScheduleVO);
|
||||||
|
Mockito.doReturn(date).when(vmScheduler).scheduleNextJob(vmScheduleVO);
|
||||||
|
Date actualDate = vmScheduler.scheduleNextJob(vmScheduleId);
|
||||||
|
Assert.assertEquals(date, 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(job1.getVmId()).thenReturn(1L);
|
||||||
|
Mockito.when(job1.getScheduledTime()).thenReturn(new Date());
|
||||||
|
Mockito.when(job1.getAction()).thenReturn(VMSchedule.Action.START);
|
||||||
|
Mockito.when(job1.getVmScheduleId()).thenReturn(1L);
|
||||||
|
Mockito.when(job2.getVmId()).thenReturn(2L);
|
||||||
|
Mockito.when(job2.getScheduledTime()).thenReturn(new Date());
|
||||||
|
Mockito.when(job2.getAction()).thenReturn(VMSchedule.Action.STOP);
|
||||||
|
Mockito.when(job2.getVmScheduleId()).thenReturn(2L);
|
||||||
|
|
||||||
|
Mockito.when(userVmManager.getUserVm(1L)).thenReturn(vm1);
|
||||||
|
Mockito.when(userVmManager.getUserVm(2L)).thenReturn(vm2);
|
||||||
|
|
||||||
|
Mockito.doReturn(1L).when(vmScheduler).processJob(job1, vm1);
|
||||||
|
Mockito.doReturn(null).when(vmScheduler).processJob(job2, vm2);
|
||||||
|
|
||||||
|
Mockito.when(vmScheduledJobDao.acquireInLockTable(job1.getId())).thenReturn(job1);
|
||||||
|
Mockito.when(vmScheduledJobDao.acquireInLockTable(job2.getId())).thenReturn(job2);
|
||||||
|
|
||||||
|
Map<Long, VMScheduledJob> jobs = new HashMap<>();
|
||||||
|
jobs.put(1L, job1);
|
||||||
|
jobs.put(2L, job2);
|
||||||
|
|
||||||
|
ReflectionTestUtils.setField(vmScheduler, "currentTimestamp", new Date());
|
||||||
|
|
||||||
|
vmScheduler.executeJobs(jobs);
|
||||||
|
|
||||||
|
Mockito.verify(vmScheduler, Mockito.times(2)).processJob(Mockito.any(), Mockito.any());
|
||||||
|
Mockito.verify(vmScheduledJobDao, Mockito.times(2)).acquireInLockTable(Mockito.anyLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteStopVMJob() {
|
||||||
|
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
|
||||||
|
Mockito.when(asyncJobManager.submitAsyncJob(Mockito.any(AsyncJobVO.class))).thenReturn(1L);
|
||||||
|
PowerMockito.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);
|
||||||
|
PowerMockito.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);
|
||||||
|
PowerMockito.mockStatic(ComponentContext.class);
|
||||||
|
when(ComponentContext.inject(StartVMCmd.class)).thenReturn(Mockito.mock(StartVMCmd.class));
|
||||||
|
long jobId = vmScheduler.executeStartVMJob(vm, 1L);
|
||||||
|
|
||||||
|
Assert.assertEquals(1L, jobId);
|
||||||
|
}
|
||||||
|
}
|
||||||
606
test/integration/smoke/test_vm_schedule.py
Normal file
606
test/integration/smoke/test_vm_schedule.py
Normal file
@ -0,0 +1,606 @@
|
|||||||
|
# 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.
|
||||||
|
""" P1 tests for VM Schedule
|
||||||
|
"""
|
||||||
|
from marvin.cloudstackTestCase import cloudstackTestCase
|
||||||
|
from marvin.lib.base import Account, ServiceOffering, VirtualMachine, VMSchedule
|
||||||
|
from marvin.lib.common import get_domain, get_zone, get_template
|
||||||
|
from marvin.lib.utils import cleanup_resources
|
||||||
|
|
||||||
|
# Import Local Modules
|
||||||
|
from nose.plugins.attrib import attr
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class Services:
|
||||||
|
"""Test Snapshots Services"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.services = {
|
||||||
|
"account": {
|
||||||
|
"email": "test@test.com",
|
||||||
|
"firstname": "Test",
|
||||||
|
"lastname": "User",
|
||||||
|
"username": "test",
|
||||||
|
# Random characters are appended for unique
|
||||||
|
# username
|
||||||
|
"password": "password",
|
||||||
|
},
|
||||||
|
"service_offering": {
|
||||||
|
"name": "Tiny Instance",
|
||||||
|
"displaytext": "Tiny Instance",
|
||||||
|
"cpunumber": 1,
|
||||||
|
"cpuspeed": 200, # in MHz
|
||||||
|
"memory": 256, # In MBs
|
||||||
|
},
|
||||||
|
"disk_offering": {
|
||||||
|
"displaytext": "Small Disk",
|
||||||
|
"name": "Small Disk",
|
||||||
|
"disksize": 1,
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"displayname": "TestVM",
|
||||||
|
"username": "root",
|
||||||
|
"password": "password",
|
||||||
|
"ssh_port": 22,
|
||||||
|
"privateport": 22,
|
||||||
|
"publicport": 22,
|
||||||
|
"protocol": "TCP",
|
||||||
|
},
|
||||||
|
"mgmt_server": {
|
||||||
|
"ipaddress": "192.168.100.21",
|
||||||
|
"username": "root",
|
||||||
|
"password": "password",
|
||||||
|
"port": 22,
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"displaytext": "Template",
|
||||||
|
"name": "Template",
|
||||||
|
"ostype": "CentOS 5.3 (64-bit)",
|
||||||
|
"templatefilter": "self",
|
||||||
|
},
|
||||||
|
"ostype": "CentOS 5.3 (64-bit)",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestVMSchedule(cloudstackTestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.testClient = super(TestVMSchedule, cls).getClsTestClient()
|
||||||
|
cls.api_client = cls.testClient.getApiClient()
|
||||||
|
|
||||||
|
cls._cleanup = []
|
||||||
|
|
||||||
|
cls.hypervisor = cls.testClient.getHypervisorInfo()
|
||||||
|
|
||||||
|
cls.services = Services().services
|
||||||
|
# Get Zone, Domain and templates
|
||||||
|
cls.domain = get_domain(cls.api_client)
|
||||||
|
cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests())
|
||||||
|
cls.services["mode"] = cls.zone.networktype
|
||||||
|
template = get_template(cls.api_client, cls.zone.id, cls.services["ostype"])
|
||||||
|
|
||||||
|
cls.services["domainid"] = cls.domain.id
|
||||||
|
cls.services["server"]["zoneid"] = cls.zone.id
|
||||||
|
|
||||||
|
cls.services["templates"]["ostypeid"] = template.ostypeid
|
||||||
|
cls.services["zoneid"] = cls.zone.id
|
||||||
|
|
||||||
|
# Create VMs, NAT Rules etc
|
||||||
|
cls.account = Account.create(
|
||||||
|
cls.api_client, cls.services["account"], domainid=cls.domain.id
|
||||||
|
)
|
||||||
|
cls._cleanup.append(cls.account)
|
||||||
|
|
||||||
|
cls.services["account"] = cls.account.name
|
||||||
|
|
||||||
|
cls.service_offering = ServiceOffering.create(
|
||||||
|
cls.api_client, cls.services["service_offering"]
|
||||||
|
)
|
||||||
|
cls._cleanup.append(cls.service_offering)
|
||||||
|
cls.virtual_machine = VirtualMachine.create(
|
||||||
|
cls.api_client,
|
||||||
|
cls.services["server"],
|
||||||
|
templateid=template.id,
|
||||||
|
accountid=cls.account.name,
|
||||||
|
domainid=cls.account.domainid,
|
||||||
|
serviceofferingid=cls.service_offering.id,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
super(TestVMSchedule, cls).tearDownClass()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.apiclient = self.testClient.getApiClient()
|
||||||
|
self.dbclient = self.testClient.getDbConnection()
|
||||||
|
self.cleanup = []
|
||||||
|
return
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(TestVMSchedule, self).tearDown()
|
||||||
|
|
||||||
|
@attr(tags=["advanced", "basic"], required_hardware="false")
|
||||||
|
def test_01_vmschedule_create(self):
|
||||||
|
"""Test VM Schedule Creation in cron format and validate responses"""
|
||||||
|
|
||||||
|
# Validate the following
|
||||||
|
# 1. Create VM Schedule in cron format
|
||||||
|
# 2. List VM Schedule and verify the response
|
||||||
|
# 3. Delete VM Schedule and verify the response
|
||||||
|
|
||||||
|
# Create VM Schedule
|
||||||
|
schedule = "0 0 1 * *"
|
||||||
|
vmschedule = VMSchedule.create(
|
||||||
|
self.apiclient,
|
||||||
|
self.virtual_machine.id,
|
||||||
|
"start",
|
||||||
|
schedule,
|
||||||
|
datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
# Current date minutes in format "2014-01-01 00:00:00"
|
||||||
|
(datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
),
|
||||||
|
enabled=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cleanup.append(vmschedule)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
isinstance(vmschedules, list),
|
||||||
|
True,
|
||||||
|
"Check list response returns a valid list",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertNotEqual(len(vmschedules), 0, "Check VM Schedule list")
|
||||||
|
|
||||||
|
self.debug("List VM Schedule response: %s" % vmschedules[0].__dict__)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
vmschedules[0].id,
|
||||||
|
vmschedule.id,
|
||||||
|
"Check VM Schedule ID in list resources call",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
vmschedules[0].virtualmachineid,
|
||||||
|
self.virtual_machine.id,
|
||||||
|
"Check VM ID in list resources call",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
vmschedules[0].schedule,
|
||||||
|
schedule,
|
||||||
|
"Check VM Schedule in list resources call",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
vmschedules[0].timezone,
|
||||||
|
str(datetime.datetime.now().astimezone().tzinfo),
|
||||||
|
"Check VM Schedule timezone in list resources call",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check for entry in vm_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')"
|
||||||
|
% vmschedule.id,
|
||||||
|
db="cloud",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsInstance(
|
||||||
|
vmscheduled_job,
|
||||||
|
list,
|
||||||
|
"Check if VM Schedule exists in vm_scheduled_job table",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertGreater(
|
||||||
|
len(vmscheduled_job),
|
||||||
|
0,
|
||||||
|
"Check if VM Schedule exists in vm_scheduled_job table",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
@attr(tags=["advanced", "basic"], required_hardware="false")
|
||||||
|
def test_02_vmschedule_create_parameter_exceptions(self):
|
||||||
|
"""Test VM Schedule Creation exceptions with invalid parameters"""
|
||||||
|
|
||||||
|
# Validate the following
|
||||||
|
# 1. Create VM Schedule with invalid virtual machine ID
|
||||||
|
# 2. Create VM Schedule with invalid schedule
|
||||||
|
# 3. Create VM Schedule with invalid start date
|
||||||
|
# 5. Create VM Schedule with invalid action
|
||||||
|
# 6. Create VM Schedule with invalid end date
|
||||||
|
|
||||||
|
# Create VM Schedule with invalid virtual machine ID
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
VMSchedule.create(
|
||||||
|
self.apiclient,
|
||||||
|
"invalid",
|
||||||
|
"start",
|
||||||
|
"0 0 1 * *",
|
||||||
|
datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
# Current date minutes in format "2014-01-01 00:00:00"
|
||||||
|
(datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create VM Schedule with invalid schedule
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
VMSchedule.create(
|
||||||
|
self.apiclient,
|
||||||
|
self.virtual_machine.id,
|
||||||
|
"start",
|
||||||
|
"invalid",
|
||||||
|
datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
# Current date minutes in format "2014-01-01 00:00:00"
|
||||||
|
(datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create VM Schedule with invalid start date
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
VMSchedule.create(
|
||||||
|
self.apiclient,
|
||||||
|
self.virtual_machine.id,
|
||||||
|
"start",
|
||||||
|
"0 0 1 * *",
|
||||||
|
datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
# Current date minutes in format "2014-01-01 00:00:00"
|
||||||
|
"invalid",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create VM Schedule with invalid action
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
VMSchedule.create(
|
||||||
|
self.apiclient,
|
||||||
|
self.virtual_machine.id,
|
||||||
|
"invalid",
|
||||||
|
"0 0 1 * *",
|
||||||
|
datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
# Current date minutes in format "2014-01-01 00:00:00"
|
||||||
|
(datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# test invalid end date
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
VMSchedule.create(
|
||||||
|
self.apiclient,
|
||||||
|
self.virtual_machine.id,
|
||||||
|
"start",
|
||||||
|
"0 0 1 * *",
|
||||||
|
datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
# Current date minutes in format "2014-01-01 00:00:00"
|
||||||
|
(datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
),
|
||||||
|
enddate="invalid",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
@attr(tags=["advanced", "basic"], required_hardware="false")
|
||||||
|
def test_03_vmschedule_update(self):
|
||||||
|
"""Test VM Schedule Update in cron format and validate responses"""
|
||||||
|
|
||||||
|
# Validate the following
|
||||||
|
# 1. Create VM Schedule in cron format
|
||||||
|
# 2. Update VM Schedule and verify the response
|
||||||
|
|
||||||
|
# Create VM Schedule
|
||||||
|
schedule = "0 0 1 * *"
|
||||||
|
vmschedule = VMSchedule.create(
|
||||||
|
self.apiclient,
|
||||||
|
self.virtual_machine.id,
|
||||||
|
"start",
|
||||||
|
schedule,
|
||||||
|
datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
# Current date minutes in format "2014-01-01 00:00:00"
|
||||||
|
(datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cleanup.append(vmschedule)
|
||||||
|
|
||||||
|
self.debug("Created VM Schedule with ID: %s" % vmschedule.id)
|
||||||
|
|
||||||
|
# Update VM Schedule
|
||||||
|
new_schedule = "0 0 2 * *"
|
||||||
|
vmschedule.update(
|
||||||
|
self.apiclient,
|
||||||
|
id=vmschedule.id,
|
||||||
|
virtualmachineid=self.virtual_machine.id,
|
||||||
|
description="TestVM",
|
||||||
|
schedule=new_schedule,
|
||||||
|
timezone=datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
startdate=(
|
||||||
|
datetime.datetime.now() + datetime.timedelta(minutes=10)
|
||||||
|
).strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
enddate=(datetime.datetime.now() + datetime.timedelta(hours=10)).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
isinstance(vmschedules, list),
|
||||||
|
True,
|
||||||
|
"Check list response returns a valid list",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertNotEqual(len(vmschedules), 0, "Check VM Schedule list")
|
||||||
|
|
||||||
|
self.debug("List VM Schedule response: %s" % vmschedules[0].__dict__)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
vmschedules[0].id,
|
||||||
|
vmschedule.id,
|
||||||
|
"Check VM Schedule ID in list resources call",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
vmschedules[0].virtualmachineid,
|
||||||
|
self.virtual_machine.id,
|
||||||
|
"Check VM ID in list resources call",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
vmschedules[0].schedule,
|
||||||
|
new_schedule,
|
||||||
|
"Check VM Schedule in list resources call",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
@attr(tags=["advanced", "basic"], required_hardware="false")
|
||||||
|
def test_04_vmschedule_update_parameter_exceptions(self):
|
||||||
|
"""Test VM Schedule Update exceptions with invalid parameters"""
|
||||||
|
|
||||||
|
# Validate the following
|
||||||
|
# 1. Update VM Schedule with invalid schedule
|
||||||
|
# 2. Update VM Schedule with invalid start date
|
||||||
|
# 3. Update VM Schedule with invalid ID
|
||||||
|
# 4. Update VM Schedule with invalid end date
|
||||||
|
|
||||||
|
# Create VM Schedule
|
||||||
|
schedule = "0 0 1 * *"
|
||||||
|
vmschedule = VMSchedule.create(
|
||||||
|
self.apiclient,
|
||||||
|
self.virtual_machine.id,
|
||||||
|
"start",
|
||||||
|
schedule,
|
||||||
|
datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
# Current date minutes in format "2014-01-01 00:00:00"
|
||||||
|
(datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cleanup.append(vmschedule)
|
||||||
|
|
||||||
|
self.debug("Created VM Schedule with ID: %s" % vmschedule.id)
|
||||||
|
|
||||||
|
# Update VM Schedule with invalid schedule
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
vmschedule.update(
|
||||||
|
self.apiclient,
|
||||||
|
id=vmschedule.id,
|
||||||
|
virtualmachineid=self.virtual_machine.id,
|
||||||
|
description="TestVM",
|
||||||
|
schedule="invalid",
|
||||||
|
timezone=datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
startdate=(
|
||||||
|
datetime.datetime.now() + datetime.timedelta(minutes=5)
|
||||||
|
).strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update VM Schedule with invalid start date
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
vmschedule.update(
|
||||||
|
self.apiclient,
|
||||||
|
id=vmschedule.id,
|
||||||
|
virtualmachineid=self.virtual_machine.id,
|
||||||
|
description="TestVM",
|
||||||
|
schedule=schedule,
|
||||||
|
timezone=datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
startdate=(
|
||||||
|
datetime.datetime.now() - datetime.timedelta(days=1)
|
||||||
|
).strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update VM Schedule with invalid ID
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
vmschedule.update(
|
||||||
|
self.apiclient,
|
||||||
|
id="invalid",
|
||||||
|
virtualmachineid=self.virtual_machine.id,
|
||||||
|
description="TestVM",
|
||||||
|
schedule=schedule,
|
||||||
|
timezone=datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
startdate=(
|
||||||
|
datetime.datetime.now() + datetime.timedelta(minutes=5)
|
||||||
|
).strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update VM Schedule with invalid end date
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
vmschedule.update(
|
||||||
|
self.apiclient,
|
||||||
|
id=vmschedule.id,
|
||||||
|
virtualmachineid=self.virtual_machine.id,
|
||||||
|
description="TestVM",
|
||||||
|
schedule=schedule,
|
||||||
|
timezone=datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
startdate=(
|
||||||
|
datetime.datetime.now() + datetime.timedelta(minutes=5)
|
||||||
|
).strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
enddate=(
|
||||||
|
datetime.datetime.now() - datetime.timedelta(minutes=5)
|
||||||
|
).strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
@attr(tags=["advanced", "basic"], required_hardware="false")
|
||||||
|
def test_05_vmschedule_test_e2e(self):
|
||||||
|
# Validate the following
|
||||||
|
# 1. Create 2 VM Schedules - start and stop
|
||||||
|
# 2. Verify VM Schedule is created
|
||||||
|
# 3. Verify VM is stopped after schedule time
|
||||||
|
# 4. Verify VM is started after schedule time
|
||||||
|
# 5. Delete VM Schedule
|
||||||
|
# 6. Verify VM Schedule is deleted
|
||||||
|
# 7. Verify VM is not stopped after schedule time
|
||||||
|
# 8. Verify VM is not started after schedule time
|
||||||
|
|
||||||
|
# Create VM Schedule - start
|
||||||
|
start_schedule = "*/2 * * * *"
|
||||||
|
start_vmschedule = VMSchedule.create(
|
||||||
|
self.apiclient,
|
||||||
|
self.virtual_machine.id,
|
||||||
|
"start",
|
||||||
|
start_schedule,
|
||||||
|
datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
# Current date minutes in format "2014-01-01 00:00:00"
|
||||||
|
(datetime.datetime.now() + datetime.timedelta(seconds=5)).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
),
|
||||||
|
enabled=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.debug("Created VM Schedule with ID: %s" % start_vmschedule.id)
|
||||||
|
|
||||||
|
# Create VM Schedule - stop
|
||||||
|
stop_schedule = "*/1 * * * *"
|
||||||
|
stop_vmschedule = VMSchedule.create(
|
||||||
|
self.apiclient,
|
||||||
|
self.virtual_machine.id,
|
||||||
|
"stop",
|
||||||
|
stop_schedule,
|
||||||
|
datetime.datetime.now().astimezone().tzinfo,
|
||||||
|
# Current date minutes in format "2014-01-01 00:00:00"
|
||||||
|
(datetime.datetime.now() + datetime.timedelta(seconds=5)).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
),
|
||||||
|
enabled=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
isinstance(vmschedules, list),
|
||||||
|
True,
|
||||||
|
"Check list response returns a valid list",
|
||||||
|
)
|
||||||
|
self.assertNotEqual(len(vmschedules), 0, "Check VM Schedule is created")
|
||||||
|
|
||||||
|
# poll every 20 seconds (max waiting time is 4 minutes ) and check VM's state for changes
|
||||||
|
previous_state = self.virtual_machine.state
|
||||||
|
self.debug("VM state: %s" % self.virtual_machine.state)
|
||||||
|
is_stop_schedule_working = False
|
||||||
|
is_start_schedule_working = False
|
||||||
|
for i in range(0, 12):
|
||||||
|
time.sleep(20)
|
||||||
|
current_state = self.virtual_machine.update(self.apiclient).state
|
||||||
|
self.debug("Polling VM state: %s" % current_state)
|
||||||
|
if previous_state in ("Running", "Starting") and current_state in (
|
||||||
|
"Stopped",
|
||||||
|
"Stopping",
|
||||||
|
):
|
||||||
|
is_stop_schedule_working = True
|
||||||
|
elif previous_state in ("Stopped", "Stopping") and current_state in (
|
||||||
|
"Running",
|
||||||
|
"Starting",
|
||||||
|
):
|
||||||
|
is_start_schedule_working = True
|
||||||
|
if is_start_schedule_working and is_stop_schedule_working:
|
||||||
|
break
|
||||||
|
previous_state = current_state
|
||||||
|
|
||||||
|
self.debug("Is stop schedule working: %s" % is_stop_schedule_working)
|
||||||
|
self.debug("Is start schedule working: %s" % is_start_schedule_working)
|
||||||
|
|
||||||
|
self.assertTrue(
|
||||||
|
is_stop_schedule_working,
|
||||||
|
"VM switched states from Running to Stopped at least once",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(
|
||||||
|
is_start_schedule_working,
|
||||||
|
"VM switched states from Stopped to Running at least once",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Delete VM Schedule
|
||||||
|
start_vmschedule.delete(self.apiclient)
|
||||||
|
stop_vmschedule.delete(self.apiclient)
|
||||||
|
|
||||||
|
# Verify VM Schedule is deleted
|
||||||
|
self.assertEqual(
|
||||||
|
VMSchedule.list(
|
||||||
|
self.apiclient, 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
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
"Check VM Schedule is deleted",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify VM does not switch states after deleting schedules at least for 1.5 minutes
|
||||||
|
previous_state = self.virtual_machine.update(self.apiclient).state
|
||||||
|
state_changed = False
|
||||||
|
for i in range(0, 3):
|
||||||
|
time.sleep(30)
|
||||||
|
current_state = self.virtual_machine.update(self.apiclient).state
|
||||||
|
if previous_state != current_state:
|
||||||
|
self.debug(
|
||||||
|
"VM changed state from %s to %s" % (previous_state, current_state)
|
||||||
|
)
|
||||||
|
state_changed = True
|
||||||
|
break
|
||||||
|
|
||||||
|
self.assertFalse(
|
||||||
|
state_changed,
|
||||||
|
"VM did not switch states after schedule time",
|
||||||
|
)
|
||||||
|
return
|
||||||
@ -6521,3 +6521,43 @@ class PolicyRule:
|
|||||||
cmd.policyuuid = policyuuid
|
cmd.policyuuid = policyuuid
|
||||||
cmd.ruleuuid = ruleuuid
|
cmd.ruleuuid = ruleuuid
|
||||||
return apiclient.removeTungstenFabricPolicyRule(cmd)
|
return apiclient.removeTungstenFabricPolicyRule(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
class VMSchedule:
|
||||||
|
|
||||||
|
def __init__(self, items):
|
||||||
|
self.__dict__.update(items)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, apiclient, virtualmachineid, action, schedule, timezone, startdate, enabled=False, description=None, enddate=None):
|
||||||
|
cmd = createVMSchedule.createVMScheduleCmd()
|
||||||
|
cmd.virtualmachineid = virtualmachineid
|
||||||
|
cmd.description = description
|
||||||
|
cmd.action = action
|
||||||
|
cmd.schedule = schedule
|
||||||
|
cmd.timezone = timezone
|
||||||
|
cmd.startdate = startdate
|
||||||
|
cmd.enddate = enddate
|
||||||
|
cmd.enabled = enabled
|
||||||
|
return VMSchedule(apiclient.createVMSchedule(cmd).__dict__)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list(cls, apiclient, virtualmachineid, id=None, enabled=None, action=None):
|
||||||
|
cmd = listVMSchedule.listVMScheduleCmd()
|
||||||
|
cmd.virtualmachineid = virtualmachineid
|
||||||
|
cmd.id = id
|
||||||
|
cmd.enabled = enabled
|
||||||
|
cmd.action = action
|
||||||
|
return apiclient.listVMSchedule(cmd)
|
||||||
|
|
||||||
|
def update(self, apiclient, **kwargs):
|
||||||
|
cmd = updateVMSchedule.updateVMScheduleCmd()
|
||||||
|
cmd.id = self.id
|
||||||
|
[setattr(cmd, k, v) for k, v in list(kwargs.items())]
|
||||||
|
return apiclient.updateVMSchedule(cmd)
|
||||||
|
|
||||||
|
def delete(self, apiclient):
|
||||||
|
cmd = deleteVMSchedule.deleteVMScheduleCmd()
|
||||||
|
cmd.id = self.id
|
||||||
|
cmd.virtualmachineid = self.virtualmachineid
|
||||||
|
return (apiclient.deleteVMSchedule(cmd))
|
||||||
|
|||||||
@ -42,7 +42,7 @@ module.exports = {
|
|||||||
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
|
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
|
||||||
],
|
],
|
||||||
transformIgnorePatterns: [
|
transformIgnorePatterns: [
|
||||||
'<rootDir>/node_modules/(?!ant-design-vue|vue|@babel/runtime|lodash-es|@ant-design)'
|
'<rootDir>/node_modules/(?!ant-design-vue|vue|@babel/runtime|lodash-es|@ant-design|@vue-js-cron)'
|
||||||
],
|
],
|
||||||
collectCoverage: true,
|
collectCoverage: true,
|
||||||
collectCoverageFrom: [
|
collectCoverageFrom: [
|
||||||
|
|||||||
192
ui/package-lock.json
generated
192
ui/package-lock.json
generated
@ -1604,67 +1604,6 @@
|
|||||||
"postcss": "^7.0.0"
|
"postcss": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@intlify/core-base": {
|
|
||||||
"version": "9.1.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.1.10.tgz",
|
|
||||||
"integrity": "sha512-So9CNUavB/IsZ+zBmk2Cv6McQp6vc2wbGi1S0XQmJ8Vz+UFcNn9MFXAe9gY67PreIHrbLsLxDD0cwo1qsxM1Nw==",
|
|
||||||
"requires": {
|
|
||||||
"@intlify/devtools-if": "9.1.10",
|
|
||||||
"@intlify/message-compiler": "9.1.10",
|
|
||||||
"@intlify/message-resolver": "9.1.10",
|
|
||||||
"@intlify/runtime": "9.1.10",
|
|
||||||
"@intlify/shared": "9.1.10",
|
|
||||||
"@intlify/vue-devtools": "9.1.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@intlify/devtools-if": {
|
|
||||||
"version": "9.1.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.1.10.tgz",
|
|
||||||
"integrity": "sha512-SHaKoYu6sog3+Q8js1y3oXLywuogbH1sKuc7NSYkN3GElvXSBaMoCzW+we0ZSFqj/6c7vTNLg9nQ6rxhKqYwnQ==",
|
|
||||||
"requires": {
|
|
||||||
"@intlify/shared": "9.1.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@intlify/message-compiler": {
|
|
||||||
"version": "9.1.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.1.10.tgz",
|
|
||||||
"integrity": "sha512-+JiJpXff/XTb0EadYwdxOyRTB0hXNd4n1HaJ/a4yuV960uRmPXaklJsedW0LNdcptd/hYUZtCkI7Lc9J5C1gxg==",
|
|
||||||
"requires": {
|
|
||||||
"@intlify/message-resolver": "9.1.10",
|
|
||||||
"@intlify/shared": "9.1.10",
|
|
||||||
"source-map": "0.6.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@intlify/message-resolver": {
|
|
||||||
"version": "9.1.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.1.10.tgz",
|
|
||||||
"integrity": "sha512-5YixMG/M05m0cn9+gOzd4EZQTFRUu8RGhzxJbR1DWN21x/Z3bJ8QpDYj6hC4FwBj5uKsRfKpJQ3Xqg98KWoA+w=="
|
|
||||||
},
|
|
||||||
"@intlify/runtime": {
|
|
||||||
"version": "9.1.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.1.10.tgz",
|
|
||||||
"integrity": "sha512-7QsuByNzpe3Gfmhwq6hzgXcMPpxz8Zxb/XFI6s9lQdPLPe5Lgw4U1ovRPZTOs6Y2hwitR3j/HD8BJNGWpJnOFA==",
|
|
||||||
"requires": {
|
|
||||||
"@intlify/message-compiler": "9.1.10",
|
|
||||||
"@intlify/message-resolver": "9.1.10",
|
|
||||||
"@intlify/shared": "9.1.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@intlify/shared": {
|
|
||||||
"version": "9.1.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.1.10.tgz",
|
|
||||||
"integrity": "sha512-Om54xJeo1Vw+K1+wHYyXngE8cAbrxZHpWjYzMR9wCkqbhGtRV5VLhVc214Ze2YatPrWlS2WSMOWXR8JktX/IgA=="
|
|
||||||
},
|
|
||||||
"@intlify/vue-devtools": {
|
|
||||||
"version": "9.1.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.1.10.tgz",
|
|
||||||
"integrity": "sha512-5l3qYARVbkWAkagLu1XbDUWRJSL8br1Dj60wgMaKB0+HswVsrR6LloYZTg7ozyvM621V6+zsmwzbQxbVQyrytQ==",
|
|
||||||
"requires": {
|
|
||||||
"@intlify/message-resolver": "9.1.10",
|
|
||||||
"@intlify/runtime": "9.1.10",
|
|
||||||
"@intlify/shared": "9.1.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@istanbuljs/load-nyc-config": {
|
"@istanbuljs/load-nyc-config": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
||||||
@ -2962,6 +2901,49 @@
|
|||||||
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
|
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@vue-js-cron/ant": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue-js-cron/ant/-/ant-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-E7PbZX/Fwb4w4GYYllnhDx8ywTce3OQ3WS20BPS8EbPlmShmkdvtU9dyNC+C+bGyzXfUwPIdLbtQ1vKzLrMm0A==",
|
||||||
|
"requires": {
|
||||||
|
"@vue-js-cron/core": "3.7.1",
|
||||||
|
"ant-design-vue": "^3.2.12"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ant-design-vue": {
|
||||||
|
"version": "3.2.20",
|
||||||
|
"resolved": "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-3.2.20.tgz",
|
||||||
|
"integrity": "sha512-YWpMfGaGoRastIXEYfCoJiaRiDHk4chqtYhlKQM5GqPt6NfvrM1Vg2e60yHtjxlZjed91wCMm0rAmyUr7Hwzdg==",
|
||||||
|
"requires": {
|
||||||
|
"@ant-design/colors": "^6.0.0",
|
||||||
|
"@ant-design/icons-vue": "^6.1.0",
|
||||||
|
"@babel/runtime": "^7.10.5",
|
||||||
|
"@ctrl/tinycolor": "^3.4.0",
|
||||||
|
"@simonwep/pickr": "~1.8.0",
|
||||||
|
"array-tree-filter": "^2.1.0",
|
||||||
|
"async-validator": "^4.0.0",
|
||||||
|
"dayjs": "^1.10.5",
|
||||||
|
"dom-align": "^1.12.1",
|
||||||
|
"dom-scroll-into-view": "^2.0.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"lodash-es": "^4.17.15",
|
||||||
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
|
"scroll-into-view-if-needed": "^2.2.25",
|
||||||
|
"shallow-equal": "^1.0.0",
|
||||||
|
"vue-types": "^3.0.0",
|
||||||
|
"warning": "^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@vue-js-cron/core": {
|
||||||
|
"version": "3.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue-js-cron/core/-/core-3.7.1.tgz",
|
||||||
|
"integrity": "sha512-aWCkbfCbwpEJwmptY0tRFlxH4ZaVtnmR2Q3f0L8SzSC58cn45VYMe2/eSK53221cBmk/w+18Hq563u+W7Jp9Eg==",
|
||||||
|
"requires": {
|
||||||
|
"mustache": "^4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@vue/babel-helper-vue-jsx-merge-props": {
|
"@vue/babel-helper-vue-jsx-merge-props": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz",
|
||||||
@ -8083,6 +8065,11 @@
|
|||||||
"sha.js": "^2.4.8"
|
"sha.js": "^2.4.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cronstrue": {
|
||||||
|
"version": "2.26.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-2.26.0.tgz",
|
||||||
|
"integrity": "sha512-M1VdV3hpBAsd1Zzvqcvf63wgDpcwCuS4WiNEVFpJ0s33MGO2sVDTfswYq0EPypCmESrCzmgL8h68DTzJuSDbVA=="
|
||||||
|
},
|
||||||
"cross-spawn": {
|
"cross-spawn": {
|
||||||
"version": "6.0.5",
|
"version": "6.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||||
@ -16088,6 +16075,21 @@
|
|||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
|
||||||
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw=="
|
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw=="
|
||||||
},
|
},
|
||||||
|
"moment-timezone": {
|
||||||
|
"version": "0.5.43",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz",
|
||||||
|
"integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==",
|
||||||
|
"requires": {
|
||||||
|
"moment": "^2.29.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"moment": {
|
||||||
|
"version": "2.29.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||||
|
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"move-concurrently": {
|
"move-concurrently": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||||
@ -16143,6 +16145,11 @@
|
|||||||
"integrity": "sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ==",
|
"integrity": "sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"mustache": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="
|
||||||
|
},
|
||||||
"mute-stream": {
|
"mute-stream": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||||
@ -22450,14 +22457,63 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"vue-i18n": {
|
"vue-i18n": {
|
||||||
"version": "9.1.10",
|
"version": "9.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz",
|
||||||
"integrity": "sha512-jpr7gV5KPk4n+sSPdpZT8Qx3XzTcNDWffRlHV/cT2NUyEf+sEgTTmLvnBAibjOFJ0zsUyZlVTAWH5DDnYep+1g==",
|
"integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@intlify/core-base": "9.1.10",
|
"@intlify/core-base": "9.2.2",
|
||||||
"@intlify/shared": "9.1.10",
|
"@intlify/shared": "9.2.2",
|
||||||
"@intlify/vue-devtools": "9.1.10",
|
"@intlify/vue-devtools": "9.2.2",
|
||||||
"@vue/devtools-api": "^6.0.0-beta.7"
|
"@vue/devtools-api": "^6.2.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/core-base": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/devtools-if": "9.2.2",
|
||||||
|
"@intlify/message-compiler": "9.2.2",
|
||||||
|
"@intlify/shared": "9.2.2",
|
||||||
|
"@intlify/vue-devtools": "9.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@intlify/devtools-if": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/shared": "9.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@intlify/message-compiler": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/shared": "9.2.2",
|
||||||
|
"source-map": "0.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@intlify/shared": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q=="
|
||||||
|
},
|
||||||
|
"@intlify/vue-devtools": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/core-base": "9.2.2",
|
||||||
|
"@intlify/shared": "9.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@vue/devtools-api": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vue-jest": {
|
"vue-jest": {
|
||||||
|
|||||||
@ -38,6 +38,8 @@
|
|||||||
"@fortawesome/free-brands-svg-icons": "^5.15.2",
|
"@fortawesome/free-brands-svg-icons": "^5.15.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.2",
|
"@fortawesome/free-solid-svg-icons": "^5.15.2",
|
||||||
"@fortawesome/vue-fontawesome": "^3.0.0-4",
|
"@fortawesome/vue-fontawesome": "^3.0.0-4",
|
||||||
|
"@vue-js-cron/ant": "^1.1.3",
|
||||||
|
"@vue-js-cron/core": "^3.7.1",
|
||||||
"ant-design-vue": "^3.2.9",
|
"ant-design-vue": "^3.2.9",
|
||||||
"antd": "^4.21.4",
|
"antd": "^4.21.4",
|
||||||
"antd-theme-webpack-plugin": "^1.3.9",
|
"antd-theme-webpack-plugin": "^1.3.9",
|
||||||
@ -46,12 +48,14 @@
|
|||||||
"chart.js": "^3.7.1",
|
"chart.js": "^3.7.1",
|
||||||
"chartjs-adapter-moment": "^1.0.0",
|
"chartjs-adapter-moment": "^1.0.0",
|
||||||
"core-js": "^3.21.1",
|
"core-js": "^3.21.1",
|
||||||
|
"cronstrue": "^2.26.0",
|
||||||
"enquire.js": "^2.1.6",
|
"enquire.js": "^2.1.6",
|
||||||
"js-cookie": "^2.2.1",
|
"js-cookie": "^2.2.1",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"md5": "^2.2.1",
|
"md5": "^2.2.1",
|
||||||
"mitt": "^2.1.0",
|
"mitt": "^2.1.0",
|
||||||
"moment": "^2.26.0",
|
"moment": "^2.26.0",
|
||||||
|
"moment-timezone": "^0.5.43",
|
||||||
"npm-check-updates": "^6.0.1",
|
"npm-check-updates": "^6.0.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"qrious": "^4.0.2",
|
"qrious": "^4.0.2",
|
||||||
|
|||||||
@ -556,6 +556,7 @@
|
|||||||
"label.creating": "Creating",
|
"label.creating": "Creating",
|
||||||
"label.creating.iprange": "Creating IP ranges",
|
"label.creating.iprange": "Creating IP ranges",
|
||||||
"label.credit": "Credit",
|
"label.credit": "Credit",
|
||||||
|
"label.cron": "Cron expression",
|
||||||
"label.crosszones": "Cross zones",
|
"label.crosszones": "Cross zones",
|
||||||
"label.currency": "Currency",
|
"label.currency": "Currency",
|
||||||
"label.current": "Current",
|
"label.current": "Current",
|
||||||
@ -857,6 +858,8 @@
|
|||||||
"label.for": "for",
|
"label.for": "for",
|
||||||
"label.forbidden": "Forbidden",
|
"label.forbidden": "Forbidden",
|
||||||
"label.forced": "Force",
|
"label.forced": "Force",
|
||||||
|
"label.force.stop": "Force stop",
|
||||||
|
"label.force.reboot": "Force reboot",
|
||||||
"label.forceencap": "Force UDP encapsulation of ESP packets",
|
"label.forceencap": "Force UDP encapsulation of ESP packets",
|
||||||
"label.forgedtransmits": "Forged transmits",
|
"label.forgedtransmits": "Forged transmits",
|
||||||
"label.format": "Format",
|
"label.format": "Format",
|
||||||
@ -1710,8 +1713,10 @@
|
|||||||
"label.scaleup.policies": "ScaleUp policies",
|
"label.scaleup.policies": "ScaleUp policies",
|
||||||
"label.scaleup.policy": "ScaleUp policy",
|
"label.scaleup.policy": "ScaleUp policy",
|
||||||
"label.schedule": "Schedule",
|
"label.schedule": "Schedule",
|
||||||
|
"label.schedule.add": "Add schedule",
|
||||||
"label.scheduled.backups": "Scheduled backups",
|
"label.scheduled.backups": "Scheduled backups",
|
||||||
"label.scheduled.snapshots": "Scheduled snapshots",
|
"label.scheduled.snapshots": "Scheduled snapshots",
|
||||||
|
"label.schedules": "Schedules",
|
||||||
"label.scope": "Scope",
|
"label.scope": "Scope",
|
||||||
"label.search": "Search",
|
"label.search": "Search",
|
||||||
"label.secondary.isolated.vlan.type.isolated": "Isolated",
|
"label.secondary.isolated.vlan.type.isolated": "Isolated",
|
||||||
@ -2134,6 +2139,7 @@
|
|||||||
"label.vmlimit": "Instance limits",
|
"label.vmlimit": "Instance limits",
|
||||||
"label.vmname": "VM name",
|
"label.vmname": "VM name",
|
||||||
"label.vms": "VMs",
|
"label.vms": "VMs",
|
||||||
|
"label.vmscheduleactions": "Actions",
|
||||||
"label.vmstate": "VM state",
|
"label.vmstate": "VM state",
|
||||||
"label.vmtotal": "Total of VMs",
|
"label.vmtotal": "Total of VMs",
|
||||||
"label.vmware.storage.policy": "VMWare storage policy",
|
"label.vmware.storage.policy": "VMWare storage policy",
|
||||||
@ -2638,6 +2644,7 @@
|
|||||||
"message.error.remove.nic": "There was an error",
|
"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.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.tungsten.routing.policy": "Removing Tungsten-Fabric Routing Policy from network failed",
|
||||||
|
"message.error.remove.vm.schedule": "Removing Instance Schedule failed",
|
||||||
"message.error.required.input": "Please enter input",
|
"message.error.required.input": "Please enter input",
|
||||||
"message.error.reset.config": "Unable to reset config to default value",
|
"message.error.reset.config": "Unable to reset config to default value",
|
||||||
"message.error.retrieve.kubeconfig": "Unable to retrieve Kubernetes cluster config",
|
"message.error.retrieve.kubeconfig": "Unable to retrieve Kubernetes cluster config",
|
||||||
@ -2940,6 +2947,7 @@
|
|||||||
"message.success.config.backup.schedule": "Successfully configured VM backup schedule",
|
"message.success.config.backup.schedule": "Successfully configured VM backup schedule",
|
||||||
"message.success.config.health.monitor": "Successfully Configure Health Monitor",
|
"message.success.config.health.monitor": "Successfully Configure Health Monitor",
|
||||||
"message.success.config.sticky.policy": "Successfully configured sticky policy",
|
"message.success.config.sticky.policy": "Successfully configured sticky policy",
|
||||||
|
"message.success.config.vm.schedule": "Successfully configured instance schedule",
|
||||||
"message.success.copy.clipboard": "Successfully copied to clipboard",
|
"message.success.copy.clipboard": "Successfully copied to clipboard",
|
||||||
"message.success.create.account": "Successfully created account",
|
"message.success.create.account": "Successfully created account",
|
||||||
"message.success.create.internallb": "Successfully created Internal Load Balancer",
|
"message.success.create.internallb": "Successfully created Internal Load Balancer",
|
||||||
|
|||||||
@ -99,6 +99,12 @@
|
|||||||
<span v-if="['USER.LOGIN', 'USER.LOGOUT', 'ROUTER.HEALTH.CHECKS', 'FIREWALL.CLOSE', 'ALERT.SERVICE.DOMAINROUTER'].includes(text)">{{ $t(text.toLowerCase()) }}</span>
|
<span v-if="['USER.LOGIN', 'USER.LOGOUT', 'ROUTER.HEALTH.CHECKS', 'FIREWALL.CLOSE', 'ALERT.SERVICE.DOMAINROUTER'].includes(text)">{{ $t(text.toLowerCase()) }}</span>
|
||||||
<span v-else>{{ text }}</span>
|
<span v-else>{{ text }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-if="column.key === 'schedule'">
|
||||||
|
{{ text }}
|
||||||
|
<br/>
|
||||||
|
({{ generateHumanReadableSchedule(text) }})
|
||||||
|
</template>
|
||||||
<template v-if="column.key === 'displayname'">
|
<template v-if="column.key === 'displayname'">
|
||||||
<QuickView
|
<QuickView
|
||||||
style="margin-left: 5px"
|
style="margin-left: 5px"
|
||||||
@ -296,11 +302,15 @@
|
|||||||
<template v-if="column.key === 'current'">
|
<template v-if="column.key === 'current'">
|
||||||
<status :text="record.current ? record.current.toString() : 'false'" />
|
<status :text="record.current ? record.current.toString() : 'false'" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'created'">
|
<template v-if="column.key === 'enabled'">
|
||||||
|
<status :text="record.enabled ? record.enabled.toString() : 'false'" />
|
||||||
|
{{ record.enabled ? 'Enabled' : 'Disabled' }}
|
||||||
|
</template>
|
||||||
|
<template v-if="['created', 'sent'].includes(column.key)">
|
||||||
{{ $toLocaleDate(text) }}
|
{{ $toLocaleDate(text) }}
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'sent'">
|
<template v-if="['startdate', 'enddate'].includes(column.key) && ['vm'].includes($route.path.split('/')[1])">
|
||||||
{{ $toLocaleDate(text) }}
|
{{ getDateAtTimeZone(text, record.timezone) }}
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'order'">
|
<template v-if="column.key === 'order'">
|
||||||
<div class="shift-btns">
|
<div class="shift-btns">
|
||||||
@ -387,6 +397,20 @@
|
|||||||
@onClick="editTariffValue(record)" />
|
@onClick="editTariffValue(record)" />
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="column.key === 'vmScheduleActions'">
|
||||||
|
<tooltip-button
|
||||||
|
:tooltip="$t('label.edit')"
|
||||||
|
:disabled="!('updateVMSchedule' in $store.getters.apis)"
|
||||||
|
icon="edit-outlined"
|
||||||
|
@onClick="updateVMSchedule(record)" />
|
||||||
|
<tooltip-button
|
||||||
|
:tooltip="$t('label.remove')"
|
||||||
|
:disabled="!('deleteVMSchedule' in $store.getters.apis)"
|
||||||
|
icon="delete-outlined"
|
||||||
|
:danger="true"
|
||||||
|
type="primary"
|
||||||
|
@onClick="removeVMSchedule(record)" />
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span v-if="hasSelected">
|
<span v-if="hasSelected">
|
||||||
@ -405,6 +429,8 @@ import TooltipButton from '@/components/widgets/TooltipButton'
|
|||||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||||
import ResourceLabel from '@/components/widgets/ResourceLabel'
|
import ResourceLabel from '@/components/widgets/ResourceLabel'
|
||||||
import { createPathBasedOnVmType } from '@/utils/plugins'
|
import { createPathBasedOnVmType } from '@/utils/plugins'
|
||||||
|
import cronstrue from 'cronstrue/i18n'
|
||||||
|
import moment from 'moment-timezone'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ListView',
|
name: 'ListView',
|
||||||
@ -522,6 +548,9 @@ export default {
|
|||||||
'diskoffering', 'backupoffering', 'networkoffering', 'vpcoffering', 'ilbvm', 'kubernetes', 'comment'
|
'diskoffering', 'backupoffering', 'networkoffering', 'vpcoffering', 'ilbvm', 'kubernetes', 'comment'
|
||||||
].includes(this.$route.name)
|
].includes(this.$route.name)
|
||||||
},
|
},
|
||||||
|
getDateAtTimeZone (date, timezone) {
|
||||||
|
return date ? moment(date).tz(timezone).format('YYYY-MM-DD HH:mm:ss') : null
|
||||||
|
},
|
||||||
fetchColumns () {
|
fetchColumns () {
|
||||||
if (this.isOrderUpdatable()) {
|
if (this.isOrderUpdatable()) {
|
||||||
return this.columns
|
return this.columns
|
||||||
@ -686,6 +715,12 @@ export default {
|
|||||||
editTariffValue (record) {
|
editTariffValue (record) {
|
||||||
this.$emit('edit-tariff-action', true, record)
|
this.$emit('edit-tariff-action', true, record)
|
||||||
},
|
},
|
||||||
|
updateVMSchedule (record) {
|
||||||
|
this.$emit('update-vm-schedule', record)
|
||||||
|
},
|
||||||
|
removeVMSchedule (record) {
|
||||||
|
this.$emit('remove-vm-schedule', record)
|
||||||
|
},
|
||||||
ipV6Address (text, record) {
|
ipV6Address (text, record) {
|
||||||
if (!record || !record.nic || record.nic.length === 0) {
|
if (!record || !record.nic || record.nic.length === 0) {
|
||||||
return ''
|
return ''
|
||||||
@ -730,6 +765,9 @@ export default {
|
|||||||
default: return record.entitytype.toLowerCase().replace('_', '')
|
default: return record.entitytype.toLowerCase().replace('_', '')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
generateHumanReadableSchedule (schedule) {
|
||||||
|
return cronstrue.toString(schedule, { locale: this.$i18n.locale })
|
||||||
|
},
|
||||||
entityTypeToPath (entitytype) {
|
entityTypeToPath (entitytype) {
|
||||||
switch (entitytype) {
|
switch (entitytype) {
|
||||||
case 'VM' : return 'vm'
|
case 'VM' : return 'vm'
|
||||||
|
|||||||
@ -68,6 +68,9 @@ import {
|
|||||||
import VueClipboard from 'vue3-clipboard'
|
import VueClipboard from 'vue3-clipboard'
|
||||||
import VueCropper from 'vue-cropper'
|
import VueCropper from 'vue-cropper'
|
||||||
|
|
||||||
|
import cronAnt from '@vue-js-cron/ant'
|
||||||
|
import '@vue-js-cron/ant/dist/ant.css'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
install: (app) => {
|
install: (app) => {
|
||||||
app.config.globalProperties.$confirm = Modal.confirm
|
app.config.globalProperties.$confirm = Modal.confirm
|
||||||
@ -77,6 +80,7 @@ export default {
|
|||||||
app.config.globalProperties.$error = Modal.error
|
app.config.globalProperties.$error = Modal.error
|
||||||
app.config.globalProperties.$warning = Modal.warning
|
app.config.globalProperties.$warning = Modal.warning
|
||||||
|
|
||||||
|
app.use(cronAnt)
|
||||||
app.use(VueClipboard, { autoSetContainer: true })
|
app.use(VueClipboard, { autoSetContainer: true })
|
||||||
app.use(VueCropper)
|
app.use(VueCropper)
|
||||||
app.use(ConfigProvider)
|
app.use(ConfigProvider)
|
||||||
|
|||||||
461
ui/src/views/compute/InstanceSchedules.vue
Normal file
461
ui/src/views/compute/InstanceSchedules.vue
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-button
|
||||||
|
type="dashed"
|
||||||
|
style="width: 100%; margin-bottom: 10px"
|
||||||
|
@click="showAddModal"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="!('createVMSchedule' in $store.getters.apis)">
|
||||||
|
<template #icon><plus-outlined/></template> {{ $t('label.schedule.add') }}
|
||||||
|
</a-button>
|
||||||
|
<list-view
|
||||||
|
:loading="tabLoading"
|
||||||
|
:columns="columns"
|
||||||
|
:items="schedules"
|
||||||
|
:columnKeys="columnKeys"
|
||||||
|
:selectedColumns="selectedColumnKeys"
|
||||||
|
ref="listview"
|
||||||
|
@update-selected-columns="updateSelectedColumns"
|
||||||
|
@update-vm-schedule="updateVMSchedule"
|
||||||
|
@remove-vm-schedule="removeVMSchedule"
|
||||||
|
@refresh="this.fetchData"/>
|
||||||
|
<a-pagination
|
||||||
|
class="row-element"
|
||||||
|
style="margin-top: 10px"
|
||||||
|
size="small"
|
||||||
|
:current="page"
|
||||||
|
:pageSize="pageSize"
|
||||||
|
:total="totalCount"
|
||||||
|
:showTotal="total => `${$t('label.showing')} ${Math.min(total, 1+((page-1)*pageSize))}-${Math.min(page*pageSize, total)} ${$t('label.of')} ${total} ${$t('label.items')}`"
|
||||||
|
:pageSizeOptions="pageSizeOptions"
|
||||||
|
@change="changePage"
|
||||||
|
@showSizeChange="changePage"
|
||||||
|
showSizeChanger
|
||||||
|
showQuickJumper>
|
||||||
|
<template #buildOptionText="props">
|
||||||
|
<span>{{ props.value }} / {{ $t('label.page') }}</span>
|
||||||
|
</template>
|
||||||
|
</a-pagination>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-modal
|
||||||
|
:visible="showModal"
|
||||||
|
:title="$t('label.schedule')"
|
||||||
|
:maskClosable="false"
|
||||||
|
:closable="true"
|
||||||
|
:footer="null"
|
||||||
|
@cancel="closeModal">
|
||||||
|
<a-form
|
||||||
|
layout="vertical"
|
||||||
|
:ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
@finish="submitForm"
|
||||||
|
v-ctrl-enter="submitForm">
|
||||||
|
<a-form-item name="description" ref="description">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.description')" :tooltip="apiParams.description.description"/>
|
||||||
|
</template>
|
||||||
|
<a-input
|
||||||
|
v-model:value="form.description"
|
||||||
|
v-focus="true" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item name="action" ref="action">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.action')" :tooltip="apiParams.action.description"/>
|
||||||
|
</template>
|
||||||
|
<a-radio-group
|
||||||
|
v-model:value="form.action"
|
||||||
|
button-style="solid"
|
||||||
|
:disabled="isEdit">
|
||||||
|
<a-radio-button v-for="action in actions" :key="action.id" :value="action.value">
|
||||||
|
{{ $t(action.label) }}
|
||||||
|
</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item name="schedule" ref="schedule">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.schedule')" :tooltip="apiParams.schedule.description"/>
|
||||||
|
</template>
|
||||||
|
<label>{{ $t('label.advanced.mode') }}</label>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="form.useCronFormat"
|
||||||
|
>
|
||||||
|
</a-switch>
|
||||||
|
<br/>
|
||||||
|
<span v-if="!form.useCronFormat">
|
||||||
|
<cron-ant
|
||||||
|
v-model="form.schedule"
|
||||||
|
:periods="periods"
|
||||||
|
:button-props="{ type: 'primary', size: 'small', disabled: form.useCronFormat }"
|
||||||
|
@error="error=$event"/>
|
||||||
|
</span>
|
||||||
|
<span v-if="form.useCronFormat">
|
||||||
|
<label>{{ generateHumanReadableSchedule(form.schedule) }}</label>
|
||||||
|
<br/>
|
||||||
|
</span>
|
||||||
|
<a-input
|
||||||
|
:addonBefore="$t('label.cron')"
|
||||||
|
v-model:value="form.schedule"
|
||||||
|
:disabled="!form.useCronFormat"
|
||||||
|
v-focus="true" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item name="timezone" ref="timezone">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.timezone')" :tooltip="apiParams.timezone.description"/>
|
||||||
|
</template>
|
||||||
|
<a-select
|
||||||
|
showSearch
|
||||||
|
v-model:value="form.timezone"
|
||||||
|
optionFilterProp="label"
|
||||||
|
:filterOption="(input, option) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
}"
|
||||||
|
:loading="fetching">
|
||||||
|
<a-select-option v-for="opt in timeZoneMap" :key="opt.id" :label="opt.name || opt.description">
|
||||||
|
{{ opt.name || opt.description }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item name="startDate" ref="startDate">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.start.date.and.time')" :tooltip="apiParams.startdate.description"/>
|
||||||
|
</template>
|
||||||
|
<a-date-picker
|
||||||
|
v-model:value="form.startDate"
|
||||||
|
show-time
|
||||||
|
:locale="this.$i18n.locale"
|
||||||
|
:placeholder="$t('message.select.start.date.and.time')"/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item name="endDate" ref="endDate">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.end.date.and.time')" :tooltip="apiParams.enddate.description"/>
|
||||||
|
</template>
|
||||||
|
<a-date-picker
|
||||||
|
v-model:value="form.endDate"
|
||||||
|
show-time
|
||||||
|
:locale="this.$i18n.locale"
|
||||||
|
:placeholder="$t('message.select.end.date.and.time')"/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item name="enabled" ref="enabled">
|
||||||
|
<template #label>
|
||||||
|
<tooltip-label :title="$t('label.enabled')" :tooltip="apiParams.enabled.description"/>
|
||||||
|
</template>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="form.enabled">
|
||||||
|
</a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<div :span="24" class="action-button">
|
||||||
|
<a-button
|
||||||
|
:loading="loading"
|
||||||
|
@click="closeModal">
|
||||||
|
{{ $t('label.cancel') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
:loading="loading"
|
||||||
|
ref="submit"
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit">
|
||||||
|
{{ $t('label.ok') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import { reactive, ref, toRaw } from 'vue'
|
||||||
|
import { api } from '@/api'
|
||||||
|
import ListView from '@/components/view/ListView'
|
||||||
|
import Status from '@/components/widgets/Status'
|
||||||
|
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||||
|
import { mixinForm } from '@/utils/mixin'
|
||||||
|
import { timeZone } from '@/utils/timezone'
|
||||||
|
import debounce from 'lodash/debounce'
|
||||||
|
import cronstrue from 'cronstrue/i18n'
|
||||||
|
import moment from 'moment-timezone'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'InstanceSchedules',
|
||||||
|
mixins: [mixinForm],
|
||||||
|
components: {
|
||||||
|
Status,
|
||||||
|
ListView,
|
||||||
|
TooltipLabel
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
virtualmachine: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
this.fetchTimeZone = debounce(this.fetchTimeZone, 800)
|
||||||
|
return {
|
||||||
|
tabLoading: false,
|
||||||
|
columnKeys: ['action', 'enabled', 'description', 'schedule', 'timezone', 'startdate', 'enddate', 'created', 'vmScheduleActions'],
|
||||||
|
selectedColumnKeys: [],
|
||||||
|
columns: [],
|
||||||
|
schedules: [],
|
||||||
|
timeZoneMap: [],
|
||||||
|
actions: [
|
||||||
|
{ value: 'START', label: 'label.start' },
|
||||||
|
{ value: 'STOP', label: 'label.stop' },
|
||||||
|
{ value: 'REBOOT', label: 'label.reboot' },
|
||||||
|
{ value: 'FORCE_STOP', label: 'label.force.stop' },
|
||||||
|
{ value: 'FORCE_REBOOT', label: 'label.force.reboot' }
|
||||||
|
],
|
||||||
|
periods: [
|
||||||
|
{ id: 'year', value: ['month', 'day', 'dayOfWeek', 'hour', 'minute'] },
|
||||||
|
{ id: 'month', value: ['day', 'dayOfWeek', 'hour', 'minute'] },
|
||||||
|
{ id: 'week', value: ['dayOfWeek', 'hour', 'minute'] },
|
||||||
|
{ id: 'day', value: ['hour', 'minute'] }
|
||||||
|
],
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
totalCount: 0,
|
||||||
|
showModal: false,
|
||||||
|
isSubmitted: false,
|
||||||
|
isEdit: false,
|
||||||
|
error: '',
|
||||||
|
pattern: 'YYYY-MM-DD HH:mm:ss'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeCreate () {
|
||||||
|
this.apiParams = this.$getApiParams('createVMSchedule')
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
pageSizeOptions () {
|
||||||
|
var sizes = [20, 50, 100, 200, this.$store.getters.defaultListViewPageSize]
|
||||||
|
if (this.device !== 'desktop') {
|
||||||
|
sizes.unshift(10)
|
||||||
|
}
|
||||||
|
return [...new Set(sizes)].sort(function (a, b) {
|
||||||
|
return a - b
|
||||||
|
}).map(String)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.selectedColumnKeys = this.columnKeys
|
||||||
|
this.updateColumns()
|
||||||
|
this.pageSize = this.pageSizeOptions[0] * 1
|
||||||
|
this.initForm()
|
||||||
|
this.fetchData()
|
||||||
|
this.fetchTimeZone()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
virtualmachine: {
|
||||||
|
handler () {
|
||||||
|
this.fetchSchedules()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initForm () {
|
||||||
|
this.formRef = ref()
|
||||||
|
this.form = reactive({
|
||||||
|
action: 'START',
|
||||||
|
schedule: '* * * * *',
|
||||||
|
description: '',
|
||||||
|
timezone: 'UTC',
|
||||||
|
startDate: '',
|
||||||
|
endDate: '',
|
||||||
|
enabled: true,
|
||||||
|
useCronFormat: false
|
||||||
|
})
|
||||||
|
this.rules = reactive({
|
||||||
|
schedule: [{ type: 'string', required: true, message: this.$t('message.error.required.input') }],
|
||||||
|
action: [{ type: 'string', required: true, message: this.$t('message.error.required.input') }],
|
||||||
|
timezone: [{ required: true, message: `${this.$t('message.error.select')}` }],
|
||||||
|
startDate: [{ required: false, message: `${this.$t('message.error.select')}` }],
|
||||||
|
endDate: [{ required: false, message: `${this.$t('message.error.select')}` }]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
createVMSchedule (schedule) {
|
||||||
|
this.resetForm()
|
||||||
|
this.showAddModal()
|
||||||
|
},
|
||||||
|
removeVMSchedule (schedule) {
|
||||||
|
api('deleteVMSchedule', {
|
||||||
|
id: schedule.id,
|
||||||
|
virtualmachineid: this.virtualmachine.id
|
||||||
|
}).then(() => {
|
||||||
|
if (this.totalCount - 1 === this.pageSize * (this.page - 1)) {
|
||||||
|
this.page = this.page - 1 > 0 ? this.page - 1 : 1
|
||||||
|
}
|
||||||
|
const message = `${this.$t('label.removing')} ${schedule.description}`
|
||||||
|
this.$message.success(message)
|
||||||
|
}).catch(error => {
|
||||||
|
console.error(error)
|
||||||
|
this.$message.error(this.$t('message.error.remove.vm.schedule'))
|
||||||
|
this.$notification.error({
|
||||||
|
message: this.$t('label.error'),
|
||||||
|
description: this.$t('message.error.remove.vm.schedule')
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
this.fetchData()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updateVMSchedule (schedule) {
|
||||||
|
this.resetForm()
|
||||||
|
this.isEdit = true
|
||||||
|
Object.assign(this.form, schedule)
|
||||||
|
// Some weird issue when we directly pass in the moment with tz object
|
||||||
|
this.form.startDate = moment(moment(schedule.startdate).tz(schedule.timezone).format(this.pattern))
|
||||||
|
this.form.endDate = schedule.enddate ? moment(moment(schedule.enddate).tz(schedule.timezone).format(this.pattern)) : ''
|
||||||
|
this.showAddModal()
|
||||||
|
},
|
||||||
|
showAddModal () {
|
||||||
|
this.showModal = true
|
||||||
|
},
|
||||||
|
submitForm () {
|
||||||
|
if (this.isSubmitted) return
|
||||||
|
this.isSubmitted = true
|
||||||
|
this.formRef.value.validate().then(() => {
|
||||||
|
const formRaw = toRaw(this.form)
|
||||||
|
const values = this.handleRemoveFields(formRaw)
|
||||||
|
var params = {
|
||||||
|
description: values.description,
|
||||||
|
schedule: values.schedule,
|
||||||
|
timezone: values.timezone,
|
||||||
|
action: values.action,
|
||||||
|
virtualmachineid: this.virtualmachine.id,
|
||||||
|
enabled: values.enabled,
|
||||||
|
startdate: (values.startDate) ? values.startDate.format(this.pattern) : null,
|
||||||
|
enddate: (values.endDate) ? values.endDate.format(this.pattern) : null
|
||||||
|
}
|
||||||
|
let command = null
|
||||||
|
if (this.form.id === null || this.form.id === undefined) {
|
||||||
|
command = 'createVMSchedule'
|
||||||
|
} else {
|
||||||
|
params.id = this.form.id
|
||||||
|
command = 'updateVMSchedule'
|
||||||
|
}
|
||||||
|
|
||||||
|
api(command, params).then(response => {
|
||||||
|
this.$notification.success({
|
||||||
|
message: this.$t('label.schedule'),
|
||||||
|
description: this.$t('message.success.config.vm.schedule')
|
||||||
|
})
|
||||||
|
this.isSubmitted = false
|
||||||
|
this.fetchData()
|
||||||
|
this.closeModal()
|
||||||
|
}).catch(error => {
|
||||||
|
this.$notifyError(error)
|
||||||
|
this.isSubmitted = false
|
||||||
|
})
|
||||||
|
}).catch(error => {
|
||||||
|
this.$notifyError(error)
|
||||||
|
if (error.errorFields !== undefined) {
|
||||||
|
this.formRef.value.scrollToField(error.errorFields[0].name)
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.isSubmitted = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
resetForm () {
|
||||||
|
this.isEdit = false
|
||||||
|
if (this.formRef.value) {
|
||||||
|
this.formRef.value.resetFields()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchTimeZone (value) {
|
||||||
|
this.timeZoneMap = []
|
||||||
|
this.fetching = true
|
||||||
|
|
||||||
|
timeZone(value).then(json => {
|
||||||
|
this.timeZoneMap = json
|
||||||
|
this.fetching = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
closeModal () {
|
||||||
|
this.resetForm()
|
||||||
|
this.initForm()
|
||||||
|
this.showModal = false
|
||||||
|
},
|
||||||
|
fetchData () {
|
||||||
|
this.fetchSchedules()
|
||||||
|
},
|
||||||
|
fetchSchedules () {
|
||||||
|
this.schedules = []
|
||||||
|
if (!this.virtualmachine.id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const params = {
|
||||||
|
page: this.page,
|
||||||
|
pagesize: this.pageSize,
|
||||||
|
virtualmachineid: this.virtualmachine.id,
|
||||||
|
listall: true
|
||||||
|
}
|
||||||
|
this.tabLoading = true
|
||||||
|
api('listVMSchedule', params).then(json => {
|
||||||
|
this.schedules = []
|
||||||
|
this.totalCount = json?.listvmscheduleresponse?.count || 0
|
||||||
|
this.schedules = json?.listvmscheduleresponse?.vmschedule || []
|
||||||
|
this.tabLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
changePage (page, pageSize) {
|
||||||
|
this.page = page
|
||||||
|
this.pageSize = pageSize
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
updateSelectedColumns (key) {
|
||||||
|
if (this.selectedColumnKeys.includes(key)) {
|
||||||
|
this.selectedColumnKeys = this.selectedColumnKeys.filter(x => x !== key)
|
||||||
|
} else {
|
||||||
|
this.selectedColumnKeys.push(key)
|
||||||
|
}
|
||||||
|
this.updateColumns()
|
||||||
|
},
|
||||||
|
generateHumanReadableSchedule (schedule) {
|
||||||
|
return cronstrue.toString(schedule, { locale: this.$i18n.locale, throwExceptionOnParseError: false })
|
||||||
|
},
|
||||||
|
updateColumns () {
|
||||||
|
this.columns = []
|
||||||
|
for (var columnKey of this.columnKeys) {
|
||||||
|
if (!this.selectedColumnKeys.includes(columnKey)) continue
|
||||||
|
this.columns.push({
|
||||||
|
key: columnKey,
|
||||||
|
// If columnKey is 'enabled', then title is 'state'
|
||||||
|
// If columnKey is 'startdate', then the title is `start.date.and.time`
|
||||||
|
// else title is columnKey
|
||||||
|
title: columnKey === 'enabled'
|
||||||
|
? this.$t('label.state')
|
||||||
|
: columnKey === 'startdate'
|
||||||
|
? this.$t('label.start.date.and.time')
|
||||||
|
: columnKey === 'enddate'
|
||||||
|
? this.$t('label.end.date.and.time')
|
||||||
|
: this.$t('label.' + String(columnKey).toLowerCase()),
|
||||||
|
dataIndex: columnKey
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (this.columns.length > 0) {
|
||||||
|
this.columns[this.columns.length - 1].customFilterDropdown = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -116,6 +116,11 @@
|
|||||||
:routerlinks="(record) => { return { name: '/securitygroups/' + record.id } }"
|
:routerlinks="(record) => { return { name: '/securitygroups/' + record.id } }"
|
||||||
:showSearch="false"/>
|
:showSearch="false"/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
<a-tab-pane :tab="$t('label.schedules')" key="schedules" v-if="'listVMSchedule' in $store.getters.apis">
|
||||||
|
<InstanceSchedules
|
||||||
|
:virtualmachine="vm"
|
||||||
|
:loading="loading"/>
|
||||||
|
</a-tab-pane>
|
||||||
<a-tab-pane :tab="$t('label.settings')" key="settings">
|
<a-tab-pane :tab="$t('label.settings')" key="settings">
|
||||||
<DetailSettings :resource="dataResource" :loading="loading" />
|
<DetailSettings :resource="dataResource" :loading="loading" />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
@ -290,6 +295,7 @@ import StatsTab from '@/components/view/StatsTab'
|
|||||||
import EventsTab from '@/components/view/EventsTab'
|
import EventsTab from '@/components/view/EventsTab'
|
||||||
import DetailSettings from '@/components/view/DetailSettings'
|
import DetailSettings from '@/components/view/DetailSettings'
|
||||||
import NicsTable from '@/views/network/NicsTable'
|
import NicsTable from '@/views/network/NicsTable'
|
||||||
|
import InstanceSchedules from '@/views/compute/InstanceSchedules.vue'
|
||||||
import ListResourceTable from '@/components/view/ListResourceTable'
|
import ListResourceTable from '@/components/view/ListResourceTable'
|
||||||
import TooltipButton from '@/components/widgets/TooltipButton'
|
import TooltipButton from '@/components/widgets/TooltipButton'
|
||||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||||
@ -305,6 +311,7 @@ export default {
|
|||||||
EventsTab,
|
EventsTab,
|
||||||
DetailSettings,
|
DetailSettings,
|
||||||
NicsTable,
|
NicsTable,
|
||||||
|
InstanceSchedules,
|
||||||
ListResourceTable,
|
ListResourceTable,
|
||||||
TooltipButton,
|
TooltipButton,
|
||||||
ResourceIcon,
|
ResourceIcon,
|
||||||
|
|||||||
@ -118,6 +118,11 @@
|
|||||||
<artifactId>javax.servlet-api</artifactId>
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.cronutils</groupId>
|
||||||
|
<artifactId>cron-utils</artifactId>
|
||||||
|
<version>${cs.cron-utils.version}</version>
|
||||||
|
</dependency>
|
||||||
<!-- Test dependency in mysql for db tests -->
|
<!-- Test dependency in mysql for db tests -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>mysql</groupId>
|
||||||
|
|||||||
@ -22,6 +22,9 @@ package com.cloud.utils;
|
|||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
@ -31,6 +34,13 @@ import java.time.format.DateTimeParseException;
|
|||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
|
|
||||||
import com.cloud.utils.exception.CloudRuntimeException;
|
import com.cloud.utils.exception.CloudRuntimeException;
|
||||||
|
import com.cronutils.descriptor.CronDescriptor;
|
||||||
|
import com.cronutils.model.CronType;
|
||||||
|
import com.cronutils.model.definition.CronDefinition;
|
||||||
|
import com.cronutils.model.definition.CronDefinitionBuilder;
|
||||||
|
import com.cronutils.parser.CronParser;
|
||||||
|
import org.springframework.scheduling.support.CronExpression;
|
||||||
|
|
||||||
|
|
||||||
public class DateUtil {
|
public class DateUtil {
|
||||||
public static final int HOURS_IN_A_MONTH = 30 * 24;
|
public static final int HOURS_IN_A_MONTH = 30 * 24;
|
||||||
@ -295,4 +305,35 @@ public class DateUtil {
|
|||||||
return (dateCalendar1.getTimeInMillis() - dateCalendar2.getTimeInMillis() )/1000;
|
return (dateCalendar1.getTimeInMillis() - dateCalendar2.getTimeInMillis() )/1000;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static CronExpression parseSchedule(String schedule) {
|
||||||
|
if (schedule != null) {
|
||||||
|
// CronExpression's granularity is in seconds. Prepending "0 " to change the granularity to minutes.
|
||||||
|
return CronExpression.parse(String.format("0 %s", schedule));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getHumanReadableSchedule(CronExpression schedule) {
|
||||||
|
CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(CronType.SPRING);
|
||||||
|
CronParser parser = new CronParser(cronDefinition);
|
||||||
|
CronDescriptor descriptor = CronDescriptor.instance();
|
||||||
|
return descriptor.describe(parser.parse(schedule.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ZonedDateTime getZoneDateTime(Date date, ZoneId tzId) {
|
||||||
|
if (date == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ZonedDateTime zonedDate = ZonedDateTime.ofInstant(date.toInstant(), tzId);
|
||||||
|
LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), TimeZone.getDefault().toZoneId());
|
||||||
|
zonedDate = zonedDate.withYear(localDateTime.getYear())
|
||||||
|
.withMonth(localDateTime.getMonthValue())
|
||||||
|
.withDayOfMonth(localDateTime.getDayOfMonth())
|
||||||
|
.withHour(localDateTime.getHour())
|
||||||
|
.withMinute(localDateTime.getMinute())
|
||||||
|
.withSecond(localDateTime.getSecond());
|
||||||
|
return zonedDate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user