mirror of https://github.com/apache/cloudstack.git
Merge branch 'main' into cks-enhancements-upstream
This commit is contained in:
commit
9b7ee63ded
|
|
@ -164,7 +164,8 @@ jobs:
|
|||
component/test_cpu_limits
|
||||
component/test_cpu_max_limits
|
||||
component/test_cpu_project_limits
|
||||
component/test_deploy_vm_userdata_multi_nic",
|
||||
component/test_deploy_vm_userdata_multi_nic
|
||||
component/test_deploy_vm_lease",
|
||||
"component/test_egress_fw_rules
|
||||
component/test_invalid_gw_nm
|
||||
component/test_ip_reservation",
|
||||
|
|
|
|||
|
|
@ -798,6 +798,11 @@ public class EventTypes {
|
|||
// Resource Limit
|
||||
public static final String EVENT_RESOURCE_LIMIT_UPDATE = "RESOURCE.LIMIT.UPDATE";
|
||||
|
||||
public static final String VM_LEASE_EXPIRED = "VM.LEASE.EXPIRED";
|
||||
public static final String VM_LEASE_DISABLED = "VM.LEASE.DISABLED";
|
||||
public static final String VM_LEASE_CANCELLED = "VM.LEASE.CANCELLED";
|
||||
public static final String VM_LEASE_EXPIRING = "VM.LEASE.EXPIRING";
|
||||
|
||||
static {
|
||||
|
||||
// TODO: need a way to force author adding event types to declare the entity details as well, with out braking
|
||||
|
|
@ -1292,6 +1297,12 @@ public class EventTypes {
|
|||
entityEventDetails.put(EVENT_SHAREDFS_DESTROY, SharedFS.class);
|
||||
entityEventDetails.put(EVENT_SHAREDFS_EXPUNGE, SharedFS.class);
|
||||
entityEventDetails.put(EVENT_SHAREDFS_RECOVER, SharedFS.class);
|
||||
|
||||
// VM Lease
|
||||
entityEventDetails.put(VM_LEASE_EXPIRED, VirtualMachine.class);
|
||||
entityEventDetails.put(VM_LEASE_EXPIRING, VirtualMachine.class);
|
||||
entityEventDetails.put(VM_LEASE_DISABLED, VirtualMachine.class);
|
||||
entityEventDetails.put(VM_LEASE_CANCELLED, VirtualMachine.class);
|
||||
}
|
||||
|
||||
public static boolean isNetworkEvent(String eventType) {
|
||||
|
|
|
|||
|
|
@ -113,4 +113,8 @@ public interface VmDetailConstants {
|
|||
// CPU mode and model, ADMIN only
|
||||
String GUEST_CPU_MODE = "guest.cpu.mode";
|
||||
String GUEST_CPU_MODEL = "guest.cpu.model";
|
||||
|
||||
String INSTANCE_LEASE_EXPIRY_DATE = "leaseexpirydate";
|
||||
String INSTANCE_LEASE_EXPIRY_ACTION = "leaseexpiryaction";
|
||||
String INSTANCE_LEASE_EXECUTION = "leaseactionexecution";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,7 +274,10 @@ public class ApiConstants {
|
|||
public static final String INTERNAL_DNS2 = "internaldns2";
|
||||
public static final String INTERNET_PROTOCOL = "internetprotocol";
|
||||
public static final String INTERVAL_TYPE = "intervaltype";
|
||||
public static final String LOCATION_TYPE = "locationtype";
|
||||
public static final String INSTANCE_LEASE_DURATION = "leaseduration";
|
||||
public static final String INSTANCE_LEASE_ENABLED = "instanceleaseenabled";
|
||||
public static final String INSTANCE_LEASE_EXPIRY_ACTION = "leaseexpiryaction";
|
||||
public static final String INSTANCE_LEASE_EXPIRY_DATE= "leaseexpirydate";
|
||||
public static final String IOPS_READ_RATE = "iopsreadrate";
|
||||
public static final String IOPS_READ_RATE_MAX = "iopsreadratemax";
|
||||
public static final String IOPS_READ_RATE_MAX_LENGTH = "iopsreadratemaxlength";
|
||||
|
|
@ -322,11 +325,13 @@ public class ApiConstants {
|
|||
public static final String LAST_BOOT = "lastboottime";
|
||||
public static final String LAST_SERVER_START = "lastserverstart";
|
||||
public static final String LAST_SERVER_STOP = "lastserverstop";
|
||||
public static final String LEASED = "leased";
|
||||
public static final String LEVEL = "level";
|
||||
public static final String LENGTH = "length";
|
||||
public static final String LIMIT = "limit";
|
||||
public static final String LIMIT_CPU_USE = "limitcpuuse";
|
||||
public static final String LIST_HOSTS = "listhosts";
|
||||
public static final String LOCATION_TYPE = "locationtype";
|
||||
public static final String LOCK = "lock";
|
||||
public static final String LUN = "lun";
|
||||
public static final String LBID = "lbruleid";
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ import org.apache.cloudstack.api.response.ServiceOfferingResponse;
|
|||
import org.apache.cloudstack.api.response.VsphereStoragePoliciesResponse;
|
||||
import org.apache.cloudstack.api.response.ZoneResponse;
|
||||
import org.apache.cloudstack.api.response.DiskOfferingResponse;
|
||||
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang3.EnumUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
||||
|
|
@ -251,7 +253,15 @@ public class CreateServiceOfferingCmd extends BaseCmd {
|
|||
since="4.20")
|
||||
private Boolean purgeResources;
|
||||
|
||||
@Parameter(name = ApiConstants.INSTANCE_LEASE_DURATION,
|
||||
type = CommandType.INTEGER,
|
||||
description = "Number of days instance is leased for.",
|
||||
since = "4.21.0")
|
||||
private Integer leaseDuration;
|
||||
|
||||
@Parameter(name = ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION, type = CommandType.STRING, since = "4.21.0",
|
||||
description = "Lease expiry action, valid values are STOP and DESTROY")
|
||||
private String leaseExpiryAction;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
|
|
@ -487,6 +497,22 @@ public class CreateServiceOfferingCmd extends BaseCmd {
|
|||
return false;
|
||||
}
|
||||
|
||||
public VMLeaseManager.ExpiryAction getLeaseExpiryAction() {
|
||||
if (StringUtils.isBlank(leaseExpiryAction)) {
|
||||
return null;
|
||||
}
|
||||
VMLeaseManager.ExpiryAction action = EnumUtils.getEnumIgnoreCase(VMLeaseManager.ExpiryAction.class, leaseExpiryAction);
|
||||
if (action == null) {
|
||||
throw new InvalidParameterValueException("Invalid value configured for leaseexpiryaction, valid values are: " +
|
||||
com.cloud.utils.EnumUtils.listValues(VMLeaseManager.ExpiryAction.values()));
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
public Integer getLeaseDuration() {
|
||||
return leaseDuration;
|
||||
}
|
||||
|
||||
public boolean isPurgeResources() {
|
||||
return Boolean.TRUE.equals(purgeResources);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ public class ListCapabilitiesCmd extends BaseCmd {
|
|||
response.setInstancesDisksStatsRetentionTime((Integer) capabilities.get(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_TIME));
|
||||
response.setSharedFsVmMinCpuCount((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT));
|
||||
response.setSharedFsVmMinRamSize((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE));
|
||||
response.setInstanceLeaseEnabled((Boolean) capabilities.get(ApiConstants.INSTANCE_LEASE_ENABLED));
|
||||
response.setObjectName("capability");
|
||||
response.setResponseName(getCommandName());
|
||||
this.setResponseObject(response);
|
||||
|
|
|
|||
|
|
@ -16,17 +16,24 @@
|
|||
// under the License.
|
||||
package org.apache.cloudstack.api.command.user.vm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import com.cloud.agent.api.LogLevel;
|
||||
import com.cloud.event.EventTypes;
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.InsufficientServerCapacityException;
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||
import com.cloud.network.Network;
|
||||
import com.cloud.network.Network.IpAddresses;
|
||||
import com.cloud.offering.DiskOffering;
|
||||
import com.cloud.template.VirtualMachineTemplate;
|
||||
import com.cloud.uservm.UserVm;
|
||||
import com.cloud.utils.net.Dhcp;
|
||||
import com.cloud.utils.net.NetUtils;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.VmDetailConstants;
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.affinity.AffinityGroupResponse;
|
||||
import org.apache.cloudstack.api.ACL;
|
||||
|
|
@ -53,29 +60,22 @@ import org.apache.cloudstack.api.response.UserDataResponse;
|
|||
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||
import org.apache.cloudstack.api.response.ZoneResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.EnumUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.cloud.agent.api.LogLevel;
|
||||
import com.cloud.event.EventTypes;
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.InsufficientServerCapacityException;
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||
import com.cloud.network.Network;
|
||||
import com.cloud.network.Network.IpAddresses;
|
||||
import com.cloud.offering.DiskOffering;
|
||||
import com.cloud.template.VirtualMachineTemplate;
|
||||
import com.cloud.uservm.UserVm;
|
||||
import com.cloud.utils.net.Dhcp;
|
||||
import com.cloud.utils.net.NetUtils;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.VmDetailConstants;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@APICommand(name = "deployVirtualMachine", description = "Creates and automatically starts a virtual machine based on a service offering, disk offering, and template.", responseObject = UserVmResponse.class, responseView = ResponseView.Restricted, entityType = {VirtualMachine.class},
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = true)
|
||||
|
|
@ -278,6 +278,14 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG
|
|||
description = "Enable packed virtqueues or not.")
|
||||
private Boolean nicPackedVirtQueues;
|
||||
|
||||
@Parameter(name = ApiConstants.INSTANCE_LEASE_DURATION, type = CommandType.INTEGER, since = "4.21.0",
|
||||
description = "Number of days instance is leased for.")
|
||||
private Integer leaseDuration;
|
||||
|
||||
@Parameter(name = ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION, type = CommandType.STRING, since = "4.21.0",
|
||||
description = "Lease expiry action, valid values are STOP and DESTROY")
|
||||
private String leaseExpiryAction;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -475,6 +483,22 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG
|
|||
return password;
|
||||
}
|
||||
|
||||
public Integer getLeaseDuration() {
|
||||
return leaseDuration;
|
||||
}
|
||||
|
||||
public VMLeaseManager.ExpiryAction getLeaseExpiryAction() {
|
||||
if (StringUtils.isBlank(leaseExpiryAction)) {
|
||||
return null;
|
||||
}
|
||||
VMLeaseManager.ExpiryAction action = EnumUtils.getEnumIgnoreCase(VMLeaseManager.ExpiryAction.class, leaseExpiryAction);
|
||||
if (action == null) {
|
||||
throw new InvalidParameterValueException("Invalid value configured for leaseexpiryaction, valid values are: " +
|
||||
com.cloud.utils.EnumUtils.listValues(VMLeaseManager.ExpiryAction.values()));
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
public List<Long> getNetworkIds() {
|
||||
if (MapUtils.isNotEmpty(vAppNetworks)) {
|
||||
if (CollectionUtils.isNotEmpty(networkIds) || ipAddress != null || getIp6Address() != null || MapUtils.isNotEmpty(ipToNetworkList)) {
|
||||
|
|
|
|||
|
|
@ -160,6 +160,11 @@ public class ListVMsCmd extends BaseListRetrieveOnlyResourceCountCmd implements
|
|||
since = "4.20.1")
|
||||
private String arch;
|
||||
|
||||
@Parameter(name = ApiConstants.LEASED, type = CommandType.BOOLEAN,
|
||||
description = "Whether to return only leased instances",
|
||||
since = "4.21.0")
|
||||
private Boolean onlyLeasedInstances = false;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -303,6 +308,10 @@ public class ListVMsCmd extends BaseListRetrieveOnlyResourceCountCmd implements
|
|||
return StringUtils.isBlank(arch) ? null : CPU.CPUArch.fromType(arch);
|
||||
}
|
||||
|
||||
public boolean getOnlyLeasedInstances() {
|
||||
return BooleanUtils.toBoolean(onlyLeasedInstances);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -16,20 +16,19 @@
|
|||
// under the License.
|
||||
package org.apache.cloudstack.api.command.user.vm;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.uservm.UserVm;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
import org.apache.cloudstack.api.ApiArgValidator;
|
||||
import org.apache.cloudstack.api.response.UserDataResponse;
|
||||
|
||||
import com.cloud.utils.net.Dhcp;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
|
||||
import org.apache.cloudstack.api.ACL;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiArgValidator;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
|
|
@ -40,15 +39,17 @@ import org.apache.cloudstack.api.ServerApiException;
|
|||
import org.apache.cloudstack.api.command.user.UserCmd;
|
||||
import org.apache.cloudstack.api.response.GuestOSResponse;
|
||||
import org.apache.cloudstack.api.response.SecurityGroupResponse;
|
||||
import org.apache.cloudstack.api.response.UserDataResponse;
|
||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||
import org.apache.commons.lang3.EnumUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.uservm.UserVm;
|
||||
import com.cloud.utils.net.Dhcp;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@APICommand(name = "updateVirtualMachine", description="Updates properties of a virtual machine. The VM has to be stopped and restarted for the " +
|
||||
"new properties to take effect. UpdateVirtualMachine does not first check whether the VM is stopped. " +
|
||||
|
|
@ -154,6 +155,14 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction,
|
|||
" autoscaling groups or CKS, delete protection will be ignored.")
|
||||
private Boolean deleteProtection;
|
||||
|
||||
@Parameter(name = ApiConstants.INSTANCE_LEASE_DURATION, type = CommandType.INTEGER, since = "4.21.0",
|
||||
description = "Number of days to lease the instance from now onward. Use -1 to remove the existing lease")
|
||||
private Integer leaseDuration;
|
||||
|
||||
@Parameter(name = ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION, type = CommandType.STRING, since = "4.21.0",
|
||||
description = "Lease expiry action, valid values are STOP and DESTROY")
|
||||
private String leaseExpiryAction;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -324,4 +333,21 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction,
|
|||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.VirtualMachine;
|
||||
}
|
||||
|
||||
public Integer getLeaseDuration() {
|
||||
return leaseDuration;
|
||||
}
|
||||
|
||||
public VMLeaseManager.ExpiryAction getLeaseExpiryAction() {
|
||||
if (StringUtils.isBlank(leaseExpiryAction)) {
|
||||
return null;
|
||||
}
|
||||
VMLeaseManager.ExpiryAction action = EnumUtils.getEnumIgnoreCase(VMLeaseManager.ExpiryAction.class, leaseExpiryAction);
|
||||
if (action == null) {
|
||||
throw new InvalidParameterValueException("Invalid value configured for leaseexpiryaction, valid values are: " +
|
||||
com.cloud.utils.EnumUtils.listValues(VMLeaseManager.ExpiryAction.values()));
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,6 +136,10 @@ public class CapabilitiesResponse extends BaseResponse {
|
|||
@Param(description = "the min Ram size for the service offering used by the shared filesystem instance", since = "4.20.0")
|
||||
private Integer sharedFsVmMinRamSize;
|
||||
|
||||
@SerializedName(ApiConstants.INSTANCE_LEASE_ENABLED)
|
||||
@Param(description = "true if instance lease feature is enabled", since = "4.21.0")
|
||||
private Boolean instanceLeaseEnabled;
|
||||
|
||||
public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) {
|
||||
this.securityGroupsEnabled = securityGroupsEnabled;
|
||||
}
|
||||
|
|
@ -247,4 +251,8 @@ public class CapabilitiesResponse extends BaseResponse {
|
|||
public void setSharedFsVmMinRamSize(Integer sharedFsVmMinRamSize) {
|
||||
this.sharedFsVmMinRamSize = sharedFsVmMinRamSize;
|
||||
}
|
||||
|
||||
public void setInstanceLeaseEnabled(Boolean instanceLeaseEnabled) {
|
||||
this.instanceLeaseEnabled = instanceLeaseEnabled;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,6 +238,14 @@ public class ServiceOfferingResponse extends BaseResponseWithAnnotations {
|
|||
@Param(description = "Whether to cleanup VM and its associated resource upon expunge", since = "4.20")
|
||||
private Boolean purgeResources;
|
||||
|
||||
@SerializedName(ApiConstants.INSTANCE_LEASE_DURATION)
|
||||
@Param(description = "Instance lease duration (in days) for service offering", since = "4.21.0")
|
||||
private Integer leaseDuration;
|
||||
|
||||
@SerializedName(ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION)
|
||||
@Param(description = "Action to be taken once lease is over", since = "4.21.0")
|
||||
private String leaseExpiryAction;
|
||||
|
||||
public ServiceOfferingResponse() {
|
||||
}
|
||||
|
||||
|
|
@ -505,6 +513,22 @@ public class ServiceOfferingResponse extends BaseResponseWithAnnotations {
|
|||
this.cacheMode = cacheMode;
|
||||
}
|
||||
|
||||
public Integer getLeaseDuration() {
|
||||
return leaseDuration;
|
||||
}
|
||||
|
||||
public void setLeaseDuration(Integer leaseDuration) {
|
||||
this.leaseDuration = leaseDuration;
|
||||
}
|
||||
|
||||
public String getLeaseExpiryAction() {
|
||||
return leaseExpiryAction;
|
||||
}
|
||||
|
||||
public void setLeaseExpiryAction(String leaseExpiryAction) {
|
||||
this.leaseExpiryAction = leaseExpiryAction;
|
||||
}
|
||||
|
||||
public String getVsphereStoragePolicy() {
|
||||
return vsphereStoragePolicy;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -392,7 +392,7 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
|
|||
@Param(description = "VNF details", since = "4.19.0")
|
||||
private Map<String, String> vnfDetails;
|
||||
|
||||
@SerializedName((ApiConstants.VM_TYPE))
|
||||
@SerializedName(ApiConstants.VM_TYPE)
|
||||
@Param(description = "User VM type", since = "4.20.0")
|
||||
private String vmType;
|
||||
|
||||
|
|
@ -400,6 +400,18 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
|
|||
@Param(description = "CPU arch of the VM", since = "4.20.1")
|
||||
private String arch;
|
||||
|
||||
@SerializedName(ApiConstants.INSTANCE_LEASE_DURATION)
|
||||
@Param(description = "Instance lease duration in days", since = "4.21.0")
|
||||
private Integer leaseDuration;
|
||||
|
||||
@SerializedName(ApiConstants.INSTANCE_LEASE_EXPIRY_DATE)
|
||||
@Param(description = "Instance lease expiry date", since = "4.21.0")
|
||||
private Date leaseExpiryDate;
|
||||
|
||||
@SerializedName(ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION)
|
||||
@Param(description = "Instance lease expiry action", since = "4.21.0")
|
||||
private String leaseExpiryAction;
|
||||
|
||||
public UserVmResponse() {
|
||||
securityGroupList = new LinkedHashSet<>();
|
||||
nics = new TreeSet<>(Comparator.comparingInt(x -> Integer.parseInt(x.getDeviceId())));
|
||||
|
|
@ -1181,4 +1193,29 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
|
|||
public void setArch(String arch) {
|
||||
this.arch = arch;
|
||||
}
|
||||
|
||||
public Integer getLeaseDuration() {
|
||||
return leaseDuration;
|
||||
}
|
||||
|
||||
public void setLeaseDuration(Integer leaseDuration) {
|
||||
this.leaseDuration = leaseDuration;
|
||||
}
|
||||
|
||||
public String getLeaseExpiryAction() {
|
||||
return leaseExpiryAction;
|
||||
}
|
||||
|
||||
public void setLeaseExpiryAction(String leaseExpiryAction) {
|
||||
this.leaseExpiryAction = leaseExpiryAction;
|
||||
}
|
||||
|
||||
public Date getLeaseExpiryDate() {
|
||||
return leaseExpiryDate;
|
||||
}
|
||||
|
||||
public void setLeaseExpiryDate(Date leaseExpiryDate) {
|
||||
this.leaseExpiryDate = leaseExpiryDate;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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.lease;
|
||||
|
||||
import com.cloud.utils.component.Manager;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface VMLeaseManager extends Manager {
|
||||
|
||||
int MAX_LEASE_DURATION_DAYS = 365_00; // 100 years
|
||||
|
||||
enum ExpiryAction {
|
||||
STOP,
|
||||
DESTROY
|
||||
}
|
||||
|
||||
enum LeaseActionExecution {
|
||||
PENDING,
|
||||
DISABLED,
|
||||
DONE,
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
ConfigKey<Boolean> InstanceLeaseEnabled = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Boolean.class,
|
||||
"instance.lease.enabled", "false", "Indicates whether to enable the Instance lease," +
|
||||
" will be applicable only on instances created after lease is enabled. Disabling the feature cancels lease on existing instances with lease." +
|
||||
" Re-enabling feature will not cause lease expiry actions on grandfathered instances",
|
||||
true, List.of(ConfigKey.Scope.Global));
|
||||
|
||||
ConfigKey<Integer> InstanceLeaseSchedulerInterval = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class,
|
||||
"instance.lease.scheduler.interval", "3600", "VM Lease Scheduler interval in seconds",
|
||||
false, List.of(ConfigKey.Scope.Global));
|
||||
|
||||
ConfigKey<Integer> InstanceLeaseExpiryEventSchedulerInterval = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class,
|
||||
"instance.lease.eventscheduler.interval", "86400", "Lease expiry event Scheduler interval in seconds",
|
||||
false, List.of(ConfigKey.Scope.Global));
|
||||
|
||||
ConfigKey<Integer> InstanceLeaseExpiryEventDaysBefore = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class,
|
||||
"instance.lease.expiryevent.daysbefore", "7", "Indicates how many days in advance, expiry events will be created before expiry.",
|
||||
true, List.of(ConfigKey.Scope.Global));
|
||||
|
||||
void onLeaseFeatureToggle();
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package org.apache.cloudstack.api.command.admin.offering;
|
||||
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
|
@ -55,4 +57,25 @@ public class CreateServiceOfferingCmdTest {
|
|||
ReflectionTestUtils.setField(createServiceOfferingCmd, "purgeResources", true);
|
||||
Assert.assertTrue(createServiceOfferingCmd.isPurgeResources());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLeaseDuration() {
|
||||
ReflectionTestUtils.setField(createServiceOfferingCmd, "leaseDuration", 10);
|
||||
Assert.assertEquals(10, createServiceOfferingCmd.getLeaseDuration().longValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLeaseExpiryAction() {
|
||||
ReflectionTestUtils.setField(createServiceOfferingCmd, "leaseExpiryAction", "stop");
|
||||
Assert.assertEquals(VMLeaseManager.ExpiryAction.STOP, createServiceOfferingCmd.getLeaseExpiryAction());
|
||||
|
||||
ReflectionTestUtils.setField(createServiceOfferingCmd, "leaseExpiryAction", "DESTROY");
|
||||
Assert.assertEquals(VMLeaseManager.ExpiryAction.DESTROY, createServiceOfferingCmd.getLeaseExpiryAction());
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testGetLeaseExpiryActionInvalidValue() {
|
||||
ReflectionTestUtils.setField(createServiceOfferingCmd, "leaseExpiryAction", "Unknown");
|
||||
Assert.assertEquals(null, createServiceOfferingCmd.getLeaseExpiryAction());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ SELECT
|
|||
`service_offering`.`dynamic_scaling_enabled` AS `dynamic_scaling_enabled`,
|
||||
`service_offering`.`disk_offering_strictness` AS `disk_offering_strictness`,
|
||||
`vsphere_storage_policy`.`value` AS `vsphere_storage_policy`,
|
||||
`lease_duration_details`.`value` AS `lease_duration`,
|
||||
`lease_expiry_action_details`.`value` AS `lease_expiry_action`,
|
||||
GROUP_CONCAT(DISTINCT(domain.id)) AS domain_id,
|
||||
GROUP_CONCAT(DISTINCT(domain.uuid)) AS domain_uuid,
|
||||
GROUP_CONCAT(DISTINCT(domain.name)) AS domain_name,
|
||||
|
|
@ -109,5 +111,11 @@ FROM
|
|||
LEFT JOIN
|
||||
`cloud`.`service_offering_details` AS `vsphere_storage_policy` ON `vsphere_storage_policy`.`service_offering_id` = `service_offering`.`id`
|
||||
AND `vsphere_storage_policy`.`name` = 'storagepolicy'
|
||||
LEFT JOIN
|
||||
`cloud`.`service_offering_details` AS `lease_duration_details` ON `lease_duration_details`.`service_offering_id` = `service_offering`.`id`
|
||||
AND `lease_duration_details`.`name` = 'leaseduration'
|
||||
LEFT JOIN
|
||||
`cloud`.`service_offering_details` AS `lease_expiry_action_details` ON `lease_expiry_action_details`.`service_offering_id` = `service_offering`.`id`
|
||||
AND `lease_expiry_action_details`.`name` = 'leaseexpiryaction'
|
||||
GROUP BY
|
||||
`service_offering`.`id`;
|
||||
|
|
|
|||
|
|
@ -169,7 +169,10 @@ SELECT
|
|||
`user_data`.`uuid` AS `user_data_uuid`,
|
||||
`user_data`.`name` AS `user_data_name`,
|
||||
`user_vm`.`user_data_details` AS `user_data_details`,
|
||||
`vm_template`.`user_data_link_policy` AS `user_data_policy`
|
||||
`vm_template`.`user_data_link_policy` AS `user_data_policy`,
|
||||
`lease_expiry_date`.`value` AS `lease_expiry_date`,
|
||||
`lease_expiry_action`.`value` AS `lease_expiry_action`,
|
||||
`lease_action_execution`.`value` AS `lease_action_execution`
|
||||
FROM
|
||||
(((((((((((((((((((((((((((((((((((`user_vm`
|
||||
JOIN `vm_instance` ON (((`vm_instance`.`id` = `user_vm`.`id`)
|
||||
|
|
@ -216,4 +219,10 @@ FROM
|
|||
LEFT JOIN `user_vm_details` `custom_speed` ON (((`custom_speed`.`vm_id` = `vm_instance`.`id`)
|
||||
AND (`custom_speed`.`name` = 'CpuSpeed'))))
|
||||
LEFT JOIN `user_vm_details` `custom_ram_size` ON (((`custom_ram_size`.`vm_id` = `vm_instance`.`id`)
|
||||
AND (`custom_ram_size`.`name` = 'memory'))));
|
||||
AND (`custom_ram_size`.`name` = 'memory')))
|
||||
LEFT JOIN `user_vm_details` `lease_expiry_date` ON ((`lease_expiry_date`.`vm_id` = `vm_instance`.`id`)
|
||||
AND (`lease_expiry_date`.`name` = 'leaseexpirydate'))
|
||||
LEFT JOIN `user_vm_details` `lease_action_execution` ON ((`lease_action_execution`.`vm_id` = `vm_instance`.`id`)
|
||||
AND (`lease_action_execution`.`name` = 'leaseactionexecution'))
|
||||
LEFT JOIN `user_vm_details` `lease_expiry_action` ON (((`lease_expiry_action`.`vm_id` = `vm_instance`.`id`)
|
||||
AND (`lease_expiry_action`.`name` = 'leaseexpiryaction'))));
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
|
|||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
|
||||
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang3.EnumUtils;
|
||||
|
|
@ -1355,6 +1356,11 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||
}
|
||||
}
|
||||
|
||||
if (!VMLeaseManager.InstanceLeaseEnabled.value() && cmd.getOnlyLeasedInstances()) {
|
||||
throw new InvalidParameterValueException(" Cannot list leased instances because the Instance Lease feature " +
|
||||
"is disabled, please enable it to list leased instances");
|
||||
}
|
||||
|
||||
Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(cmd.getDomainId(), cmd.isRecursive(), null);
|
||||
accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, listAll, false);
|
||||
Long domainId = domainIdRecursiveListProject.first();
|
||||
|
|
@ -1508,6 +1514,14 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||
userVmSearchBuilder.join("tags", resourceTagSearch, resourceTagSearch.entity().getResourceId(), userVmSearchBuilder.entity().getId(), JoinBuilder.JoinType.INNER);
|
||||
}
|
||||
|
||||
if (cmd.getOnlyLeasedInstances()) {
|
||||
SearchBuilder<UserVmDetailVO> leasedInstancesSearch = userVmDetailsDao.createSearchBuilder();
|
||||
leasedInstancesSearch.and(leasedInstancesSearch.entity().getName(), SearchCriteria.Op.EQ).values(VmDetailConstants.INSTANCE_LEASE_EXECUTION);
|
||||
leasedInstancesSearch.and(leasedInstancesSearch.entity().getValue(), SearchCriteria.Op.EQ).values(VMLeaseManager.LeaseActionExecution.PENDING.name());
|
||||
userVmSearchBuilder.join("userVmToLeased", leasedInstancesSearch, leasedInstancesSearch.entity().getResourceId(),
|
||||
userVmSearchBuilder.entity().getId(), JoinBuilder.JoinType.INNER);
|
||||
}
|
||||
|
||||
if (keyPairName != null) {
|
||||
SearchBuilder<UserVmDetailVO> vmDetailSearchKeys = userVmDetailsDao.createSearchBuilder();
|
||||
SearchBuilder<UserVmDetailVO> vmDetailSearchVmIds = userVmDetailsDao.createSearchBuilder();
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import org.apache.cloudstack.api.ApiConstants;
|
|||
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
|
@ -176,6 +177,11 @@ public class ServiceOfferingJoinDaoImpl extends GenericDaoBase<ServiceOfferingJo
|
|||
}
|
||||
}
|
||||
|
||||
if (VMLeaseManager.InstanceLeaseEnabled.value() && offering.getLeaseDuration() != null && offering.getLeaseDuration() > 0L) {
|
||||
offeringResponse.setLeaseDuration(offering.getLeaseDuration());
|
||||
offeringResponse.setLeaseExpiryAction(offering.getLeaseExpiryAction().name());
|
||||
}
|
||||
|
||||
long rootDiskSizeInGb = (long) offering.getRootDiskSize() / GB_TO_BYTES;
|
||||
offeringResponse.setRootDiskSize(rootDiskSizeInGb);
|
||||
offeringResponse.setDiskOfferingStrictness(offering.getDiskOfferingStrictness());
|
||||
|
|
|
|||
|
|
@ -16,18 +16,17 @@
|
|||
// under the License.
|
||||
package com.cloud.api.query.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants.VMDetails;
|
||||
import org.apache.cloudstack.api.ResponseObject.ResponseView;
|
||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||
|
||||
import com.cloud.api.query.vo.UserVmJoinVO;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.uservm.UserVm;
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import org.apache.cloudstack.api.ApiConstants.VMDetails;
|
||||
import org.apache.cloudstack.api.ResponseObject.ResponseView;
|
||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public interface UserVmJoinDao extends GenericDao<UserVmJoinVO, Long> {
|
||||
|
||||
|
|
@ -46,4 +45,8 @@ public interface UserVmJoinDao extends GenericDao<UserVmJoinVO, Long> {
|
|||
|
||||
List<UserVmJoinVO> listByAccountServiceOfferingTemplateAndNotInState(long accountId,
|
||||
List<VirtualMachine.State> states, List<Long> offeringIds, List<Long> templateIds);
|
||||
|
||||
List<UserVmJoinVO> listEligibleInstancesWithExpiredLease();
|
||||
|
||||
List<UserVmJoinVO> listLeaseInstancesExpiringInDays(int days);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,38 +16,6 @@
|
|||
// under the License.
|
||||
package com.cloud.api.query.dao;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.affinity.AffinityGroupResponse;
|
||||
import org.apache.cloudstack.annotation.AnnotationService;
|
||||
import org.apache.cloudstack.annotation.dao.AnnotationDao;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiConstants.VMDetails;
|
||||
import org.apache.cloudstack.api.ResponseObject.ResponseView;
|
||||
import org.apache.cloudstack.api.response.NicExtraDhcpOptionResponse;
|
||||
import org.apache.cloudstack.api.response.NicResponse;
|
||||
import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
|
||||
import org.apache.cloudstack.api.response.SecurityGroupResponse;
|
||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||
import org.apache.cloudstack.api.response.VnfNicResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.query.QueryService;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.api.ApiDBUtils;
|
||||
import com.cloud.api.ApiResponseHelper;
|
||||
import com.cloud.api.query.vo.UserVmJoinVO;
|
||||
|
|
@ -84,6 +52,42 @@ import com.cloud.vm.VmStats;
|
|||
import com.cloud.vm.dao.NicExtraDhcpOptionDao;
|
||||
import com.cloud.vm.dao.NicSecondaryIpVO;
|
||||
import com.cloud.vm.dao.UserVmDetailsDao;
|
||||
import org.apache.cloudstack.affinity.AffinityGroupResponse;
|
||||
import org.apache.cloudstack.annotation.AnnotationService;
|
||||
import org.apache.cloudstack.annotation.dao.AnnotationDao;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiConstants.VMDetails;
|
||||
import org.apache.cloudstack.api.ResponseObject.ResponseView;
|
||||
import org.apache.cloudstack.api.response.NicExtraDhcpOptionResponse;
|
||||
import org.apache.cloudstack.api.response.NicResponse;
|
||||
import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
|
||||
import org.apache.cloudstack.api.response.SecurityGroupResponse;
|
||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||
import org.apache.cloudstack.api.response.VnfNicResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.query.QueryService;
|
||||
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJoinVO, UserVmResponse> implements UserVmJoinDao {
|
||||
|
|
@ -108,9 +112,13 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
|
|||
VnfTemplateDetailsDao vnfTemplateDetailsDao;
|
||||
@Inject
|
||||
VnfTemplateNicDao vnfTemplateNicDao;
|
||||
@Inject
|
||||
ConfigurationDao configurationDao;
|
||||
|
||||
private final SearchBuilder<UserVmJoinVO> VmDetailSearch;
|
||||
private final SearchBuilder<UserVmJoinVO> activeVmByIsoSearch;
|
||||
private final SearchBuilder<UserVmJoinVO> leaseExpiredInstanceSearch;
|
||||
private final SearchBuilder<UserVmJoinVO> remainingLeaseInDaysSearch;
|
||||
|
||||
protected UserVmJoinDaoImpl() {
|
||||
|
||||
|
|
@ -124,6 +132,29 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
|
|||
activeVmByIsoSearch.and("isoId", activeVmByIsoSearch.entity().getIsoId(), SearchCriteria.Op.EQ);
|
||||
activeVmByIsoSearch.and("stateNotIn", activeVmByIsoSearch.entity().getState(), SearchCriteria.Op.NIN);
|
||||
activeVmByIsoSearch.done();
|
||||
|
||||
leaseExpiredInstanceSearch = createSearchBuilder();
|
||||
leaseExpiredInstanceSearch.selectFields(leaseExpiredInstanceSearch.entity().getId(), leaseExpiredInstanceSearch.entity().getState(),
|
||||
leaseExpiredInstanceSearch.entity().isDeleteProtection(), leaseExpiredInstanceSearch.entity().getName(),
|
||||
leaseExpiredInstanceSearch.entity().getUuid(), leaseExpiredInstanceSearch.entity().getLeaseExpiryAction());
|
||||
|
||||
leaseExpiredInstanceSearch.and(leaseExpiredInstanceSearch.entity().getLeaseActionExecution(), Op.EQ).values(VMLeaseManager.LeaseActionExecution.PENDING.name());
|
||||
leaseExpiredInstanceSearch.and("leaseExpired", leaseExpiredInstanceSearch.entity().getLeaseExpiryDate(), Op.LT);
|
||||
leaseExpiredInstanceSearch.and("leaseExpiryActions", leaseExpiredInstanceSearch.entity().getLeaseExpiryAction(), Op.IN);
|
||||
leaseExpiredInstanceSearch.and("instanceStateNotIn", leaseExpiredInstanceSearch.entity().getState(), Op.NOTIN);
|
||||
leaseExpiredInstanceSearch.done();
|
||||
|
||||
remainingLeaseInDaysSearch = createSearchBuilder();
|
||||
remainingLeaseInDaysSearch.selectFields(remainingLeaseInDaysSearch.entity().getId(),
|
||||
remainingLeaseInDaysSearch.entity().getUuid(), remainingLeaseInDaysSearch.entity().getName(),
|
||||
remainingLeaseInDaysSearch.entity().getUserId(), remainingLeaseInDaysSearch.entity().getDomainId(),
|
||||
remainingLeaseInDaysSearch.entity().getAccountId(), remainingLeaseInDaysSearch.entity().getLeaseExpiryAction());
|
||||
|
||||
remainingLeaseInDaysSearch.and(remainingLeaseInDaysSearch.entity().getLeaseActionExecution(), Op.EQ).values(VMLeaseManager.LeaseActionExecution.PENDING.name());
|
||||
remainingLeaseInDaysSearch.and("leaseCurrentDate", remainingLeaseInDaysSearch.entity().getLeaseExpiryDate(), Op.GTEQ);
|
||||
remainingLeaseInDaysSearch.and("leaseExpiryEndDate", remainingLeaseInDaysSearch.entity().getLeaseExpiryDate(), Op.LT);
|
||||
remainingLeaseInDaysSearch.done();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -427,10 +458,10 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
|
|||
userVmResponse.setDynamicallyScalable(userVm.isDynamicallyScalable());
|
||||
}
|
||||
|
||||
if (userVm.getDeleteProtection() == null) {
|
||||
if (userVm.isDeleteProtection() == null) {
|
||||
userVmResponse.setDeleteProtection(false);
|
||||
} else {
|
||||
userVmResponse.setDeleteProtection(userVm.getDeleteProtection());
|
||||
userVmResponse.setDeleteProtection(userVm.isDeleteProtection());
|
||||
}
|
||||
|
||||
if (userVm.getAutoScaleVmGroupName() != null) {
|
||||
|
|
@ -447,6 +478,15 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
|
|||
userVmResponse.setUserDataPolicy(userVm.getUserDataPolicy());
|
||||
}
|
||||
|
||||
if (VMLeaseManager.InstanceLeaseEnabled.value() && userVm.getLeaseExpiryDate() != null &&
|
||||
VMLeaseManager.LeaseActionExecution.PENDING.name().equals(userVm.getLeaseActionExecution())) {
|
||||
|
||||
userVmResponse.setLeaseExpiryAction(userVm.getLeaseExpiryAction());
|
||||
userVmResponse.setLeaseExpiryDate(userVm.getLeaseExpiryDate());
|
||||
int leaseDuration = (int) computeLeaseDurationFromExpiryDate(new Date(), userVm.getLeaseExpiryDate());
|
||||
userVmResponse.setLeaseDuration(leaseDuration);
|
||||
}
|
||||
|
||||
addVmRxTxDataToResponse(userVm, userVmResponse);
|
||||
|
||||
if (TemplateType.VNF.equals(userVm.getTemplateType()) && (details.contains(VMDetails.all) || details.contains(VMDetails.vnfnics))) {
|
||||
|
|
@ -456,6 +496,13 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
|
|||
return userVmResponse;
|
||||
}
|
||||
|
||||
|
||||
private long computeLeaseDurationFromExpiryDate(Date created, Date leaseExpiryDate) {
|
||||
LocalDate createdDate = created.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||
LocalDate expiryDate = leaseExpiryDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||
return ChronoUnit.DAYS.between(createdDate, expiryDate);
|
||||
}
|
||||
|
||||
private void addVnfInfoToserVmResponse(UserVmJoinVO userVm, UserVmResponse userVmResponse) {
|
||||
List<VnfTemplateNicVO> vnfNics = vnfTemplateNicDao.listByTemplateId(userVm.getTemplateId());
|
||||
for (VnfTemplateNicVO nic : vnfNics) {
|
||||
|
|
@ -718,4 +765,43 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
|
|||
sc.setParameters("displayVm", 1);
|
||||
return customSearch(sc, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fetches instances where
|
||||
* 1. lease has expired
|
||||
* 2. leaseExpiryActions are valid, either STOP or DESTROY
|
||||
* 3. instance State is eligible for expiry action
|
||||
* @return list of instances, expiry action can be executed on
|
||||
*/
|
||||
@Override
|
||||
public List<UserVmJoinVO> listEligibleInstancesWithExpiredLease() {
|
||||
SearchCriteria<UserVmJoinVO> sc = leaseExpiredInstanceSearch.create();
|
||||
sc.setParameters("leaseExpired", new Date());
|
||||
sc.setParameters("leaseExpiryActions", VMLeaseManager.ExpiryAction.STOP.name(), VMLeaseManager.ExpiryAction.DESTROY.name());
|
||||
sc.setParameters("instanceStateNotIn", State.Destroyed, State.Expunging, State.Error, State.Unknown, State.Migrating);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method will return instances which are expiring within days
|
||||
* in case negative value is given, there won't be any endDate
|
||||
*
|
||||
* @param days
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<UserVmJoinVO> listLeaseInstancesExpiringInDays(int days) {
|
||||
SearchCriteria<UserVmJoinVO> sc = remainingLeaseInDaysSearch.create();
|
||||
Date currentDate = new Date();
|
||||
sc.setParameters("leaseCurrentDate", currentDate);
|
||||
if (days > 0) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(currentDate);
|
||||
calendar.add(Calendar.DAY_OF_MONTH, days);
|
||||
Date nextDate = calendar.getTime();
|
||||
sc.setParameters("leaseExpiryEndDate", nextDate);
|
||||
}
|
||||
return listBy(sc);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,12 @@
|
|||
// under the License.
|
||||
package com.cloud.api.query.vo;
|
||||
|
||||
import java.util.Date;
|
||||
import com.cloud.offering.ServiceOffering.State;
|
||||
import com.cloud.storage.Storage;
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.api.Identity;
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
|
|
@ -24,13 +29,7 @@ import javax.persistence.EnumType;
|
|||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import com.cloud.offering.ServiceOffering.State;
|
||||
import org.apache.cloudstack.api.Identity;
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
|
||||
import com.cloud.storage.Storage;
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import java.util.Date;
|
||||
|
||||
@Entity
|
||||
@Table(name = "service_offering_view")
|
||||
|
|
@ -221,6 +220,13 @@ public class ServiceOfferingJoinVO extends BaseViewVO implements InternalIdentit
|
|||
@Column(name = "encrypt_root")
|
||||
private boolean encryptRoot;
|
||||
|
||||
@Column(name = "lease_duration")
|
||||
private Integer leaseDuration;
|
||||
|
||||
@Column(name = "lease_expiry_action")
|
||||
@Enumerated(value = EnumType.STRING)
|
||||
private VMLeaseManager.ExpiryAction leaseExpiryAction;
|
||||
|
||||
public ServiceOfferingJoinVO() {
|
||||
}
|
||||
|
||||
|
|
@ -459,4 +465,12 @@ public class ServiceOfferingJoinVO extends BaseViewVO implements InternalIdentit
|
|||
}
|
||||
|
||||
public boolean getEncryptRoot() { return encryptRoot; }
|
||||
|
||||
public Integer getLeaseDuration() {
|
||||
return leaseDuration;
|
||||
}
|
||||
|
||||
public VMLeaseManager.ExpiryAction getLeaseExpiryAction() {
|
||||
return leaseExpiryAction;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import javax.persistence.EnumType;
|
|||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
import com.cloud.host.Status;
|
||||
|
|
@ -442,6 +444,15 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro
|
|||
@Column(name = "arch")
|
||||
protected String arch;
|
||||
|
||||
@Column(name = "lease_expiry_date")
|
||||
@Temporal(value = TemporalType.TIMESTAMP)
|
||||
private Date leaseExpiryDate;
|
||||
|
||||
@Column(name = "lease_expiry_action")
|
||||
private String leaseExpiryAction;
|
||||
|
||||
@Column(name = "lease_action_execution")
|
||||
private String leaseActionExecution;
|
||||
|
||||
public UserVmJoinVO() {
|
||||
// Empty constructor
|
||||
|
|
@ -952,7 +963,7 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro
|
|||
return isDynamicallyScalable;
|
||||
}
|
||||
|
||||
public Boolean getDeleteProtection() {
|
||||
public Boolean isDeleteProtection() {
|
||||
return deleteProtection;
|
||||
}
|
||||
|
||||
|
|
@ -984,4 +995,20 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro
|
|||
public String getArch() {
|
||||
return arch;
|
||||
}
|
||||
|
||||
public Date getLeaseExpiryDate() {
|
||||
return leaseExpiryDate;
|
||||
}
|
||||
|
||||
public String getLeaseExpiryAction() {
|
||||
return leaseExpiryAction;
|
||||
}
|
||||
|
||||
public void setLeaseExpiryAction(String leaseExpiryAction) {
|
||||
this.leaseExpiryAction = leaseExpiryAction;
|
||||
}
|
||||
|
||||
public String getLeaseActionExecution() {
|
||||
return leaseActionExecution;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ import org.apache.cloudstack.userdata.UserDataManager;
|
|||
import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper;
|
||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||
import org.apache.cloudstack.vm.UnmanagedVMsManager;
|
||||
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang3.EnumUtils;
|
||||
|
|
@ -479,6 +480,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||
NsxProviderDao nsxProviderDao;
|
||||
@Inject
|
||||
ResourceManager resourceManager;
|
||||
@Inject
|
||||
VMLeaseManager vmLeaseManager;
|
||||
|
||||
// FIXME - why don't we have interface for DataCenterLinkLocalIpAddressDao?
|
||||
@Inject
|
||||
|
|
@ -586,6 +589,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||
configValuesForValidation.add(UserDataManager.VM_USERDATA_MAX_LENGTH_STRING);
|
||||
configValuesForValidation.add(UnmanagedVMsManager.RemoteKvmInstanceDisksCopyTimeout.key());
|
||||
configValuesForValidation.add(UnmanagedVMsManager.ConvertVmwareInstanceToKvmTimeout.key());
|
||||
configValuesForValidation.add(VMLeaseManager.InstanceLeaseSchedulerInterval.key());
|
||||
configValuesForValidation.add(VMLeaseManager.InstanceLeaseExpiryEventSchedulerInterval.key());
|
||||
configValuesForValidation.add(VMLeaseManager.InstanceLeaseExpiryEventDaysBefore.key());
|
||||
}
|
||||
|
||||
protected void weightBasedParametersForValidation() {
|
||||
|
|
@ -639,6 +645,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||
params.put(Config.RouterAggregationCommandEachTimeout.toString(), _configDao.getValue(Config.RouterAggregationCommandEachTimeout.toString()));
|
||||
params.put(Config.MigrateWait.toString(), _configDao.getValue(Config.MigrateWait.toString()));
|
||||
_agentManager.propagateChangeToAgents(params);
|
||||
} else if (VMLeaseManager.InstanceLeaseEnabled.key().equals(globalSettingUpdated)) {
|
||||
vmLeaseManager.onLeaseFeatureToggle();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -3382,6 +3390,10 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||
}
|
||||
}
|
||||
|
||||
// validate lease properties and set leaseExpiryAction
|
||||
Integer leaseDuration = cmd.getLeaseDuration();
|
||||
VMLeaseManager.ExpiryAction leaseExpiryAction = validateAndGetLeaseExpiryAction(leaseDuration, cmd.getLeaseExpiryAction());
|
||||
|
||||
return createServiceOffering(userId, cmd.isSystem(), vmType, cmd.getServiceOfferingName(), cpuNumber, memory, cpuSpeed, cmd.getDisplayText(),
|
||||
cmd.getProvisioningType(), localStorageRequired, offerHA, limitCpuUse, volatileVm, cmd.getTags(), cmd.getDomainIds(), cmd.getZoneIds(), cmd.getHostTag(),
|
||||
cmd.getNetworkRate(), cmd.getDeploymentPlanner(), details, cmd.getRootDiskSize(), isCustomizedIops, cmd.getMinIops(), cmd.getMaxIops(),
|
||||
|
|
@ -3390,20 +3402,20 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||
cmd.getIopsReadRate(), cmd.getIopsReadRateMax(), cmd.getIopsReadRateMaxLength(),
|
||||
cmd.getIopsWriteRate(), cmd.getIopsWriteRateMax(), cmd.getIopsWriteRateMaxLength(),
|
||||
cmd.getHypervisorSnapshotReserve(), cmd.getCacheMode(), storagePolicyId, cmd.getDynamicScalingEnabled(), diskOfferingId,
|
||||
cmd.getDiskOfferingStrictness(), cmd.isCustomized(), cmd.getEncryptRoot(), cmd.isPurgeResources());
|
||||
cmd.getDiskOfferingStrictness(), cmd.isCustomized(), cmd.getEncryptRoot(), cmd.isPurgeResources(), leaseDuration, leaseExpiryAction);
|
||||
}
|
||||
|
||||
protected ServiceOfferingVO createServiceOffering(final long userId, final boolean isSystem, final VirtualMachine.Type vmType,
|
||||
final String name, final Integer cpu, final Integer ramSize, final Integer speed, final String displayText, final String provisioningType, final boolean localStorageRequired,
|
||||
final boolean offerHA, final boolean limitResourceUse, final boolean volatileVm, String tags, final List<Long> domainIds, List<Long> zoneIds, final String hostTag,
|
||||
final Integer networkRate, final String deploymentPlanner, final Map<String, String> details, Long rootDiskSizeInGiB, final Boolean isCustomizedIops, Long minIops, Long maxIops,
|
||||
Long bytesReadRate, Long bytesReadRateMax, Long bytesReadRateMaxLength,
|
||||
Long bytesWriteRate, Long bytesWriteRateMax, Long bytesWriteRateMaxLength,
|
||||
Long iopsReadRate, Long iopsReadRateMax, Long iopsReadRateMaxLength,
|
||||
Long iopsWriteRate, Long iopsWriteRateMax, Long iopsWriteRateMaxLength,
|
||||
final Integer hypervisorSnapshotReserve, String cacheMode, final Long storagePolicyID,
|
||||
final boolean dynamicScalingEnabled, final Long diskOfferingId, final boolean diskOfferingStrictness,
|
||||
final boolean isCustomized, final boolean encryptRoot, final boolean purgeResources) {
|
||||
final String name, final Integer cpu, final Integer ramSize, final Integer speed, final String displayText, final String provisioningType, final boolean localStorageRequired,
|
||||
final boolean offerHA, final boolean limitResourceUse, final boolean volatileVm, String tags, final List<Long> domainIds, List<Long> zoneIds, final String hostTag,
|
||||
final Integer networkRate, final String deploymentPlanner, final Map<String, String> details, Long rootDiskSizeInGiB, final Boolean isCustomizedIops, Long minIops, Long maxIops,
|
||||
Long bytesReadRate, Long bytesReadRateMax, Long bytesReadRateMaxLength,
|
||||
Long bytesWriteRate, Long bytesWriteRateMax, Long bytesWriteRateMaxLength,
|
||||
Long iopsReadRate, Long iopsReadRateMax, Long iopsReadRateMaxLength,
|
||||
Long iopsWriteRate, Long iopsWriteRateMax, Long iopsWriteRateMaxLength,
|
||||
final Integer hypervisorSnapshotReserve, String cacheMode, final Long storagePolicyID,
|
||||
final boolean dynamicScalingEnabled, final Long diskOfferingId, final boolean diskOfferingStrictness,
|
||||
final boolean isCustomized, final boolean encryptRoot, final boolean purgeResources, Integer leaseDuration, VMLeaseManager.ExpiryAction leaseExpiryAction) {
|
||||
|
||||
// Filter child domains when both parent and child domains are present
|
||||
List<Long> filteredDomainIds = filterChildSubDomains(domainIds);
|
||||
|
|
@ -3510,6 +3522,12 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||
}
|
||||
|
||||
if ((serviceOffering = _serviceOfferingDao.persist(serviceOffering)) != null) {
|
||||
//persist lease properties if leaseExpiryAction is valid
|
||||
if (leaseExpiryAction != null) {
|
||||
detailsVOList.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), ApiConstants.INSTANCE_LEASE_DURATION, String.valueOf(leaseDuration), false));
|
||||
detailsVOList.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION, leaseExpiryAction.name(), false));
|
||||
}
|
||||
|
||||
for (Long domainId : filteredDomainIds) {
|
||||
detailsVOList.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), ApiConstants.DOMAIN_ID, String.valueOf(domainId), false));
|
||||
}
|
||||
|
|
@ -3533,6 +3551,31 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return valid and non-empty expiryAction when
|
||||
* "instance.lease.enabled" feature is enabled at global level
|
||||
* leaseDuration is positive > 0 and has valid leaseExpiryAction provided
|
||||
* @param leaseDuration
|
||||
* @param cmdExpiryAction
|
||||
* @return leaseExpiryAction
|
||||
*/
|
||||
public static VMLeaseManager.ExpiryAction validateAndGetLeaseExpiryAction(Integer leaseDuration, VMLeaseManager.ExpiryAction cmdExpiryAction) {
|
||||
if (!VMLeaseManager.InstanceLeaseEnabled.value() || ObjectUtils.allNull(leaseDuration, cmdExpiryAction)) { // both are null
|
||||
return null;
|
||||
}
|
||||
|
||||
// one of them is non-null
|
||||
if (ObjectUtils.anyNull(leaseDuration, cmdExpiryAction)) {
|
||||
throw new InvalidParameterValueException("Provide values for both: leaseduration and leaseexpiryaction");
|
||||
}
|
||||
|
||||
if (leaseDuration < 1L || leaseDuration > VMLeaseManager.MAX_LEASE_DURATION_DAYS) {
|
||||
throw new InvalidParameterValueException("Invalid leaseduration: must be a natural number (>=1), max supported value is 36500");
|
||||
}
|
||||
|
||||
return cmdExpiryAction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateExtraConfigInServiceOfferingDetail(String detailName) {
|
||||
if (!detailName.equals(DpdkHelper.DPDK_NUMA) && !detailName.equals(DpdkHelper.DPDK_HUGE_PAGES)
|
||||
|
|
|
|||
|
|
@ -644,6 +644,7 @@ import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
|
|||
import org.apache.cloudstack.userdata.UserDataManager;
|
||||
import org.apache.cloudstack.utils.CloudStackVersion;
|
||||
import org.apache.cloudstack.utils.identity.ManagementServerNode;
|
||||
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
|
@ -4565,6 +4566,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
|||
capabilities.put(ApiConstants.INSTANCES_STATS_USER_ONLY, StatsCollector.vmStatsCollectUserVMOnly.value());
|
||||
capabilities.put(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_ENABLED, StatsCollector.vmDiskStatsRetentionEnabled.value());
|
||||
capabilities.put(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_TIME, StatsCollector.vmDiskStatsMaxRetentionTime.value());
|
||||
capabilities.put(ApiConstants.INSTANCE_LEASE_ENABLED, VMLeaseManager.InstanceLeaseEnabled.value());
|
||||
if (apiLimitEnabled) {
|
||||
capabilities.put("apiLimitInterval", apiLimitInterval);
|
||||
capabilities.put("apiLimitMax", apiLimitMax);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ import java.io.IOException;
|
|||
import java.io.StringReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
|
@ -39,6 +42,7 @@ import java.util.Map;
|
|||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
|
@ -136,11 +140,13 @@ import org.apache.cloudstack.storage.template.VnfTemplateManager;
|
|||
import org.apache.cloudstack.userdata.UserDataManager;
|
||||
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
|
||||
import org.apache.cloudstack.utils.security.ParserUtils;
|
||||
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||
import org.apache.cloudstack.vm.UnmanagedVMsManager;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang.math.NumberUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
|
@ -2870,6 +2876,13 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
} else {
|
||||
if (MapUtils.isNotEmpty(details)) {
|
||||
// error out if lease related keys are passed in details
|
||||
if (details.containsKey(VmDetailConstants.INSTANCE_LEASE_EXECUTION)
|
||||
|| details.containsKey(VmDetailConstants.INSTANCE_LEASE_EXPIRY_DATE)
|
||||
|| details.containsKey(VmDetailConstants.INSTANCE_LEASE_EXPIRY_ACTION)) {
|
||||
throw new InvalidParameterValueException("lease parameters should not be included in details as key");
|
||||
}
|
||||
|
||||
if (details.containsKey("extraconfig")) {
|
||||
throw new InvalidParameterValueException("'extraconfig' should not be included in details as key");
|
||||
}
|
||||
|
|
@ -2917,6 +2930,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (VMLeaseManager.InstanceLeaseEnabled.value() && cmd.getLeaseDuration() != null) {
|
||||
applyLeaseOnUpdateInstance(vmInstance, cmd.getLeaseDuration(), cmd.getLeaseExpiryAction());
|
||||
}
|
||||
|
||||
return updateVirtualMachine(id, displayName, group, ha, isDisplayVm,
|
||||
cmd.getDeleteProtection(), osTypeId, userData,
|
||||
userDataId, userDataDetails, isDynamicallyScalable, cmd.getHttpMethod(),
|
||||
|
|
@ -6170,6 +6188,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
}
|
||||
|
||||
boolean isLeaseFeatureEnabled = VMLeaseManager.InstanceLeaseEnabled.value();
|
||||
if (isLeaseFeatureEnabled) {
|
||||
validateLeaseProperties(cmd.getLeaseDuration(), cmd.getLeaseExpiryAction());
|
||||
}
|
||||
|
||||
List<Long> networkIds = cmd.getNetworkIds();
|
||||
LinkedHashMap<Integer, Long> userVmNetworkMap = getVmOvfNetworkMapping(zone, owner, template, cmd.getVmNetworkMap());
|
||||
if (MapUtils.isNotEmpty(userVmNetworkMap)) {
|
||||
|
|
@ -6268,9 +6291,117 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
}
|
||||
}
|
||||
if (isLeaseFeatureEnabled) {
|
||||
applyLeaseOnCreateInstance(vm, cmd.getLeaseDuration(), cmd.getLeaseExpiryAction(), svcOffering);
|
||||
}
|
||||
return vm;
|
||||
}
|
||||
|
||||
protected void validateLeaseProperties(Integer leaseDuration, VMLeaseManager.ExpiryAction leaseExpiryAction) {
|
||||
if (ObjectUtils.allNull(leaseDuration, leaseExpiryAction) // both are null
|
||||
|| (leaseDuration != null && leaseDuration == -1)) { // special condition to disable lease for instance
|
||||
return;
|
||||
}
|
||||
|
||||
// any one of them have value
|
||||
// validate leaseduration
|
||||
if (leaseDuration == null || leaseDuration < 1 || leaseDuration > VMLeaseManager.MAX_LEASE_DURATION_DAYS) {
|
||||
throw new InvalidParameterValueException("Invalid leaseduration: must be a natural number (>=1) or -1, max supported value is 36500");
|
||||
}
|
||||
|
||||
if (leaseExpiryAction == null) {
|
||||
throw new InvalidParameterValueException("Provide values for both: leaseduration and leaseexpiryaction");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if lease feature is enabled
|
||||
* use leaseDuration and leaseExpiryAction passed in the cmd
|
||||
* get leaseDuration from service_offering if leaseDuration is not passed
|
||||
* @param vm
|
||||
* @param leaseDuration
|
||||
* @param leaseExpiryAction
|
||||
* @param serviceOfferingJoinVO
|
||||
*/
|
||||
void applyLeaseOnCreateInstance(UserVm vm, Integer leaseDuration, VMLeaseManager.ExpiryAction leaseExpiryAction, ServiceOfferingJoinVO serviceOfferingJoinVO) {
|
||||
if (leaseDuration == null) {
|
||||
leaseDuration = serviceOfferingJoinVO.getLeaseDuration();
|
||||
}
|
||||
// if leaseDuration is null or < 1, instance will never expire, nothing to be done
|
||||
if (leaseDuration == null || leaseDuration < 1) {
|
||||
return;
|
||||
}
|
||||
leaseExpiryAction = leaseExpiryAction != null ? leaseExpiryAction : serviceOfferingJoinVO.getLeaseExpiryAction();
|
||||
if (leaseExpiryAction == null) {
|
||||
return;
|
||||
}
|
||||
addLeaseDetailsForInstance(vm, leaseDuration, leaseExpiryAction);
|
||||
}
|
||||
|
||||
protected void applyLeaseOnUpdateInstance(UserVm instance, Integer leaseDuration, VMLeaseManager.ExpiryAction leaseExpiryAction) {
|
||||
validateLeaseProperties(leaseDuration, leaseExpiryAction);
|
||||
String instanceUuid = instance.getUuid();
|
||||
|
||||
// vm must have active lease associated during deployment
|
||||
Map<String, String> vmDetails = userVmDetailsDao.listDetailsKeyPairs(instance.getId(),
|
||||
List.of(VmDetailConstants.INSTANCE_LEASE_EXPIRY_DATE, VmDetailConstants.INSTANCE_LEASE_EXECUTION));
|
||||
String leaseExecution = vmDetails.get(VmDetailConstants.INSTANCE_LEASE_EXECUTION);
|
||||
String leaseExpiryDate = vmDetails.get(VmDetailConstants.INSTANCE_LEASE_EXPIRY_DATE);
|
||||
|
||||
if (StringUtils.isEmpty(leaseExpiryDate)) {
|
||||
String errorMsg = "Lease can't be applied on instance with id: " + instanceUuid + ", it doesn't have lease associated during deployment";
|
||||
logger.debug(errorMsg);
|
||||
throw new CloudRuntimeException(errorMsg);
|
||||
}
|
||||
|
||||
if (!VMLeaseManager.LeaseActionExecution.PENDING.name().equals(leaseExecution)) {
|
||||
String errorMsg = "Lease can't be applied on instance with id: " + instanceUuid + ", it doesn't have active lease";
|
||||
logger.debug(errorMsg);
|
||||
throw new CloudRuntimeException(errorMsg);
|
||||
}
|
||||
|
||||
// proceed if lease is yet to expire
|
||||
long leaseExpiryTimeDiff;
|
||||
try {
|
||||
leaseExpiryTimeDiff = DateUtil.getTimeDifference(
|
||||
DateUtil.parseDateString(TimeZone.getTimeZone("UTC"), leaseExpiryDate), new Date());
|
||||
} catch (Exception ex) {
|
||||
logger.error("Error occurred computing time difference for instance lease expiry, " +
|
||||
"will skip applying lease for vm with id: {}", instanceUuid, ex);
|
||||
return;
|
||||
}
|
||||
if (leaseExpiryTimeDiff < 0) {
|
||||
logger.debug("Lease has expired for instance with id: {}, can't modify lease information", instanceUuid);
|
||||
throw new CloudRuntimeException("Lease is not allowed to be redefined on expired leased instance");
|
||||
}
|
||||
|
||||
if (leaseDuration < 1) {
|
||||
userVmDetailsDao.addDetail(instance.getId(), VmDetailConstants.INSTANCE_LEASE_EXECUTION,
|
||||
VMLeaseManager.LeaseActionExecution.DISABLED.name(), false);
|
||||
ActionEventUtils.onActionEvent(CallContext.current().getCallingUserId(), instance.getAccountId(), instance.getDomainId(),
|
||||
EventTypes.VM_LEASE_DISABLED, "Disabling lease on the instance", instance.getId(), ApiCommandResourceType.VirtualMachine.toString());
|
||||
return;
|
||||
}
|
||||
addLeaseDetailsForInstance(instance, leaseDuration, leaseExpiryAction);
|
||||
}
|
||||
|
||||
protected void addLeaseDetailsForInstance(UserVm vm, Integer leaseDuration, VMLeaseManager.ExpiryAction leaseExpiryAction) {
|
||||
if (ObjectUtils.anyNull(vm, leaseDuration) || leaseDuration < 1) {
|
||||
logger.debug("Lease can't be applied for given vm: {}, leaseduration: {} and leaseexpiryaction: {}", vm, leaseDuration, leaseExpiryAction);
|
||||
return;
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
|
||||
LocalDateTime leaseExpiryDateTime = now.plusDays(leaseDuration);
|
||||
Date leaseExpiryDate = Date.from(leaseExpiryDateTime.atZone(ZoneOffset.UTC).toInstant());
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
String formattedLeaseExpiryDate = sdf.format(leaseExpiryDate);
|
||||
userVmDetailsDao.addDetail(vm.getId(), VmDetailConstants.INSTANCE_LEASE_EXPIRY_DATE, formattedLeaseExpiryDate, false);
|
||||
userVmDetailsDao.addDetail(vm.getId(), VmDetailConstants.INSTANCE_LEASE_EXPIRY_ACTION, leaseExpiryAction.name(), false);
|
||||
userVmDetailsDao.addDetail(vm.getId(), VmDetailConstants.INSTANCE_LEASE_EXECUTION, "PENDING", false);
|
||||
logger.debug("Instance lease for instanceId: {} is configured to expire on: {} with action: {}", vm.getUuid(), formattedLeaseExpiryDate, leaseExpiryAction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist extra configuration data in the user_vm_details table as key/value pair
|
||||
* @param decodedUrl String consisting of the extra config data to appended onto the vmx file for VMware instances
|
||||
|
|
|
|||
|
|
@ -0,0 +1,381 @@
|
|||
/*
|
||||
* 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.lease;
|
||||
|
||||
import com.cloud.api.ApiGsonHelper;
|
||||
import com.cloud.api.query.dao.UserVmJoinDao;
|
||||
import com.cloud.api.query.vo.UserVmJoinVO;
|
||||
import com.cloud.event.ActionEventUtils;
|
||||
import com.cloud.event.EventTypes;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.user.User;
|
||||
import com.cloud.utils.DateUtil;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.component.ComponentContext;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.concurrency.NamedThreadFactory;
|
||||
import com.cloud.utils.db.GlobalLock;
|
||||
import com.cloud.vm.VmDetailConstants;
|
||||
import com.cloud.vm.dao.UserVmDetailsDao;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd;
|
||||
import org.apache.cloudstack.api.command.user.vm.StopVMCmd;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.framework.jobs.AsyncJob;
|
||||
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.framework.messagebus.MessageBus;
|
||||
import org.apache.cloudstack.framework.messagebus.MessageSubscriber;
|
||||
import org.apache.cloudstack.jobs.JobInfo;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
||||
import org.apache.commons.lang3.EnumUtils;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class VMLeaseManagerImpl extends ManagerBase implements VMLeaseManager, Configurable {
|
||||
private static final int ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION = 5; // 5 seconds
|
||||
|
||||
@Inject
|
||||
private UserVmDetailsDao userVmDetailsDao;
|
||||
|
||||
@Inject
|
||||
private UserVmJoinDao userVmJoinDao;
|
||||
|
||||
@Inject
|
||||
private AsyncJobManager asyncJobManager;
|
||||
@Inject
|
||||
private MessageBus messageBus;
|
||||
|
||||
private AsyncJobDispatcher asyncJobDispatcher;
|
||||
|
||||
ScheduledExecutorService vmLeaseExecutor;
|
||||
ScheduledExecutorService vmLeaseExpiryEventExecutor;
|
||||
Gson gson = ApiGsonHelper.getBuilder().create();
|
||||
VMLeaseManagerSubscriber leaseManagerSubscriber;
|
||||
|
||||
public static final String JOB_INITIATOR = "jobInitiator";
|
||||
|
||||
@Override
|
||||
public String getConfigComponentName() {
|
||||
return VMLeaseManager.class.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey[]{
|
||||
InstanceLeaseEnabled,
|
||||
InstanceLeaseSchedulerInterval,
|
||||
InstanceLeaseExpiryEventSchedulerInterval,
|
||||
InstanceLeaseExpiryEventDaysBefore
|
||||
};
|
||||
}
|
||||
|
||||
public void setAsyncJobDispatcher(final AsyncJobDispatcher dispatcher) {
|
||||
asyncJobDispatcher = dispatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
||||
if (InstanceLeaseEnabled.value()) {
|
||||
scheduleLeaseExecutors();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop() {
|
||||
shutDownLeaseExecutors();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will cancel lease on instances running under lease
|
||||
* will be primarily used when feature gets disabled
|
||||
*/
|
||||
public void cancelLeaseOnExistingInstances() {
|
||||
List<UserVmJoinVO> leaseExpiringForInstances = userVmJoinDao.listLeaseInstancesExpiringInDays(-1);
|
||||
logger.debug("Total instances found for lease cancellation: {}", leaseExpiringForInstances.size());
|
||||
for (UserVmJoinVO instance : leaseExpiringForInstances) {
|
||||
userVmDetailsDao.addDetail(instance.getId(), VmDetailConstants.INSTANCE_LEASE_EXECUTION,
|
||||
LeaseActionExecution.CANCELLED.name(), false);
|
||||
String leaseCancellationMsg = String.format("Lease is cancelled for the instance: %s (id: %s) ", instance.getName(), instance.getUuid());
|
||||
ActionEventUtils.onActionEvent(instance.getUserId(), instance.getAccountId(), instance.getDomainId(),
|
||||
EventTypes.VM_LEASE_CANCELLED, leaseCancellationMsg, instance.getId(), ApiCommandResourceType.VirtualMachine.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeaseFeatureToggle() {
|
||||
boolean isLeaseFeatureEnabled = VMLeaseManager.InstanceLeaseEnabled.value();
|
||||
if (isLeaseFeatureEnabled) {
|
||||
scheduleLeaseExecutors();
|
||||
} else {
|
||||
cancelLeaseOnExistingInstances();
|
||||
shutDownLeaseExecutors();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleLeaseExecutors() {
|
||||
if (vmLeaseExecutor == null || vmLeaseExecutor.isShutdown()) {
|
||||
logger.debug("Scheduling lease executor");
|
||||
vmLeaseExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("VMLeasePollExecutor"));
|
||||
vmLeaseExecutor.scheduleAtFixedRate(new VMLeaseSchedulerTask(),5L, InstanceLeaseSchedulerInterval.value(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
if (vmLeaseExpiryEventExecutor == null || vmLeaseExpiryEventExecutor.isShutdown()) {
|
||||
logger.debug("Scheduling lease expiry event executor");
|
||||
vmLeaseExpiryEventExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("VmLeaseExpiryEventExecutor"));
|
||||
vmLeaseExpiryEventExecutor.scheduleAtFixedRate(new VMLeaseExpiryEventSchedulerTask(), 5L, InstanceLeaseExpiryEventSchedulerInterval.value(), TimeUnit.SECONDS);
|
||||
}
|
||||
addLeaseExpiryListener();
|
||||
}
|
||||
|
||||
private void shutDownLeaseExecutors() {
|
||||
if (vmLeaseExecutor != null) {
|
||||
logger.debug("Shutting down lease executor");
|
||||
vmLeaseExecutor.shutdown();
|
||||
vmLeaseExecutor = null;
|
||||
}
|
||||
|
||||
if (vmLeaseExpiryEventExecutor != null) {
|
||||
logger.debug("Shutting down lease expiry event executor");
|
||||
vmLeaseExpiryEventExecutor.shutdown();
|
||||
vmLeaseExpiryEventExecutor = null;
|
||||
}
|
||||
removeLeaseExpiryListener();
|
||||
}
|
||||
|
||||
class VMLeaseSchedulerTask extends ManagedContextRunnable {
|
||||
@Override
|
||||
protected void runInContext() {
|
||||
Date currentTimestamp = DateUtils.round(new Date(), Calendar.MINUTE);
|
||||
String displayTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
|
||||
logger.debug("VMLeaseSchedulerTask is being called at {}", displayTime);
|
||||
if (!InstanceLeaseEnabled.value()) {
|
||||
logger.debug("Instance lease feature is disabled, no action is required");
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalLock scanLock = GlobalLock.getInternLock("VMLeaseSchedulerTask");
|
||||
try {
|
||||
if (scanLock.lock(ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION)) {
|
||||
try {
|
||||
reallyRun();
|
||||
} finally {
|
||||
scanLock.unlock();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
scanLock.releaseRef();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VMLeaseExpiryEventSchedulerTask extends ManagedContextRunnable {
|
||||
@Override
|
||||
protected void runInContext() {
|
||||
logger.debug("VMLeaseExpiryEventSchedulerTask is being called");
|
||||
// as feature is disabled, no action is required
|
||||
if (!InstanceLeaseEnabled.value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalLock scanLock = GlobalLock.getInternLock("VMLeaseExpiryEventSchedulerTask");
|
||||
try {
|
||||
if (scanLock.lock(ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION)) {
|
||||
try {
|
||||
List<UserVmJoinVO> leaseExpiringForInstances = userVmJoinDao.listLeaseInstancesExpiringInDays(InstanceLeaseExpiryEventDaysBefore.value());
|
||||
for (UserVmJoinVO instance : leaseExpiringForInstances) {
|
||||
String leaseExpiryEventMsg = String.format("Lease expiring for for instance: %s (id: %s) with action: %s",
|
||||
instance.getName(), instance.getUuid(), instance.getLeaseExpiryAction());
|
||||
ActionEventUtils.onActionEvent(instance.getUserId(), instance.getAccountId(), instance.getDomainId(),
|
||||
EventTypes.VM_LEASE_EXPIRING, leaseExpiryEventMsg, instance.getId(), ApiCommandResourceType.VirtualMachine.toString());
|
||||
}
|
||||
} finally {
|
||||
scanLock.unlock();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
scanLock.releaseRef();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void reallyRun() {
|
||||
// fetch user_instances having leaseDuration configured and has expired
|
||||
List<UserVmJoinVO> leaseExpiredInstances = userVmJoinDao.listEligibleInstancesWithExpiredLease();
|
||||
Set<Long> actionableInstanceIds = new HashSet<>();
|
||||
for (UserVmJoinVO userVmVO : leaseExpiredInstances) {
|
||||
// skip instance with delete protection for DESTROY action
|
||||
if (ExpiryAction.DESTROY.name().equals(userVmVO.getLeaseExpiryAction())
|
||||
&& userVmVO.isDeleteProtection() != null && userVmVO.isDeleteProtection()) {
|
||||
logger.debug("Ignoring DESTROY action on instance: {} (id: {}) as deleteProtection is enabled", userVmVO.getName(), userVmVO.getUuid());
|
||||
continue;
|
||||
}
|
||||
actionableInstanceIds.add(userVmVO.getId());
|
||||
}
|
||||
if (actionableInstanceIds.isEmpty()) {
|
||||
logger.debug("Lease scheduler found no instance to work upon");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Long> submittedJobIds = new ArrayList<>();
|
||||
List<Long> successfulInstanceIds = new ArrayList<>();
|
||||
List<Long> failedToSubmitInstanceIds = new ArrayList<>();
|
||||
for (Long instanceId : actionableInstanceIds) {
|
||||
UserVmJoinVO instance = userVmJoinDao.findById(instanceId);
|
||||
ExpiryAction expiryAction = getLeaseExpiryAction(instance);
|
||||
if (expiryAction == null) {
|
||||
continue;
|
||||
}
|
||||
// for qualified vms, prepare Stop/Destroy(Cmd) and submit to Job Manager
|
||||
final long eventId = ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, instance.getAccountId(), null,
|
||||
EventTypes.VM_LEASE_EXPIRED, true,
|
||||
String.format("Executing lease expiry action (%s) for instance: %s (id: %s)", instance.getLeaseExpiryAction(), instance.getName(), instance.getUuid()),
|
||||
instance.getId(), ApiCommandResourceType.VirtualMachine.toString(), 0);
|
||||
|
||||
Long jobId = executeExpiryAction(instance, expiryAction, eventId);
|
||||
if (jobId != null) {
|
||||
submittedJobIds.add(jobId);
|
||||
successfulInstanceIds.add(instanceId);
|
||||
} else {
|
||||
failedToSubmitInstanceIds.add(instanceId);
|
||||
}
|
||||
}
|
||||
logger.debug("Successfully submitted lease expiry jobs with ids: {} and instance ids: {}", submittedJobIds, successfulInstanceIds);
|
||||
if (!failedToSubmitInstanceIds.isEmpty()) {
|
||||
logger.debug("Lease scheduler failed to submit jobs for instance ids: {}", failedToSubmitInstanceIds);
|
||||
}
|
||||
}
|
||||
|
||||
Long executeExpiryAction(UserVmJoinVO instance, ExpiryAction expiryAction, long eventId) {
|
||||
// for qualified vms, prepare Stop/Destroy(Cmd) and submit to Job Manager
|
||||
switch (expiryAction) {
|
||||
case STOP: {
|
||||
logger.debug("Stopping instance: {} (id: {}) on lease expiry", instance.getName(), instance.getUuid());
|
||||
return executeStopInstanceJob(instance, eventId);
|
||||
}
|
||||
case DESTROY: {
|
||||
logger.debug("Destroying instance: {} (id: {}) on lease expiry", instance.getName(), instance.getUuid());
|
||||
return executeDestroyInstanceJob(instance, eventId);
|
||||
}
|
||||
default: {
|
||||
logger.error("Invalid configuration for instance.lease.expiryaction for instance: {} (id: {}), " +
|
||||
"valid values are: \"STOP\" and \"DESTROY\"", instance.getName(), instance.getUuid());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
long executeStopInstanceJob(UserVmJoinVO vm, long eventId) {
|
||||
final Map<String, String> params = new HashMap<>();
|
||||
params.put(ApiConstants.ID, String.valueOf(vm.getId()));
|
||||
params.put("ctxUserId", String.valueOf(User.UID_SYSTEM));
|
||||
params.put("ctxAccountId", String.valueOf(Account.ACCOUNT_ID_SYSTEM));
|
||||
params.put(ApiConstants.CTX_START_EVENT_ID, String.valueOf(eventId));
|
||||
params.put(JOB_INITIATOR, VMLeaseManager.class.getSimpleName());
|
||||
final StopVMCmd cmd = new StopVMCmd();
|
||||
ComponentContext.inject(cmd);
|
||||
AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, vm.getAccountId(), StopVMCmd.class.getName(), gson.toJson(params), vm.getId(),
|
||||
cmd.getApiResourceType() != null ? cmd.getApiResourceType().toString() : null, null);
|
||||
job.setDispatcher(asyncJobDispatcher.getName());
|
||||
return asyncJobManager.submitAsyncJob(job);
|
||||
}
|
||||
|
||||
long executeDestroyInstanceJob(UserVmJoinVO vm, long eventId) {
|
||||
final Map<String, String> params = new HashMap<>();
|
||||
params.put(ApiConstants.ID, String.valueOf(vm.getId()));
|
||||
params.put("ctxUserId", String.valueOf(User.UID_SYSTEM));
|
||||
params.put("ctxAccountId", String.valueOf(Account.ACCOUNT_ID_SYSTEM));
|
||||
params.put(ApiConstants.CTX_START_EVENT_ID, String.valueOf(eventId));
|
||||
params.put(JOB_INITIATOR, VMLeaseManager.class.getSimpleName());
|
||||
final DestroyVMCmd cmd = new DestroyVMCmd();
|
||||
ComponentContext.inject(cmd);
|
||||
|
||||
AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM, vm.getAccountId(), DestroyVMCmd.class.getName(), gson.toJson(params), vm.getId(),
|
||||
cmd.getApiResourceType() != null ? cmd.getApiResourceType().toString() : null, null);
|
||||
job.setDispatcher(asyncJobDispatcher.getName());
|
||||
return asyncJobManager.submitAsyncJob(job);
|
||||
}
|
||||
|
||||
public ExpiryAction getLeaseExpiryAction(UserVmJoinVO instance) {
|
||||
return EnumUtils.getEnumIgnoreCase(VMLeaseManager.ExpiryAction.class, instance.getLeaseExpiryAction());
|
||||
}
|
||||
|
||||
private void addLeaseExpiryListener() {
|
||||
logger.debug("Adding Lease subscriber for async job events");
|
||||
if (this.leaseManagerSubscriber == null) {
|
||||
this.leaseManagerSubscriber = new VMLeaseManagerSubscriber();
|
||||
}
|
||||
messageBus.subscribe(AsyncJob.Topics.JOB_EVENT_PUBLISH, this.leaseManagerSubscriber);
|
||||
}
|
||||
|
||||
private void removeLeaseExpiryListener() {
|
||||
logger.debug("Removing Lease subscriber for async job events");
|
||||
messageBus.unsubscribe(AsyncJob.Topics.JOB_EVENT_PUBLISH, this.leaseManagerSubscriber);
|
||||
this.leaseManagerSubscriber = null;
|
||||
}
|
||||
|
||||
class VMLeaseManagerSubscriber implements MessageSubscriber {
|
||||
@Override
|
||||
public void onPublishMessage(String senderAddress, String subject, Object args) {
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Pair<AsyncJob, String> eventInfo = (Pair<AsyncJob, String>) args;
|
||||
AsyncJob asyncExpiryJob = eventInfo.first();
|
||||
if (!"ApiAsyncJobDispatcher".equalsIgnoreCase(asyncExpiryJob.getDispatcher()) || !"complete".equalsIgnoreCase(eventInfo.second())) {
|
||||
return;
|
||||
}
|
||||
String cmd = asyncExpiryJob.getCmd();
|
||||
if ((cmd.equalsIgnoreCase(StopVMCmd.class.getName()) || cmd.equalsIgnoreCase(DestroyVMCmd.class.getName()))
|
||||
&& asyncExpiryJob.getStatus() == JobInfo.Status.SUCCEEDED && asyncExpiryJob.getInstanceId() != null) {
|
||||
|
||||
Map<String, String> params = gson.fromJson(asyncExpiryJob.getCmdInfo(), new TypeToken<Map<String, String>>() {
|
||||
}.getType());
|
||||
|
||||
if (VMLeaseManager.class.getSimpleName().equals(params.get(JOB_INITIATOR))) {
|
||||
logger.debug("Lease expiry job: {} successfully executed for instanceId: {}", asyncExpiryJob.getId(), asyncExpiryJob.getInstanceId());
|
||||
userVmDetailsDao.addDetail(asyncExpiryJob.getInstanceId(), VmDetailConstants.INSTANCE_LEASE_EXECUTION, LeaseActionExecution.DONE.name(), false);
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
logger.error("Caught exception while executing lease expiry job", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -385,4 +385,9 @@
|
|||
|
||||
<bean id="reconcileCommandServiceImpl" class="org.apache.cloudstack.command.ReconcileCommandServiceImpl">
|
||||
</bean>
|
||||
|
||||
<bean id="vmLeaseManager" class="org.apache.cloudstack.vm.lease.VMLeaseManagerImpl" >
|
||||
<property name="asyncJobDispatcher" ref="ApiAsyncJobDispatcher" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
|
|
|||
|
|
@ -80,6 +80,10 @@ import com.cloud.deploy.DataCenterDeployment;
|
|||
import com.cloud.deploy.DeployDestination;
|
||||
import com.cloud.deploy.DeploymentPlanner;
|
||||
import com.cloud.deploy.DeploymentPlanningManager;
|
||||
import com.cloud.domain.DomainVO;
|
||||
import com.cloud.domain.dao.DomainDao;
|
||||
import com.cloud.event.ActionEventUtils;
|
||||
import com.cloud.event.UsageEventUtils;
|
||||
import com.cloud.exception.InsufficientAddressCapacityException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.InsufficientServerCapacityException;
|
||||
|
|
@ -91,12 +95,28 @@ import com.cloud.host.Host;
|
|||
import com.cloud.host.HostVO;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.hypervisor.Hypervisor;
|
||||
import com.cloud.network.Network;
|
||||
import com.cloud.network.NetworkModel;
|
||||
import com.cloud.network.dao.FirewallRulesDao;
|
||||
import com.cloud.network.dao.IPAddressDao;
|
||||
import com.cloud.network.dao.IPAddressVO;
|
||||
import com.cloud.network.dao.LoadBalancerVMMapDao;
|
||||
import com.cloud.network.dao.LoadBalancerVMMapVO;
|
||||
import com.cloud.network.dao.NetworkDao;
|
||||
import com.cloud.network.dao.NetworkVO;
|
||||
import com.cloud.network.dao.PhysicalNetworkDao;
|
||||
import com.cloud.network.dao.PhysicalNetworkVO;
|
||||
import com.cloud.network.guru.NetworkGuru;
|
||||
import com.cloud.network.rules.FirewallRuleVO;
|
||||
import com.cloud.network.rules.PortForwardingRule;
|
||||
import com.cloud.network.rules.dao.PortForwardingRulesDao;
|
||||
import com.cloud.network.security.SecurityGroupManager;
|
||||
import com.cloud.network.security.SecurityGroupVO;
|
||||
import com.cloud.offering.DiskOffering;
|
||||
import com.cloud.offering.NetworkOffering;
|
||||
import com.cloud.offering.ServiceOffering;
|
||||
import com.cloud.offerings.NetworkOfferingVO;
|
||||
import com.cloud.offerings.dao.NetworkOfferingDao;
|
||||
import com.cloud.server.ManagementService;
|
||||
import com.cloud.service.ServiceOfferingVO;
|
||||
import com.cloud.service.dao.ServiceOfferingDao;
|
||||
|
|
@ -136,31 +156,23 @@ import com.cloud.vm.dao.UserVmDao;
|
|||
import com.cloud.vm.dao.UserVmDetailsDao;
|
||||
import com.cloud.vm.snapshot.VMSnapshotVO;
|
||||
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
|
||||
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import com.cloud.domain.DomainVO;
|
||||
import com.cloud.domain.dao.DomainDao;
|
||||
import com.cloud.event.UsageEventUtils;
|
||||
import com.cloud.network.Network;
|
||||
import com.cloud.network.dao.FirewallRulesDao;
|
||||
import com.cloud.network.dao.IPAddressDao;
|
||||
import com.cloud.network.dao.IPAddressVO;
|
||||
import com.cloud.network.dao.LoadBalancerVMMapDao;
|
||||
import com.cloud.network.dao.LoadBalancerVMMapVO;
|
||||
import com.cloud.network.dao.PhysicalNetworkDao;
|
||||
import com.cloud.network.dao.PhysicalNetworkVO;
|
||||
import com.cloud.network.guru.NetworkGuru;
|
||||
import com.cloud.network.rules.FirewallRuleVO;
|
||||
import com.cloud.network.rules.PortForwardingRule;
|
||||
import com.cloud.network.rules.dao.PortForwardingRulesDao;
|
||||
import com.cloud.network.security.SecurityGroupManager;
|
||||
import com.cloud.offering.NetworkOffering;
|
||||
import com.cloud.offerings.NetworkOfferingVO;
|
||||
import com.cloud.offerings.dao.NetworkOfferingDao;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class UserVmManagerImplTest {
|
||||
|
|
@ -3089,7 +3101,7 @@ public class UserVmManagerImplTest {
|
|||
configureDoNothingForMethodsThatWeDoNotWantToTest();
|
||||
|
||||
userVmManagerImpl.executeStepsToChangeOwnershipOfVm(assignVmCmdMock, callerAccount, accountMock, accountMock, userVmVoMock, serviceOfferingVoMock, volumes,
|
||||
virtualMachineTemplateMock, 1l);
|
||||
virtualMachineTemplateMock, 1L);
|
||||
|
||||
Mockito.verify(userVmManagerImpl).resourceCountDecrement(Mockito.anyLong(), Mockito.any(), Mockito.any(), Mockito.any());
|
||||
Mockito.verify(userVmManagerImpl).updateVmOwner(Mockito.any(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
|
||||
|
|
@ -3211,4 +3223,176 @@ public class UserVmManagerImplTest {
|
|||
Mockito.verify(storageManager, times(1)).getStorageAccessGroups(null, null, null, srcHost.getId());
|
||||
Mockito.verify(storageManager, times(1)).getStorageAccessGroups(null, null, null, destHost.getId());
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testValidateLeasePropertiesInvalidDuration() {
|
||||
userVmManagerImpl.validateLeaseProperties(-2, VMLeaseManager.ExpiryAction.STOP);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testValidateLeasePropertiesNullActionValue() {
|
||||
userVmManagerImpl.validateLeaseProperties(20, null);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testValidateLeasePropertiesNullDurationValue() {
|
||||
userVmManagerImpl.validateLeaseProperties(null, VMLeaseManager.ExpiryAction.STOP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateLeasePropertiesMinusOneDuration() {
|
||||
userVmManagerImpl.validateLeaseProperties(-1, null);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testValidateLeasePropertiesZeroDayDuration() {
|
||||
userVmManagerImpl.validateLeaseProperties(0, VMLeaseManager.ExpiryAction.STOP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateLeasePropertiesValidValues() {
|
||||
userVmManagerImpl.validateLeaseProperties(20, VMLeaseManager.ExpiryAction.STOP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateLeasePropertiesBothNUll() {
|
||||
userVmManagerImpl.validateLeaseProperties(null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddLeaseDetailsForInstance() {
|
||||
UserVm userVm = mock(UserVm.class);
|
||||
when(userVm.getId()).thenReturn(vmId);
|
||||
when(userVm.getUuid()).thenReturn(UUID.randomUUID().toString());
|
||||
userVmManagerImpl.addLeaseDetailsForInstance(userVm, 10, VMLeaseManager.ExpiryAction.STOP);
|
||||
verify(userVmDetailsDao).addDetail(eq(vmId), eq(VmDetailConstants.INSTANCE_LEASE_EXPIRY_ACTION), eq(VMLeaseManager.ExpiryAction.STOP.name()), anyBoolean());
|
||||
verify(userVmDetailsDao).addDetail(eq(vmId), eq(VmDetailConstants.INSTANCE_LEASE_EXPIRY_DATE), eq(getLeaseExpiryDate(10L)), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddNullDurationLeaseDetailsForInstance() {
|
||||
UserVm userVm = mock(UserVm.class);
|
||||
userVmManagerImpl.addLeaseDetailsForInstance(userVm, null, VMLeaseManager.ExpiryAction.STOP);
|
||||
Mockito.verify(userVmDetailsDao, Mockito.times(0)).removeDetail(vmId, VmDetailConstants.INSTANCE_LEASE_EXPIRY_ACTION);
|
||||
Mockito.verify(userVmDetailsDao, Mockito.times(0)).removeDetail(vmId, VmDetailConstants.INSTANCE_LEASE_EXPIRY_DATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyLeaseOnCreateInstanceFeatureEnabled() {
|
||||
UserVmVO userVm = Mockito.mock(UserVmVO.class);
|
||||
ServiceOfferingJoinVO svcOfferingMock = Mockito.mock(ServiceOfferingJoinVO.class);
|
||||
userVmManagerImpl.applyLeaseOnCreateInstance(userVm, 10, VMLeaseManager.ExpiryAction.DESTROY, svcOfferingMock);
|
||||
Mockito.verify(userVmManagerImpl, Mockito.times(1)).addLeaseDetailsForInstance(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyLeaseOnCreateInstanceNegativeLease() {
|
||||
UserVmVO userVm = Mockito.mock(UserVmVO.class);
|
||||
userVmManagerImpl.applyLeaseOnCreateInstance(userVm, -1, VMLeaseManager.ExpiryAction.DESTROY, null);
|
||||
Mockito.verify(userVmManagerImpl, Mockito.times(0)).addLeaseDetailsForInstance(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyLeaseOnCreateInstanceFromSvcOfferingWithoutLease() {
|
||||
UserVmVO userVm = Mockito.mock(UserVmVO.class);
|
||||
ServiceOfferingJoinVO svcOfferingMock = Mockito.mock(ServiceOfferingJoinVO.class);
|
||||
userVmManagerImpl.applyLeaseOnCreateInstance(userVm, null, VMLeaseManager.ExpiryAction.DESTROY, svcOfferingMock);
|
||||
Mockito.verify(userVmManagerImpl, Mockito.times(0)).addLeaseDetailsForInstance(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyLeaseOnCreateInstanceFromSvcOfferingWithLease() {
|
||||
UserVmVO userVm = Mockito.mock(UserVmVO.class);
|
||||
ServiceOfferingJoinVO svcOfferingMock = Mockito.mock(ServiceOfferingJoinVO.class);
|
||||
when(svcOfferingMock.getLeaseDuration()).thenReturn(10);
|
||||
userVmManagerImpl.applyLeaseOnCreateInstance(userVm, null, VMLeaseManager.ExpiryAction.DESTROY, svcOfferingMock);
|
||||
Mockito.verify(userVmManagerImpl, Mockito.times(1)).addLeaseDetailsForInstance(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyLeaseOnCreateInstanceNullExpiryAction() {
|
||||
UserVmVO userVm = Mockito.mock(UserVmVO.class);
|
||||
ServiceOfferingJoinVO svcOfferingMock = Mockito.mock(ServiceOfferingJoinVO.class);
|
||||
userVmManagerImpl.applyLeaseOnCreateInstance(userVm, 10, null, svcOfferingMock);
|
||||
Mockito.verify(userVmManagerImpl, Mockito.times(0)).addLeaseDetailsForInstance(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testApplyLeaseOnUpdateInstanceForNoLease() {
|
||||
UserVmVO userVm = Mockito.mock(UserVmVO.class);
|
||||
when(userVm.getId()).thenReturn(vmId);
|
||||
when(userVmDetailsDao.listDetailsKeyPairs(anyLong(), anyList())).thenReturn(getLeaseDetails(5, VMLeaseManager.LeaseActionExecution.DISABLED.name()));
|
||||
userVmManagerImpl.applyLeaseOnUpdateInstance(userVm, 10, VMLeaseManager.ExpiryAction.STOP);
|
||||
Mockito.verify(userVmManagerImpl, Mockito.times(0)).addLeaseDetailsForInstance(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyLeaseOnUpdateInstanceForLease() {
|
||||
UserVmVO userVm = Mockito.mock(UserVmVO.class);
|
||||
when(userVm.getId()).thenReturn(vmId);
|
||||
when(userVmDetailsDao.listDetailsKeyPairs(anyLong(), anyList())).thenReturn(getLeaseDetails(5, VMLeaseManager.LeaseActionExecution.PENDING.name()));
|
||||
userVmManagerImpl.applyLeaseOnUpdateInstance(userVm, 10, VMLeaseManager.ExpiryAction.STOP);
|
||||
Mockito.verify(userVmManagerImpl, Mockito.times(1)).addLeaseDetailsForInstance(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testApplyLeaseOnUpdateInstanceForDisabledLeaseInstance() {
|
||||
UserVmVO userVm = Mockito.mock(UserVmVO.class);
|
||||
when(userVm.getId()).thenReturn(vmId);
|
||||
when(userVmDetailsDao.listDetailsKeyPairs(anyLong(), anyList())).thenReturn(getLeaseDetails(5, VMLeaseManager.LeaseActionExecution.DISABLED.name()));
|
||||
userVmManagerImpl.applyLeaseOnUpdateInstance(userVm, 10, VMLeaseManager.ExpiryAction.STOP);
|
||||
Mockito.verify(userVmManagerImpl, Mockito.times(1)).addLeaseDetailsForInstance(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testApplyLeaseOnUpdateInstanceForLeaseExpired() {
|
||||
UserVmVO userVm = Mockito.mock(UserVmVO.class);
|
||||
when(userVmDetailsDao.listDetailsKeyPairs(anyLong(), anyList())).thenReturn(getLeaseDetails(-2, VMLeaseManager.LeaseActionExecution.PENDING.name()));
|
||||
userVmManagerImpl.applyLeaseOnUpdateInstance(userVm, 10, VMLeaseManager.ExpiryAction.STOP);
|
||||
Mockito.verify(userVmManagerImpl, Mockito.times(0)).addLeaseDetailsForInstance(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyLeaseOnUpdateInstanceToRemoveLease() {
|
||||
UserVmVO userVm = Mockito.mock(UserVmVO.class);
|
||||
when(userVm.getId()).thenReturn(vmId);;
|
||||
when(userVmDetailsDao.listDetailsKeyPairs(anyLong(), anyList())).thenReturn(getLeaseDetails(2, VMLeaseManager.LeaseActionExecution.PENDING.name()));
|
||||
try (MockedStatic<ActionEventUtils> ignored = Mockito.mockStatic(ActionEventUtils.class)) {
|
||||
Mockito.when(ActionEventUtils.onActionEvent(Mockito.anyLong(), Mockito.anyLong(),
|
||||
Mockito.anyLong(),
|
||||
Mockito.anyString(), Mockito.anyString(),
|
||||
Mockito.anyLong(), Mockito.anyString())).thenReturn(1L);
|
||||
userVmManagerImpl.applyLeaseOnUpdateInstance(userVm, -1, VMLeaseManager.ExpiryAction.STOP);
|
||||
}
|
||||
Mockito.verify(userVmManagerImpl, Mockito.times(0)).addLeaseDetailsForInstance(any(), any(), any());
|
||||
Mockito.verify(userVmDetailsDao, Mockito.times(1)).
|
||||
addDetail(vmId, VmDetailConstants.INSTANCE_LEASE_EXECUTION, VMLeaseManager.LeaseActionExecution.DISABLED.name(), false);
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testApplyLeaseOnUpdateInstanceToRemoveLeaseForExpired() {
|
||||
UserVmVO userVm = Mockito.mock(UserVmVO.class);
|
||||
when(userVm.getId()).thenReturn(vmId);
|
||||
when(userVmDetailsDao.listDetailsKeyPairs(anyLong(), anyList())).thenReturn(getLeaseDetails(-2, VMLeaseManager.LeaseActionExecution.PENDING.name()));
|
||||
userVmManagerImpl.applyLeaseOnUpdateInstance(userVm, -1, VMLeaseManager.ExpiryAction.STOP);
|
||||
Mockito.verify(userVmManagerImpl, Mockito.times(0)).addLeaseDetailsForInstance(any(), any(), any());
|
||||
Mockito.verify(userVmDetailsDao, Mockito.times(0)).removeDetail(vmId, VmDetailConstants.INSTANCE_LEASE_EXPIRY_ACTION);
|
||||
Mockito.verify(userVmDetailsDao, Mockito.times(0)).removeDetail(vmId, VmDetailConstants.INSTANCE_LEASE_EXPIRY_DATE);
|
||||
}
|
||||
|
||||
String getLeaseExpiryDate(long leaseDuration) {
|
||||
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
|
||||
LocalDateTime leaseExpiryDateTime = now.plusDays(leaseDuration);
|
||||
Date leaseExpiryDate = Date.from(leaseExpiryDateTime.atZone(ZoneOffset.UTC).toInstant());
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
return sdf.format(leaseExpiryDate);
|
||||
}
|
||||
|
||||
Map<String, String> getLeaseDetails(int leaseDuration, String leaseExecution) {
|
||||
Map<String, String> leaseDetails = new HashMap<>();
|
||||
leaseDetails.put(VmDetailConstants.INSTANCE_LEASE_EXPIRY_DATE, getLeaseExpiryDate(leaseDuration));
|
||||
leaseDetails.put(VmDetailConstants.INSTANCE_LEASE_EXECUTION, leaseExecution);
|
||||
return leaseDetails;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,314 @@
|
|||
/*
|
||||
* 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.lease;
|
||||
|
||||
import com.cloud.api.query.dao.UserVmJoinDao;
|
||||
import com.cloud.api.query.vo.UserVmJoinVO;
|
||||
import com.cloud.event.ActionEventUtils;
|
||||
import com.cloud.user.User;
|
||||
import com.cloud.utils.component.ComponentContext;
|
||||
import com.cloud.utils.db.GlobalLock;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.VmDetailConstants;
|
||||
import com.cloud.vm.dao.UserVmDetailsDao;
|
||||
import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd;
|
||||
import org.apache.cloudstack.api.command.user.vm.StopVMCmd;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher;
|
||||
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
|
||||
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
|
||||
import org.apache.cloudstack.framework.messagebus.MessageBus;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class VMLeaseManagerImplTest {
|
||||
public static final String DESTROY = "DESTROY";
|
||||
public static final String VM_UUID = UUID.randomUUID().toString();
|
||||
public static final String VM_NAME = "vm-name";
|
||||
|
||||
@Spy
|
||||
@InjectMocks
|
||||
private VMLeaseManagerImpl vmLeaseManager;
|
||||
|
||||
@Mock
|
||||
private UserVmJoinDao userVmJoinDao;
|
||||
|
||||
@Mock
|
||||
MessageBus messageBus;
|
||||
|
||||
@Mock
|
||||
private UserVmDetailsDao userVmDetailsDao;
|
||||
|
||||
@Mock
|
||||
private AsyncJobManager asyncJobManager;
|
||||
|
||||
@Mock
|
||||
private AsyncJobDispatcher asyncJobDispatcher;
|
||||
|
||||
@Mock
|
||||
private GlobalLock globalLock;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
vmLeaseManager.setAsyncJobDispatcher(asyncJobDispatcher);
|
||||
when(asyncJobDispatcher.getName()).thenReturn("AsyncJobDispatcher");
|
||||
when(asyncJobManager.submitAsyncJob(any(AsyncJobVO.class))).thenReturn(1L);
|
||||
doNothing().when(userVmDetailsDao).addDetail(
|
||||
anyLong(), anyString(), anyString(), anyBoolean()
|
||||
);
|
||||
try {
|
||||
vmLeaseManager.configure("VMLeaseManagerImpl", new HashMap<>());
|
||||
} catch (ConfigurationException e) {
|
||||
throw new CloudRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReallyRunNoExpiredInstances() {
|
||||
when(userVmJoinDao.listEligibleInstancesWithExpiredLease()).thenReturn(new ArrayList<>());
|
||||
vmLeaseManager.reallyRun();
|
||||
verify(asyncJobManager, never()).submitAsyncJob(any(AsyncJobVO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReallyRunWithDeleteProtection() {
|
||||
UserVmJoinVO vm = createMockVm(1L, VM_UUID, VM_NAME, VirtualMachine.State.Running, true);
|
||||
when(vm.getLeaseExpiryAction()).thenReturn("DESTROY");
|
||||
List<UserVmJoinVO> expiredVms = Arrays.asList(vm);
|
||||
when(userVmJoinDao.listEligibleInstancesWithExpiredLease()).thenReturn(expiredVms);
|
||||
vmLeaseManager.reallyRun();
|
||||
// Verify no jobs were submitted because of delete protection
|
||||
verify(asyncJobManager, never()).submitAsyncJob(any(AsyncJobVO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReallyRunStopAction() {
|
||||
UserVmJoinVO vm = createMockVm(1L, VM_UUID, VM_NAME, VirtualMachine.State.Running, false);
|
||||
List<UserVmJoinVO> expiredVms = Arrays.asList(vm);
|
||||
when(userVmJoinDao.listEligibleInstancesWithExpiredLease()).thenReturn(expiredVms);
|
||||
when(userVmJoinDao.findById(1L)).thenReturn(vm);
|
||||
doReturn(1L).when(vmLeaseManager).executeStopInstanceJob(eq(vm), anyLong());
|
||||
try (MockedStatic<ActionEventUtils> utilities = Mockito.mockStatic(ActionEventUtils.class)) {
|
||||
utilities.when(() -> ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(),
|
||||
Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyLong())).thenReturn(1L);
|
||||
|
||||
vmLeaseManager.reallyRun();
|
||||
}
|
||||
verify(vmLeaseManager).executeStopInstanceJob(eq(vm), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReallyRunDestroyAction() {
|
||||
UserVmJoinVO vm = createMockVm(1L, VM_UUID, VM_NAME, VirtualMachine.State.Running, false, DESTROY);
|
||||
List<UserVmJoinVO> expiredVms = Arrays.asList(vm);
|
||||
when(userVmJoinDao.listEligibleInstancesWithExpiredLease()).thenReturn(expiredVms);
|
||||
when(userVmJoinDao.findById(1L)).thenReturn(vm);
|
||||
doReturn(1L).when(vmLeaseManager).executeDestroyInstanceJob(eq(vm), anyLong());
|
||||
try (MockedStatic<ActionEventUtils> utilities = Mockito.mockStatic(ActionEventUtils.class)) {
|
||||
utilities.when(() -> ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(),
|
||||
Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyLong())).thenReturn(1L);
|
||||
vmLeaseManager.reallyRun();
|
||||
}
|
||||
verify(vmLeaseManager).executeDestroyInstanceJob(eq(vm), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteExpiryActionStop() {
|
||||
UserVmJoinVO vm = createMockVm(1L, VM_UUID, VM_NAME, VirtualMachine.State.Running, false);
|
||||
doReturn(1L).when(vmLeaseManager).executeStopInstanceJob(eq(vm), eq(123L));
|
||||
Long jobId = vmLeaseManager.executeExpiryAction(vm, VMLeaseManager.ExpiryAction.STOP, 123L);
|
||||
assertNotNull(jobId);
|
||||
assertEquals(1L, jobId.longValue());
|
||||
verify(vmLeaseManager).executeStopInstanceJob(eq(vm), eq(123L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteExpiryActionDestroy() {
|
||||
UserVmJoinVO vm = createMockVm(1L, VM_UUID, VM_NAME, VirtualMachine.State.Running, false, DESTROY);
|
||||
doReturn(1L).when(vmLeaseManager).executeDestroyInstanceJob(eq(vm), eq(123L));
|
||||
Long jobId = vmLeaseManager.executeExpiryAction(vm, VMLeaseManager.ExpiryAction.DESTROY, 123L);
|
||||
assertNotNull(jobId);
|
||||
assertEquals(1L, jobId.longValue());
|
||||
verify(vmLeaseManager).executeDestroyInstanceJob(eq(vm), eq(123L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteStopInstanceJob() {
|
||||
UserVmJoinVO vm = createMockVm(1L, VM_UUID, VM_NAME, VirtualMachine.State.Running, false);
|
||||
// Mock the static ComponentContext
|
||||
try (MockedStatic<ComponentContext> mockedComponentContext = Mockito.mockStatic(ComponentContext.class)) {
|
||||
ApplicationContext mockAppContext = mock(ApplicationContext.class);
|
||||
mockedComponentContext.when(ComponentContext::getApplicationContext).thenReturn(mockAppContext);
|
||||
mockedComponentContext.when(() -> ComponentContext.inject(any())).thenReturn(true);
|
||||
long jobId = vmLeaseManager.executeStopInstanceJob(vm, 123L);
|
||||
assertEquals(1L, jobId);
|
||||
ArgumentCaptor<AsyncJobVO> jobCaptor = ArgumentCaptor.forClass(AsyncJobVO.class);
|
||||
verify(asyncJobManager).submitAsyncJob(jobCaptor.capture());
|
||||
AsyncJobVO capturedJob = jobCaptor.getValue();
|
||||
assertEquals(User.UID_SYSTEM, capturedJob.getUserId());
|
||||
assertEquals(vm.getAccountId(), capturedJob.getAccountId());
|
||||
assertEquals(StopVMCmd.class.getName(), capturedJob.getCmd());
|
||||
assertEquals(vm.getId(), capturedJob.getInstanceId().longValue());
|
||||
assertEquals("AsyncJobDispatcher", capturedJob.getDispatcher());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteDestroyInstanceJob() {
|
||||
UserVmJoinVO vm = createMockVm(1L, VM_UUID, VM_NAME, VirtualMachine.State.Running, false, DESTROY);
|
||||
try (MockedStatic<ComponentContext> mockedComponentContext = Mockito.mockStatic(ComponentContext.class)) {
|
||||
ApplicationContext mockAppContext = mock(ApplicationContext.class);
|
||||
mockedComponentContext.when(ComponentContext::getApplicationContext).thenReturn(mockAppContext);
|
||||
mockedComponentContext.when(() -> ComponentContext.inject(any())).thenReturn(true);
|
||||
long jobId = vmLeaseManager.executeDestroyInstanceJob(vm, 123L);
|
||||
assertEquals(1L, jobId);
|
||||
ArgumentCaptor<AsyncJobVO> jobCaptor = ArgumentCaptor.forClass(AsyncJobVO.class);
|
||||
verify(asyncJobManager).submitAsyncJob(jobCaptor.capture());
|
||||
AsyncJobVO capturedJob = jobCaptor.getValue();
|
||||
assertEquals(User.UID_SYSTEM, capturedJob.getUserId());
|
||||
assertEquals(vm.getAccountId(), capturedJob.getAccountId());
|
||||
assertEquals(DestroyVMCmd.class.getName(), capturedJob.getCmd());
|
||||
assertEquals(vm.getId(), capturedJob.getInstanceId().longValue());
|
||||
assertEquals("AsyncJobDispatcher", capturedJob.getDispatcher());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLeaseExpiryAction() {
|
||||
UserVmJoinVO vm = createMockVm(1L, VM_UUID, VM_NAME, VirtualMachine.State.Running, false);
|
||||
VMLeaseManager.ExpiryAction action = vmLeaseManager.getLeaseExpiryAction(vm);
|
||||
assertEquals(VMLeaseManager.ExpiryAction.STOP, action);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLeaseExpiryActionNoAction() {
|
||||
UserVmJoinVO vm = createMockVm(1L, VM_UUID, VM_NAME, VirtualMachine.State.Running, false);
|
||||
when(vm.getLeaseExpiryAction()).thenReturn(null);
|
||||
vm.setLeaseExpiryAction(null);
|
||||
assertNull(vmLeaseManager.getLeaseExpiryAction(vm));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLeaseExpiryInvalidAction() {
|
||||
UserVmJoinVO vm = createMockVm(1L, VM_UUID, VM_NAME, VirtualMachine.State.Running, false);
|
||||
when(vm.getLeaseExpiryAction()).thenReturn("Unknown");
|
||||
assertNull(vmLeaseManager.getLeaseExpiryAction(vm));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetComponentName() {
|
||||
assertEquals(vmLeaseManager.getConfigComponentName(), "VMLeaseManager");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigKeys() {
|
||||
assertEquals(vmLeaseManager.getConfigKeys().length, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigure() throws Exception {
|
||||
overrideDefaultConfigValue(VMLeaseManager.InstanceLeaseEnabled, "true");
|
||||
vmLeaseManager.configure("VMLeaseManagerImpl", new HashMap<>());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStopShouldShutdownExecutors() {
|
||||
assertTrue(vmLeaseManager.stop());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelLeaseOnExistingInstances() {
|
||||
UserVmJoinVO vm = createMockVm(1L, VM_UUID, VM_NAME, VirtualMachine.State.Running, true);
|
||||
when(userVmJoinDao.listLeaseInstancesExpiringInDays(-1)).thenReturn(List.of(vm));
|
||||
try (MockedStatic<ActionEventUtils> utilities = Mockito.mockStatic(ActionEventUtils.class)) {
|
||||
utilities.when(() -> ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(),
|
||||
Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyLong())).thenReturn(1L);
|
||||
vmLeaseManager.cancelLeaseOnExistingInstances();
|
||||
verify(userVmDetailsDao).addDetail(1L, VmDetailConstants.INSTANCE_LEASE_EXECUTION, VMLeaseManager.LeaseActionExecution.CANCELLED.name(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaseFeatureToggleEnabled() throws Exception {
|
||||
overrideDefaultConfigValue(VMLeaseManager.InstanceLeaseEnabled, "true");
|
||||
vmLeaseManager.onLeaseFeatureToggle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaseFeatureToggleDisabled() throws Exception {
|
||||
overrideDefaultConfigValue(VMLeaseManager.InstanceLeaseEnabled, "false");
|
||||
vmLeaseManager.onLeaseFeatureToggle();
|
||||
}
|
||||
|
||||
private UserVmJoinVO createMockVm(Long id, String uuid, String name, VirtualMachine.State state, boolean deleteProtection) {
|
||||
return createMockVm(id, uuid, name, state, deleteProtection, "STOP");
|
||||
}
|
||||
|
||||
// Helper method to create mock VMs
|
||||
private UserVmJoinVO createMockVm(Long id, String uuid, String name, VirtualMachine.State state, boolean deleteProtection, String expiryAction) {
|
||||
UserVmJoinVO vm = mock(UserVmJoinVO.class);
|
||||
when(vm.getId()).thenReturn(id);
|
||||
when(vm.getUuid()).thenReturn(uuid);
|
||||
when(vm.isDeleteProtection()).thenReturn(deleteProtection);
|
||||
when(vm.getAccountId()).thenReturn(1L);
|
||||
when(vm.getLeaseExpiryAction()).thenReturn(expiryAction);
|
||||
return vm;
|
||||
}
|
||||
|
||||
private void overrideDefaultConfigValue(final ConfigKey configKey, final String value) throws IllegalAccessException, NoSuchFieldException {
|
||||
final Field f = ConfigKey.class.getDeclaredField("_defaultValue");
|
||||
f.setAccessible(true);
|
||||
f.set(configKey, value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,358 @@
|
|||
# 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.
|
||||
|
||||
# Import Local Modules
|
||||
from nose.plugins.attrib import attr
|
||||
from marvin.codes import FAILED
|
||||
from marvin.cloudstackTestCase import cloudstackTestCase
|
||||
from marvin.lib.utils import cleanup_resources
|
||||
from marvin.lib.base import (Account,
|
||||
VirtualMachine,
|
||||
ServiceOffering,
|
||||
DiskOffering,
|
||||
Configurations)
|
||||
from marvin.lib.common import (get_zone,
|
||||
get_domain,
|
||||
get_test_template,
|
||||
is_config_suitable)
|
||||
|
||||
|
||||
class TestDeployVMLease(cloudstackTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
||||
cls.testClient = super(TestDeployVMLease, cls).getClsTestClient()
|
||||
cls.api_client = cls.testClient.getApiClient()
|
||||
|
||||
cls.testdata = cls.testClient.getParsedTestDataConfig()
|
||||
# Get Zone, Domain and templates
|
||||
cls.domain = get_domain(cls.api_client)
|
||||
cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests())
|
||||
cls.hypervisor = cls.testClient.getHypervisorInfo()
|
||||
|
||||
cls.template = get_test_template(
|
||||
cls.api_client,
|
||||
cls.zone.id,
|
||||
cls.hypervisor
|
||||
)
|
||||
|
||||
if cls.template == FAILED:
|
||||
assert False, "get_test_template() failed to return template"
|
||||
|
||||
|
||||
# enable instance lease feature
|
||||
Configurations.update(cls.api_client,
|
||||
name="instance.lease.enabled",
|
||||
value="true"
|
||||
)
|
||||
|
||||
# Create service, disk offerings etc
|
||||
cls.non_lease_svc_offering = ServiceOffering.create(
|
||||
cls.api_client,
|
||||
cls.testdata["service_offering"],
|
||||
name="non-lease-svc-offering"
|
||||
)
|
||||
|
||||
# Create service, disk offerings etc
|
||||
cls.lease_svc_offering = ServiceOffering.create(
|
||||
cls.api_client,
|
||||
cls.testdata["service_offering"],
|
||||
name="lease-svc-offering",
|
||||
leaseduration=20,
|
||||
leaseexpiryaction="DESTROY"
|
||||
)
|
||||
|
||||
cls.disk_offering = DiskOffering.create(
|
||||
cls.api_client,
|
||||
cls.testdata["disk_offering"]
|
||||
)
|
||||
|
||||
cls._cleanup = [
|
||||
cls.lease_svc_offering,
|
||||
cls.non_lease_svc_offering,
|
||||
cls.disk_offering
|
||||
]
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
try:
|
||||
# disable instance lease feature
|
||||
Configurations.update(cls.api_client,
|
||||
name="instance.lease.enabled",
|
||||
value="false"
|
||||
)
|
||||
cleanup_resources(cls.api_client, cls._cleanup)
|
||||
except Exception as e:
|
||||
raise Exception("Warning: Exception during cleanup : %s" % e)
|
||||
|
||||
def setUp(self):
|
||||
self.apiclient = self.testClient.getApiClient()
|
||||
self.hypervisor = self.testClient.getHypervisorInfo()
|
||||
self.testdata["virtual_machine"]["zoneid"] = self.zone.id
|
||||
self.testdata["virtual_machine"]["template"] = self.template.id
|
||||
self.testdata["iso"]["zoneid"] = self.zone.id
|
||||
self.account = Account.create(
|
||||
self.apiclient,
|
||||
self.testdata["account"],
|
||||
domainid=self.domain.id
|
||||
)
|
||||
self.cleanup = [self.account]
|
||||
return
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
self.debug("Cleaning up the resources")
|
||||
cleanup_resources(self.apiclient, self.cleanup)
|
||||
self.debug("Cleanup complete!")
|
||||
except Exception as e:
|
||||
self.debug("Warning! Exception in tearDown: %s" % e)
|
||||
|
||||
@attr(
|
||||
tags=[
|
||||
"advanced",
|
||||
"basic"],
|
||||
required_hardware="true")
|
||||
def test_01_deploy_vm_no_lease_svc_offering(self):
|
||||
"""Test Deploy Virtual Machine from non-lease-svc-offering
|
||||
|
||||
Validate the following:
|
||||
1. deploy VM using non-lease-svc-offering
|
||||
2. confirm vm has no lease configured
|
||||
"""
|
||||
|
||||
non_lease_vm = VirtualMachine.create(
|
||||
self.apiclient,
|
||||
self.testdata["virtual_machine"],
|
||||
accountid=self.account.name,
|
||||
domainid=self.account.domainid,
|
||||
templateid=self.template.id,
|
||||
serviceofferingid=self.non_lease_svc_offering.id,
|
||||
diskofferingid=self.disk_offering.id,
|
||||
hypervisor=self.hypervisor
|
||||
)
|
||||
self.verify_no_lease_configured_for_vm(non_lease_vm.id)
|
||||
return
|
||||
|
||||
@attr(
|
||||
tags=[
|
||||
"advanced",
|
||||
"basic"],
|
||||
required_hardware="true")
|
||||
def test_02_deploy_vm_no_lease_svc_offering_with_lease_params(self):
|
||||
"""Test Deploy Virtual Machine from non-lease-svc-offering and lease parameters are used to enabled lease for vm
|
||||
|
||||
Validate the following:
|
||||
1. deploy VM using non-lease-svc-offering and passing leaseduration and leaseexpiryaction
|
||||
2. confirm vm has lease configured
|
||||
"""
|
||||
lease_vm = VirtualMachine.create(
|
||||
self.apiclient,
|
||||
self.testdata["virtual_machine"],
|
||||
accountid=self.account.name,
|
||||
domainid=self.account.domainid,
|
||||
templateid=self.template.id,
|
||||
serviceofferingid=self.non_lease_svc_offering.id,
|
||||
diskofferingid=self.disk_offering.id,
|
||||
hypervisor=self.hypervisor,
|
||||
leaseduration=10,
|
||||
leaseexpiryaction="STOP"
|
||||
)
|
||||
self.verify_lease_configured_for_vm(lease_vm.id, lease_duration=10, lease_expiry_action="STOP")
|
||||
return
|
||||
|
||||
@attr(
|
||||
tags=[
|
||||
"advanced",
|
||||
"basic"],
|
||||
required_hardware="true")
|
||||
def test_03_deploy_vm_lease_svc_offering_with_no_param(self):
|
||||
"""Test Deploy Virtual Machine from lease-svc-offering without lease params
|
||||
expect vm to inherit svc_offering lease properties
|
||||
|
||||
Validate the following:
|
||||
1. deploy VM using lease-svc-offering without passing leaseduration and leaseexpiryaction
|
||||
2. confirm vm has lease configured
|
||||
"""
|
||||
lease_vm = VirtualMachine.create(
|
||||
self.apiclient,
|
||||
self.testdata["virtual_machine"],
|
||||
accountid=self.account.name,
|
||||
domainid=self.account.domainid,
|
||||
templateid=self.template.id,
|
||||
serviceofferingid=self.lease_svc_offering.id,
|
||||
diskofferingid=self.disk_offering.id,
|
||||
hypervisor=self.hypervisor
|
||||
)
|
||||
self.verify_lease_configured_for_vm(lease_vm.id, lease_duration=20, lease_expiry_action="DESTROY")
|
||||
return
|
||||
|
||||
@attr(
|
||||
tags=[
|
||||
"advanced",
|
||||
"basic"],
|
||||
required_hardware="true")
|
||||
def test_04_deploy_vm_lease_svc_offering_with_param(self):
|
||||
"""Test Deploy Virtual Machine from lease-svc-offering with overridden lease properties
|
||||
|
||||
Validate the following:
|
||||
1. confirm svc_offering has lease properties
|
||||
2. deploy VM using lease-svc-offering and leaseduration and leaseexpiryaction passed
|
||||
3. confirm vm has lease configured
|
||||
"""
|
||||
self.verify_svc_offering()
|
||||
|
||||
lease_vm = VirtualMachine.create(
|
||||
self.apiclient,
|
||||
self.testdata["virtual_machine"],
|
||||
accountid=self.account.name,
|
||||
domainid=self.account.domainid,
|
||||
templateid=self.template.id,
|
||||
serviceofferingid=self.lease_svc_offering.id,
|
||||
diskofferingid=self.disk_offering.id,
|
||||
hypervisor=self.hypervisor,
|
||||
leaseduration=30,
|
||||
leaseexpiryaction="STOP"
|
||||
)
|
||||
self.verify_lease_configured_for_vm(lease_vm.id, lease_duration=30, lease_expiry_action="STOP")
|
||||
return
|
||||
|
||||
|
||||
@attr(
|
||||
tags=[
|
||||
"advanced",
|
||||
"basic"],
|
||||
required_hardware="true")
|
||||
def test_05_deploy_vm_lease_svc_offering_with_lease_param_disabled(self):
|
||||
"""Test Deploy Virtual Machine from lease-svc-offering and passing -1 leaseduration to set no-expiry
|
||||
|
||||
Validate the following:
|
||||
1. deploy VM using lease-svc-offering
|
||||
2. leaseduration is set as -1 in the deploy vm request to disable lease
|
||||
3. confirm vm has no lease configured
|
||||
"""
|
||||
|
||||
lease_vm = VirtualMachine.create(
|
||||
self.apiclient,
|
||||
self.testdata["virtual_machine"],
|
||||
accountid=self.account.name,
|
||||
domainid=self.account.domainid,
|
||||
templateid=self.template.id,
|
||||
serviceofferingid=self.non_lease_svc_offering.id,
|
||||
diskofferingid=self.disk_offering.id,
|
||||
hypervisor=self.hypervisor,
|
||||
leaseduration=-1
|
||||
)
|
||||
|
||||
vms = VirtualMachine.list(
|
||||
self.apiclient,
|
||||
id=lease_vm.id
|
||||
)
|
||||
vm = vms[0]
|
||||
self.verify_no_lease_configured_for_vm(vm.id)
|
||||
return
|
||||
|
||||
@attr(
|
||||
tags=[
|
||||
"advanced",
|
||||
"basic"],
|
||||
required_hardware="true")
|
||||
def test_06_deploy_vm_lease_svc_offering_with_disabled_lease(self):
|
||||
"""Test Deploy Virtual Machine from lease-svc-offering with lease feature disabled
|
||||
|
||||
Validate the following:
|
||||
1. Disable lease feature
|
||||
2. deploy VM using lease-svc-offering
|
||||
3. confirm vm has no lease configured
|
||||
"""
|
||||
|
||||
Configurations.update(self.api_client,
|
||||
name="instance.lease.enabled",
|
||||
value="false"
|
||||
)
|
||||
|
||||
lease_vm = VirtualMachine.create(
|
||||
self.apiclient,
|
||||
self.testdata["virtual_machine"],
|
||||
accountid=self.account.name,
|
||||
domainid=self.account.domainid,
|
||||
templateid=self.template.id,
|
||||
serviceofferingid=self.lease_svc_offering.id,
|
||||
diskofferingid=self.disk_offering.id,
|
||||
hypervisor=self.hypervisor
|
||||
)
|
||||
|
||||
vms = VirtualMachine.list(
|
||||
self.apiclient,
|
||||
id=lease_vm.id
|
||||
)
|
||||
vm = vms[0]
|
||||
self.verify_no_lease_configured_for_vm(vm.id)
|
||||
return
|
||||
|
||||
|
||||
def verify_svc_offering(self):
|
||||
svc_offering_list = ServiceOffering.list(
|
||||
self.api_client,
|
||||
id=self.lease_svc_offering.id
|
||||
)
|
||||
|
||||
svc_offering = svc_offering_list[0]
|
||||
|
||||
self.assertIsNotNone(
|
||||
svc_offering.leaseduration,
|
||||
"svc_offering has lease configured"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
20,
|
||||
svc_offering.leaseduration,
|
||||
"svc_offering has 20 days for lease"
|
||||
)
|
||||
|
||||
def verify_lease_configured_for_vm(self, vm_id=None, lease_duration=None, lease_expiry_action=None):
|
||||
vms = VirtualMachine.list(
|
||||
self.apiclient,
|
||||
id=vm_id
|
||||
)
|
||||
vm = vms[0]
|
||||
self.assertEqual(
|
||||
lease_duration,
|
||||
vm.leaseduration,
|
||||
"check to confirm leaseduration is configured"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
lease_expiry_action,
|
||||
vm.leaseexpiryaction,
|
||||
"check to confirm leaseexpiryaction is configured"
|
||||
)
|
||||
|
||||
self.assertIsNotNone(vm.leaseexpirydate, "confirm leaseexpirydate is available")
|
||||
|
||||
|
||||
def verify_no_lease_configured_for_vm(self, vm_id=None):
|
||||
if vm_id == None:
|
||||
return
|
||||
vms = VirtualMachine.list(
|
||||
self.apiclient,
|
||||
id=vm_id
|
||||
)
|
||||
vm = vms[0]
|
||||
self.assertIsNone(vm.leaseduration)
|
||||
self.assertIsNone(vm.leaseexpiryaction)
|
||||
|
|
@ -35,7 +35,8 @@ from marvin.lib.common import (list_service_offering,
|
|||
get_domain,
|
||||
get_zone,
|
||||
get_test_template,
|
||||
list_hosts)
|
||||
list_hosts,
|
||||
is_config_suitable)
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
import time
|
||||
|
|
@ -356,6 +357,164 @@ class TestCreateServiceOffering(cloudstackTestCase):
|
|||
)
|
||||
return
|
||||
|
||||
@attr(
|
||||
tags=[
|
||||
"advanced",
|
||||
"smoke",
|
||||
"basic"],
|
||||
required_hardware="false")
|
||||
def test_06_create_service_offering_lease_enabled(self):
|
||||
"""
|
||||
1. Enable lease feature
|
||||
2. Create a service_offering
|
||||
3. Verify service offering lease properties
|
||||
"""
|
||||
self.update_lease_feature("true")
|
||||
|
||||
service_offering = ServiceOffering.create(
|
||||
self.apiclient,
|
||||
self.services["service_offerings"]["tiny"],
|
||||
name="tiny-lease-svc-offering",
|
||||
leaseduration=10,
|
||||
leaseexpiryaction="STOP"
|
||||
)
|
||||
self.cleanup.append(service_offering)
|
||||
|
||||
self.debug(
|
||||
"Created service offering with ID: %s" %
|
||||
service_offering.id)
|
||||
|
||||
list_service_response = list_service_offering(
|
||||
self.apiclient,
|
||||
id=service_offering.id
|
||||
)
|
||||
|
||||
self.assertNotEqual(
|
||||
len(list_service_response),
|
||||
0,
|
||||
"Check Service offering is created"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
list_service_response[0].leaseduration,
|
||||
10,
|
||||
"Confirm leaseduration"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
list_service_response[0].leaseexpiryaction,
|
||||
"STOP",
|
||||
"Confirm leaseexpiryaction"
|
||||
)
|
||||
return
|
||||
|
||||
@attr(
|
||||
tags=[
|
||||
"advanced",
|
||||
"smoke",
|
||||
"basic"],
|
||||
required_hardware="false")
|
||||
def test_07_create_service_offering_without_lease_disabled_feature(self):
|
||||
"""
|
||||
1. Disable lease feature
|
||||
2. Create a service_offering with lease option
|
||||
3. Verify service offering for NO lease properties
|
||||
"""
|
||||
self.update_lease_feature("true")
|
||||
service_offering = ServiceOffering.create(
|
||||
self.apiclient,
|
||||
self.services["service_offerings"]["tiny"],
|
||||
name="tiny-svc-offering-novalue-lease"
|
||||
)
|
||||
self.cleanup.append(service_offering)
|
||||
|
||||
self.debug(
|
||||
"Created service offering with ID: %s" %
|
||||
service_offering.id)
|
||||
|
||||
list_service_response = list_service_offering(
|
||||
self.apiclient,
|
||||
id=service_offering.id
|
||||
)
|
||||
|
||||
self.assertNotEqual(
|
||||
len(list_service_response),
|
||||
0,
|
||||
"Check Service offering is created"
|
||||
)
|
||||
|
||||
self.assertIsNone(
|
||||
list_service_response[0].leaseduration,
|
||||
"Confirm No leaseduration"
|
||||
)
|
||||
self.assertIsNone(
|
||||
list_service_response[0].leaseexiryaction,
|
||||
"Confirm leaseexpiryaction is not set"
|
||||
)
|
||||
return
|
||||
|
||||
@attr(
|
||||
tags=[
|
||||
"advanced",
|
||||
"smoke",
|
||||
"basic"],
|
||||
required_hardware="false")
|
||||
def test_08_create_service_offering_lease_disabled(self):
|
||||
"""
|
||||
1. Disable lease feature
|
||||
2. Create a service_offering with lease option
|
||||
3. Verify service offering for NO lease properties
|
||||
"""
|
||||
self.update_lease_feature("false")
|
||||
service_offering = ServiceOffering.create(
|
||||
self.apiclient,
|
||||
self.services["service_offerings"]["tiny"],
|
||||
name="tiny-lease-svc-offering-disabled",
|
||||
leaseduration=10,
|
||||
leaseexpiryaction="STOP"
|
||||
)
|
||||
self.cleanup.append(service_offering)
|
||||
|
||||
self.debug(
|
||||
"Created service offering with ID: %s" %
|
||||
service_offering.id)
|
||||
|
||||
list_service_response = list_service_offering(
|
||||
self.apiclient,
|
||||
id=service_offering.id
|
||||
)
|
||||
|
||||
self.assertNotEqual(
|
||||
len(list_service_response),
|
||||
0,
|
||||
"Check Service offering is created"
|
||||
)
|
||||
|
||||
self.assertIsNone(
|
||||
list_service_response[0].leaseduration,
|
||||
"Confirm No leaseduration"
|
||||
)
|
||||
self.assertIsNone(
|
||||
list_service_response[0].leaseexiryaction,
|
||||
"Confirm leaseexpiryaction is not set"
|
||||
)
|
||||
return
|
||||
|
||||
def update_lease_feature(self, value=None):
|
||||
# Update global setting for "instance.lease.enabled"
|
||||
Configurations.update(self.apiclient,
|
||||
name="instance.lease.enabled",
|
||||
value=value
|
||||
)
|
||||
|
||||
# Verify that the above mentioned settings are set to true
|
||||
if not is_config_suitable(
|
||||
apiclient=self.apiclient,
|
||||
name='instance.lease.enabled',
|
||||
value=value):
|
||||
self.fail(f'instance.lease.enabled should be: {value}')
|
||||
|
||||
|
||||
|
||||
class TestServiceOfferings(cloudstackTestCase):
|
||||
|
||||
|
|
|
|||
|
|
@ -527,7 +527,8 @@ class VirtualMachine:
|
|||
customcpuspeed=None, custommemory=None, rootdisksize=None,
|
||||
rootdiskcontroller=None, vpcid=None, macaddress=None, datadisktemplate_diskoffering_list={},
|
||||
properties=None, nicnetworklist=None, bootmode=None, boottype=None, dynamicscalingenabled=None,
|
||||
userdataid=None, userdatadetails=None, extraconfig=None, size=None, overridediskofferingid=None):
|
||||
userdataid=None, userdatadetails=None, extraconfig=None, size=None, overridediskofferingid=None,
|
||||
leaseduration=None, leaseexpiryaction=None):
|
||||
"""Create the instance"""
|
||||
|
||||
cmd = deployVirtualMachine.deployVirtualMachineCmd()
|
||||
|
|
@ -691,6 +692,12 @@ class VirtualMachine:
|
|||
if extraconfig:
|
||||
cmd.extraconfig = extraconfig
|
||||
|
||||
if leaseduration:
|
||||
cmd.leaseduration = leaseduration
|
||||
|
||||
if leaseexpiryaction:
|
||||
cmd.leaseexpiryaction = leaseexpiryaction
|
||||
|
||||
virtual_machine = apiclient.deployVirtualMachine(cmd, method=method)
|
||||
|
||||
if 'password' in list(virtual_machine.__dict__.keys()):
|
||||
|
|
|
|||
|
|
@ -1184,6 +1184,7 @@
|
|||
"label.instancename": "Internal name",
|
||||
"label.instanceport": "Instance port",
|
||||
"label.instances": "Instances",
|
||||
"label.leasedinstances": "Leased Instances",
|
||||
"label.interface.route.table": "Interface Route Table",
|
||||
"label.interface.router.table": "Interface Router Table",
|
||||
"label.intermediate.certificate": "Intermediate certificate",
|
||||
|
|
@ -2701,6 +2702,15 @@
|
|||
"label.cniconfiguration": "CNI Configuration",
|
||||
"label.cniconfigname": "Associated CNI Configuration",
|
||||
"label.cniconfigparams": "CNI Configuration parameters",
|
||||
"label.lease.enable": "Enable Lease",
|
||||
"label.lease.enable.tooltip": "The Instance Lease feature allows to set a lease duration (in days) for instances, after which they automatically expire. Upon expiry, the instance can either be stopped (powered off) or destroyed, based on the configured policy",
|
||||
"label.instance.lease": "Instance lease",
|
||||
"label.instance.lease.placeholder": "Lease duration in days ( > 0)",
|
||||
"label.leaseduration": "Lease duration (in days)",
|
||||
"label.leaseexpiry.date.and.time": "Lease expiry date",
|
||||
"label.leaseexpiryaction": "Lease expiry action",
|
||||
"label.remainingdays": "Lease",
|
||||
"label.leased": "Leased",
|
||||
"message.acquire.ip.failed": "Failed to acquire IP.",
|
||||
"message.action.acquire.ip": "Please confirm that you want to acquire new IP.",
|
||||
"message.action.cancel.maintenance": "Your host has been successfully canceled for maintenance. This process can take up to several minutes.",
|
||||
|
|
|
|||
|
|
@ -159,6 +159,13 @@
|
|||
<div>{{ $toLocaleDate(dataResource[item]) }}</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
<a-list-item v-else-if="item === 'leaseexpirydate' && dataResource[item]">
|
||||
<div>
|
||||
<strong>{{ $t('label.' + item.replace('date', '.date.and.time'))}}</strong>
|
||||
<br/>
|
||||
<div>{{ $toLocaleDate(dataResource[item]) }}</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
<a-list-item v-else-if="item === 'details' && $route.meta.name === 'storagepool' && dataResource[item].rbd_default_data_pool">
|
||||
<div>
|
||||
<strong>{{ $t('label.data.pool') }}</strong>
|
||||
|
|
@ -227,6 +234,9 @@ export default {
|
|||
items.push('startdate')
|
||||
items.push('enddate')
|
||||
}
|
||||
if (this.$route.meta.name === 'vm') {
|
||||
items.push('leaseexpirydate')
|
||||
}
|
||||
return items
|
||||
},
|
||||
vnfAccessMethods () {
|
||||
|
|
|
|||
|
|
@ -96,6 +96,9 @@
|
|||
<a-tag v-if="resource.archived" :color="this.$config.theme['@warning-color']">
|
||||
{{ $t('label.archived') }}
|
||||
</a-tag>
|
||||
<a-tag v-if="resource.leaseduration != undefined">
|
||||
{{ $t('label.remainingdays') + ': ' + (resource.leaseduration > -1 ? resource.leaseduration + 'd' : 'Over') }}
|
||||
</a-tag>
|
||||
<a-tooltip placement="right" >
|
||||
<template #title>
|
||||
<span>{{ $t('label.view.console') }}</span>
|
||||
|
|
@ -226,6 +229,27 @@
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="resource-detail-item" v-if="'leaseduration' in resource && resource.leaseduration !== undefined">
|
||||
<div class="resource-detail-item__label">{{ $t('label.leaseduration') }}</div>
|
||||
<div class="resource-detail-item__details">
|
||||
<field-time-outlined
|
||||
:style="{
|
||||
color: $store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#888' },
|
||||
fontSize: '20px'
|
||||
}"/>
|
||||
{{ resource.leaseduration + ' ' + $t('label.days') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="resource-detail-item" v-if="'leaseexpiryaction' in resource && resource.leaseexpiryaction !== undefined">
|
||||
<div class="resource-detail-item__label">{{ $t('label.leaseexpiryaction') }}</div>
|
||||
<div class="resource-detail-item__details">
|
||||
<font-awesome-icon
|
||||
:icon="['fa-solid', 'fa-circle-xmark']"
|
||||
class="anticon"
|
||||
:style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#888' }]" />
|
||||
{{ resource.leaseexpiryaction }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="resource-detail-item" v-if="'memory' in resource">
|
||||
<div class="resource-detail-item__label">{{ $t('label.memory') }}</div>
|
||||
<div class="resource-detail-item__details">
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@
|
|||
<span v-else :style="{ 'margin-right': record.ostypename ? '5px' : '0' }">
|
||||
<os-logo v-if="record.ostypename" :osName="record.ostypename" size="xl" />
|
||||
</span>
|
||||
|
||||
<span v-if="record.hasannotations">
|
||||
<span v-if="record.id">
|
||||
<router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
|
||||
|
|
@ -101,6 +100,20 @@
|
|||
</a-tooltip>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
v-if="record.leaseduration !== undefined"
|
||||
:style="{
|
||||
'margin-right': '5px',
|
||||
'float': 'right'}">
|
||||
<a-tooltip>
|
||||
<template #title>{{ $t('label.remainingdays') + ": " + getRemainingLeaseText(record.leaseduration) }}</template>
|
||||
<field-time-outlined
|
||||
:style="{
|
||||
color: getLeaseColor(record.leaseduration),
|
||||
fontSize: '20px'
|
||||
}"/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'templatetype'">
|
||||
|
|
@ -1070,6 +1083,24 @@ export default {
|
|||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
getRemainingLeaseText (leaseDuration) {
|
||||
if (leaseDuration > 0) {
|
||||
return leaseDuration + (leaseDuration === 1 ? ' day' : ' days')
|
||||
} else if (leaseDuration === 0) {
|
||||
return 'expiring today'
|
||||
} else {
|
||||
return 'over'
|
||||
}
|
||||
},
|
||||
getLeaseColor (leaseDuration) {
|
||||
if (leaseDuration >= 7) {
|
||||
return '#888'
|
||||
} else if (leaseDuration >= 0) {
|
||||
return '#ffbf00'
|
||||
} else {
|
||||
return '#fd7e14'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ export default {
|
|||
if (!(store.getters.project && store.getters.project.id)) {
|
||||
filters.unshift('self')
|
||||
}
|
||||
if (store.getters.features.instanceleaseenabled) {
|
||||
filters.push('leased')
|
||||
}
|
||||
return filters
|
||||
},
|
||||
columns: () => {
|
||||
|
|
@ -83,7 +86,7 @@ export default {
|
|||
var fields = ['name', 'displayname', 'id', 'state', 'ipaddress', 'ip6address', 'templatename', 'ostypename',
|
||||
'serviceofferingname', 'isdynamicallyscalable', 'haenable', 'hypervisor', 'arch', 'boottype', 'bootmode', 'account',
|
||||
'domain', 'zonename', 'userdataid', 'userdataname', 'userdataparams', 'userdatadetails', 'userdatapolicy',
|
||||
'hostcontrolstate', 'deleteprotection']
|
||||
'hostcontrolstate', 'deleteprotection', 'leaseexpirydate', 'leaseexpiryaction']
|
||||
const listZoneHaveSGEnabled = store.getters.zones.filter(zone => zone.securitygroupsenabled === true)
|
||||
if (!listZoneHaveSGEnabled || listZoneHaveSGEnabled.length === 0) {
|
||||
return fields
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export default {
|
|||
filters: ['active', 'inactive'],
|
||||
columns: ['name', 'displaytext', 'state', 'cpunumber', 'cpuspeed', 'memory', 'domain', 'zone', 'order'],
|
||||
details: () => {
|
||||
var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'storageaccessgroups', 'storagetags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness', 'encryptroot', 'purgeresources']
|
||||
var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'storageaccessgroups', 'storagetags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness', 'encryptroot', 'purgeresources', 'leaseduration', 'leaseexpiryaction']
|
||||
if (store.getters.apis.createServiceOffering &&
|
||||
store.getters.apis.createServiceOffering.params.filter(x => x.name === 'storagepolicy').length > 0) {
|
||||
fields.splice(6, 0, 'vspherestoragepolicy')
|
||||
|
|
|
|||
|
|
@ -1728,6 +1728,7 @@ export default {
|
|||
delete query.domainid
|
||||
delete query.state
|
||||
delete query.annotationfilter
|
||||
delete query.leased
|
||||
if (this.$route.name === 'template') {
|
||||
query.templatefilter = filter
|
||||
} else if (this.$route.name === 'iso') {
|
||||
|
|
@ -1777,6 +1778,8 @@ export default {
|
|||
query.domainid = this.$store.getters.userInfo.domainid
|
||||
} else if (['running', 'stopped'].includes(filter)) {
|
||||
query.state = filter
|
||||
} else if (filter === 'leased') {
|
||||
query.leased = true
|
||||
}
|
||||
} else if (this.$route.name === 'comment') {
|
||||
query.annotationfilter = filter
|
||||
|
|
|
|||
|
|
@ -603,6 +603,34 @@
|
|||
@change="val => { dynamicscalingenabled = val }"/>
|
||||
</a-form-item>
|
||||
</a-form-item>
|
||||
<a-form-item name="showLeaseOptions" ref="showLeaseOptions" v-if="isLeaseFeatureEnabled">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.lease.enable')" :tooltip="$t('label.lease.enable.tooltip')"/>
|
||||
</template>
|
||||
<a-switch v-model:checked="showLeaseOptions" @change="onToggleLeaseData"/>
|
||||
</a-form-item>
|
||||
<a-row :gutter="12" v-if="isLeaseFeatureEnabled && showLeaseOptions">
|
||||
<a-col :md="12" :lg="12">
|
||||
<a-form-item name="leaseduration" ref="leaseduration">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.leaseduration')" />
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="form.leaseduration"
|
||||
:placeholder="$t('label.instance.lease.placeholder')"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :lg="12">
|
||||
<a-form-item name="leaseexpiryaction" ref="leaseexpiryaction">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.leaseexpiryaction')" />
|
||||
</template>
|
||||
<a-select v-model:value="form.leaseexpiryaction" :defaultValue="leaseexpiryaction">
|
||||
<a-select-option v-for="action in expiryActions" :key="action" :label="action" />
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item :label="$t('label.userdata')">
|
||||
<a-card>
|
||||
<div v-if="this.template && this.template.userdataid">
|
||||
|
|
@ -1101,6 +1129,17 @@ export default {
|
|||
description: 'ARM 64 bits (aarch64)'
|
||||
}
|
||||
]
|
||||
},
|
||||
isLeaseFeatureEnabled: this.$store.getters.features.instanceleaseenabled,
|
||||
showLeaseOptions: false,
|
||||
leaseduration: -1,
|
||||
leaseexpiryaction: undefined,
|
||||
expiryActions: ['STOP', 'DESTROY'],
|
||||
defaultLeaseDuration: 90,
|
||||
defaultLeaseExpiryAction: 'STOP',
|
||||
naturalNumberRule: {
|
||||
type: 'number',
|
||||
validator: this.validateNumber
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1582,6 +1621,10 @@ export default {
|
|||
if (this.sshKeyPairs && this.sshKeyPairs.length > 0) {
|
||||
this.vm.keypairs = this.sshKeyPairs
|
||||
}
|
||||
|
||||
if (this.leaseduration < 1) {
|
||||
this.vm.leaseduration = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1618,7 +1661,8 @@ export default {
|
|||
this.form = reactive({})
|
||||
this.rules = reactive({
|
||||
zoneid: [{ required: true, message: `${this.$t('message.error.select')}` }],
|
||||
hypervisor: [{ required: true, message: `${this.$t('message.error.select')}` }]
|
||||
hypervisor: [{ required: true, message: `${this.$t('message.error.select')}` }],
|
||||
leaseduration: [this.naturalNumberRule]
|
||||
})
|
||||
|
||||
if (this.zoneSelected) {
|
||||
|
|
@ -2193,6 +2237,12 @@ export default {
|
|||
if (values.group) {
|
||||
deployVmData.group = values.group
|
||||
}
|
||||
if (values.leaseduration) {
|
||||
deployVmData.leaseduration = values.leaseduration
|
||||
}
|
||||
if (values.leaseexpiryaction) {
|
||||
deployVmData.leaseexpiryaction = values.leaseexpiryaction
|
||||
}
|
||||
// step 8: enter setup
|
||||
if ('properties' in values) {
|
||||
const keys = Object.keys(values.properties)
|
||||
|
|
@ -2845,6 +2895,16 @@ export default {
|
|||
this.rootDiskSizeFixed = offering.rootdisksize
|
||||
this.showRootDiskSizeChanger = false
|
||||
}
|
||||
|
||||
if (this.isLeaseFeatureEnabled) {
|
||||
if (offering && offering.leaseduration > 0) {
|
||||
this.showLeaseOptions = true
|
||||
} else {
|
||||
this.showLeaseOptions = false
|
||||
}
|
||||
this.onToggleLeaseData()
|
||||
}
|
||||
|
||||
this.form.rootdisksizeitem = this.showRootDiskSizeChanger && this.rootDiskSizeFixed > 0
|
||||
this.formModel = toRaw(this.form)
|
||||
},
|
||||
|
|
@ -2888,6 +2948,23 @@ export default {
|
|||
parent.$message.success(parent.$t('label.copied.clipboard'))
|
||||
}
|
||||
})
|
||||
},
|
||||
onToggleLeaseData () {
|
||||
if (this.showLeaseOptions === false) {
|
||||
this.leaseduration = -1
|
||||
this.leaseexpiryaction = undefined
|
||||
} else {
|
||||
this.leaseduration = this.serviceOffering.leaseduration ? this.serviceOffering.leaseduration : this.defaultLeaseDuration
|
||||
this.leaseexpiryaction = this.serviceOffering.leaseexpiryaction ? this.serviceOffering.leaseexpiryaction : this.defaultLeaseExpiryAction
|
||||
}
|
||||
this.form.leaseduration = this.leaseduration
|
||||
this.form.leaseexpiryaction = this.leaseexpiryaction
|
||||
},
|
||||
async validateNumber (rule, value) {
|
||||
if (value && (isNaN(value) || value <= 0)) {
|
||||
return Promise.reject(this.$t('message.error.number'))
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2940,7 +3017,7 @@ export default {
|
|||
.vm-info-card {
|
||||
.ant-card-body {
|
||||
min-height: 250px;
|
||||
max-height: calc(100vh - 150px);
|
||||
max-height: calc(100vh - 140px);
|
||||
overflow-y: auto;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,34 @@
|
|||
</template>
|
||||
<a-switch v-model:checked="form.deleteprotection" />
|
||||
</a-form-item>
|
||||
<a-form-item name="showLeaseOptions" ref="showLeaseOptions" v-if="isLeaseEditable">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.lease.enable')" :tooltip="$t('label.lease.enable.tooltip')" />
|
||||
</template>
|
||||
<a-switch v-model:checked="showLeaseOptions" @change="onToggleLeaseData"/>
|
||||
</a-form-item>
|
||||
<a-row :gutter="12" v-if="showLeaseOptions">
|
||||
<a-col :md="12" :lg="12">
|
||||
<a-form-item name="leaseduration" ref="leaseduration">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.leaseduration')" />
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="form.leaseduration"
|
||||
:placeholder="$t('label.instance.lease.placeholder')"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :lg="12">
|
||||
<a-form-item name="leaseexpiryaction" ref="leaseexpiryaction">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.leaseexpiryaction')" />
|
||||
</template>
|
||||
<a-select v-model:value="form.leaseexpiryaction" :defaultValue="expiryActions">
|
||||
<a-select-option v-for="action in expiryActions" :key="action" :label="action" />
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<div :span="24" class="action-button">
|
||||
<a-button :loading="loading" @click="onCloseAction">{{ $t('label.cancel') }}</a-button>
|
||||
|
|
@ -165,6 +193,15 @@ export default {
|
|||
groups: {
|
||||
loading: false,
|
||||
opts: []
|
||||
},
|
||||
isLeaseEditable: this.$store.getters.features.instanceleaseenabled && this.resource.leaseduration > -1,
|
||||
showLeaseOptions: false,
|
||||
leaseduration: this.resource.leaseduration === undefined ? 90 : this.resource.leaseduration,
|
||||
leaseexpiryaction: this.resource.leaseexpiryaction === undefined ? 'STOP' : this.resource.leaseexpiryaction,
|
||||
expiryActions: ['STOP', 'DESTROY'],
|
||||
naturalNumberRule: {
|
||||
type: 'number',
|
||||
validator: this.validateNumber
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -186,9 +223,14 @@ export default {
|
|||
deleteprotection: this.resource.deleteprotection,
|
||||
group: this.resource.group,
|
||||
userdata: '',
|
||||
haenable: this.resource.haenable
|
||||
haenable: this.resource.haenable,
|
||||
leaseduration: this.resource.leaseduration,
|
||||
leaseexpiryaction: this.resource.leaseexpiryaction
|
||||
})
|
||||
this.rules = reactive({})
|
||||
this.rules = reactive({
|
||||
leaseduration: [this.naturalNumberRule]
|
||||
})
|
||||
this.showLeaseOptions = this.isLeaseEditable
|
||||
},
|
||||
fetchData () {
|
||||
this.fetchZoneDetails()
|
||||
|
|
@ -327,7 +369,6 @@ export default {
|
|||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleSubmit () {
|
||||
this.formRef.value.validate().then(() => {
|
||||
const values = toRaw(this.form)
|
||||
|
|
@ -354,6 +395,12 @@ export default {
|
|||
if (values.userdata && values.userdata.length > 0) {
|
||||
params.userdata = this.$toBase64AndURIEncoded(values.userdata)
|
||||
}
|
||||
if (values.leaseduration !== undefined && (values.leaseduration === -1 || values.leaseduration > 0)) {
|
||||
params.leaseduration = values.leaseduration
|
||||
if (values.leaseexpiryaction !== undefined) {
|
||||
params.leaseexpiryaction = values.leaseexpiryaction
|
||||
}
|
||||
}
|
||||
this.loading = true
|
||||
|
||||
api('updateVirtualMachine', {}, 'POST', params).then(json => {
|
||||
|
|
@ -372,6 +419,21 @@ export default {
|
|||
},
|
||||
onCloseAction () {
|
||||
this.$emit('close-action')
|
||||
},
|
||||
onToggleLeaseData () {
|
||||
if (this.showLeaseOptions === false) {
|
||||
this.form.leaseduration = -1
|
||||
this.form.leaseexpiryaction = undefined
|
||||
} else {
|
||||
this.form.leaseduration = this.leaseduration
|
||||
this.form.leaseexpiryaction = this.leaseexpiryaction
|
||||
}
|
||||
},
|
||||
async validateNumber (rule, value) {
|
||||
if (value && (isNaN(value) || value <= 0)) {
|
||||
return Promise.reject(this.$t('message.error.number'))
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,23 @@
|
|||
<template v-if="column.key === 'cpu'"><appstore-outlined /> {{ $t('label.cpu') }}</template>
|
||||
<template v-if="column.key === 'ram'"><bulb-outlined /> {{ $t('label.memory') }}</template>
|
||||
</template>
|
||||
<template #displayText="{ record }">
|
||||
<span>{{ record.name }}</span>
|
||||
<span
|
||||
v-if="record.leaseduration !== undefined"
|
||||
:style="{
|
||||
'margin-right': '10px',
|
||||
'float': 'right'}">
|
||||
<a-tooltip>
|
||||
<template #title>{{ $t('label.remainingdays') + ": " + getRemainingLeaseText(record.leaseduration) }}</template>
|
||||
<field-time-outlined
|
||||
:style="{
|
||||
color: $store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#888' },
|
||||
fontSize: '20px'
|
||||
}"/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<div style="display: block; text-align: right;">
|
||||
|
|
@ -119,7 +136,8 @@ export default {
|
|||
key: 'name',
|
||||
dataIndex: 'name',
|
||||
title: this.$t('label.serviceofferingid'),
|
||||
width: '40%'
|
||||
width: '40%',
|
||||
slots: { customRender: 'displayText' }
|
||||
},
|
||||
{
|
||||
key: 'cpu',
|
||||
|
|
@ -191,7 +209,8 @@ export default {
|
|||
name: item.name,
|
||||
cpu: cpuNumberValue.length > 0 ? `${cpuNumberValue} CPU x ${cpuSpeedValue} Ghz` : '',
|
||||
ram: ramValue.length > 0 ? `${ramValue} MB` : '',
|
||||
disabled: disabled
|
||||
disabled: disabled,
|
||||
leaseduration: item.leaseduration
|
||||
}
|
||||
})
|
||||
},
|
||||
|
|
@ -269,6 +288,15 @@ export default {
|
|||
this.$emit('select-compute-item', record.key)
|
||||
}
|
||||
}
|
||||
},
|
||||
getRemainingLeaseText (leaseDuration) {
|
||||
if (leaseDuration > 0) {
|
||||
return leaseDuration + (leaseDuration === 1 ? ' day' : ' days')
|
||||
} else if (leaseDuration === 0) {
|
||||
return 'expiring today'
|
||||
} else {
|
||||
return 'over'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,18 @@
|
|||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12" v-if="'listVirtualMachines' in $store.getters.apis && isLeaseFeatureEnabled">
|
||||
<router-link :to="{ path: '/vm', query: { leased: true } }">
|
||||
<a-statistic
|
||||
:title="$t('label.leasedinstances')"
|
||||
:value="data.leasedinstances"
|
||||
:value-style="{ color: $config.theme['@primary-color'] }">
|
||||
<template #prefix>
|
||||
<field-time-outlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</router-link>
|
||||
</a-col>
|
||||
<a-col :span="12" v-if="'listKubernetesClusters' in $store.getters.apis">
|
||||
<router-link :to="{ path: '/kubernetes' }">
|
||||
<a-statistic
|
||||
|
|
@ -393,8 +405,10 @@ export default {
|
|||
networks: 0,
|
||||
vpcs: 0,
|
||||
ips: 0,
|
||||
templates: 0
|
||||
}
|
||||
templates: 0,
|
||||
leasedinstances: 0
|
||||
},
|
||||
isLeaseFeatureEnabled: this.$store.getters.features.instanceleaseenabled
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -556,6 +570,15 @@ export default {
|
|||
this.loading = false
|
||||
this.data.stopped = json?.listvirtualmachinesresponse?.count
|
||||
})
|
||||
if (this.isLeaseFeatureEnabled) {
|
||||
api('listVirtualMachines', { leased: true, listall: true, details: 'min', page: 1, pagesize: 1 }).then(json => {
|
||||
this.loading = false
|
||||
this.data.leasedinstances = json?.listvirtualmachinesresponse?.count
|
||||
if (!this.data.leasedinstances) {
|
||||
this.data.leasedinstances = 0
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
listEvents () {
|
||||
if (!('listEvents' in this.$store.getters.apis)) {
|
||||
|
|
|
|||
|
|
@ -349,6 +349,34 @@
|
|||
</template>
|
||||
<a-switch v-model:checked="form.purgeresources"/>
|
||||
</a-form-item>
|
||||
<a-form-item name="showLeaseOptions" ref="showLeaseOptions" v-if="isLeaseFeatureEnabled">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.lease.enable')" :tooltip="$t('label.lease.enable.tooltip')" />
|
||||
</template>
|
||||
<a-switch v-model:checked="showLeaseOptions" @change="onToggleLeaseData"/>
|
||||
</a-form-item>
|
||||
<a-row :gutter="12" v-if="isLeaseFeatureEnabled && showLeaseOptions">
|
||||
<a-col :md="12" :lg="12">
|
||||
<a-form-item name="leaseduration" ref="leaseduration">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.leaseduration')"/>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="form.leaseduration"
|
||||
:placeholder="$t('label.instance.lease.placeholder')"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :lg="12">
|
||||
<a-form-item name="leaseexpiryaction" ref="leaseexpiryaction" v-if="form.leaseduration > 0">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.leaseexpiryaction')" />
|
||||
</template>
|
||||
<a-select v-model:value="form.leaseexpiryaction" :defaultValue="expiryActions">
|
||||
<a-select-option v-for="action in expiryActions" :key="action" :label="action"/>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item name="computeonly" ref="computeonly">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.computeonly.offering')" :tooltip="$t('label.computeonly.offering.tooltip')"/>
|
||||
|
|
@ -695,7 +723,14 @@ export default {
|
|||
diskOfferings: [],
|
||||
selectedDiskOfferingId: '',
|
||||
qosType: '',
|
||||
isDomainAdminAllowedToInformTags: false
|
||||
isDomainAdminAllowedToInformTags: false,
|
||||
isLeaseFeatureEnabled: this.$store.getters.features.instanceleaseenabled,
|
||||
showLeaseOptions: false,
|
||||
expiryActions: ['STOP', 'DESTROY'],
|
||||
defaultLeaseDuration: 90,
|
||||
defaultLeaseExpiryAction: 'STOP',
|
||||
leaseduration: undefined,
|
||||
leaseexpiryaction: undefined
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
|
|
@ -734,7 +769,9 @@ export default {
|
|||
iscustomizeddiskiops: this.isCustomizedDiskIops,
|
||||
diskofferingid: this.selectedDiskOfferingId,
|
||||
diskofferingstrictness: this.diskofferingstrictness,
|
||||
encryptdisk: this.encryptdisk
|
||||
encryptdisk: this.encryptdisk,
|
||||
leaseduration: this.leaseduration,
|
||||
leaseexpiryaction: this.leaseexpiryaction
|
||||
})
|
||||
this.rules = reactive({
|
||||
name: [{ required: true, message: this.$t('message.error.required.input') }],
|
||||
|
|
@ -785,7 +822,8 @@ export default {
|
|||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
}]
|
||||
}],
|
||||
leaseduration: [this.naturalNumberRule]
|
||||
})
|
||||
},
|
||||
fetchData () {
|
||||
|
|
@ -965,7 +1003,9 @@ export default {
|
|||
dynamicscalingenabled: values.dynamicscalingenabled,
|
||||
diskofferingstrictness: values.diskofferingstrictness,
|
||||
encryptroot: values.encryptdisk,
|
||||
purgeresources: values.purgeresources
|
||||
purgeresources: values.purgeresources,
|
||||
leaseduration: values.leaseduration,
|
||||
leaseexpiryaction: values.leaseexpiryaction
|
||||
}
|
||||
if (values.diskofferingid) {
|
||||
params.diskofferingid = values.diskofferingid
|
||||
|
|
@ -1061,6 +1101,15 @@ export default {
|
|||
if ('systemvmtype' in values && values.systemvmtype !== undefined) {
|
||||
params.systemvmtype = values.systemvmtype
|
||||
}
|
||||
|
||||
if ('leaseduration' in values && values.leaseduration !== undefined) {
|
||||
params.leaseduration = values.leaseduration
|
||||
}
|
||||
|
||||
if ('leaseexpiryaction' in values && values.leaseexpiryaction !== undefined) {
|
||||
params.leaseexpiryaction = values.leaseexpiryaction
|
||||
}
|
||||
|
||||
if (values.ispublic !== true) {
|
||||
var domainIndexes = values.domainid
|
||||
var domainId = null
|
||||
|
|
@ -1112,6 +1161,17 @@ export default {
|
|||
return Promise.reject(this.$t('message.error.number'))
|
||||
}
|
||||
return Promise.resolve()
|
||||
},
|
||||
onToggleLeaseData () {
|
||||
if (this.showLeaseOptions === false) {
|
||||
this.leaseduration = undefined
|
||||
this.leaseexpiryaction = undefined
|
||||
} else {
|
||||
this.leaseduration = this.leaseduration !== undefined ? this.leaseduration : this.defaultLeaseDuration
|
||||
this.leaseexpiryaction = this.leaseexpiryaction !== undefined ? this.leaseexpiryaction : this.defaultLeaseExpiryAction
|
||||
}
|
||||
this.form.leaseduration = this.leaseduration
|
||||
this.form.leaseexpiryaction = this.leaseexpiryaction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue