mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Instance lease: Allow deployment of instances with lease duration and leaseexpiry action (#10560)
* FR-248: Instance lease, WIP commit * insert lease expiry into db and use that to filter exiring vms, add asyncjobmanager * Add leaseDuration and leaseExpiryAction in Service offering create flow * Update listVM cmd to allow listing only leased instances * Add methods to fetch instances for which lease is expiring in next days * Changes included: config key setup and configured for alert email lease options in create and update vm screen handle delete protection, edit vm, create vm validated stop and detroy, delete protection * Update UI screens for leased properties coming from config and service offering * use global lock before running scheduler * Unit tests * Flow changes done in UI based on discussion * Include view changes in schema upgrade files and use feature in various UI elements * Added integration test for vm deployment, UI enhancements for user persona, bug fixes * validate integration tests, minor ui changes and log messages * fix build: moving configkey from setup to test itself * Disable testAlert to unblock build and trim whitespaces in integration tests * Address review comments * Minor changes in EditVM screen * Use ExecutorService instead of Timer and TimerTask * Additional review comments * Incorporate following changes: 1. Execute lease action once on the instance 2. Cancel lease on instance when feature is disabled 3. Relevant events when lease gets disabled, cancelled, executed 4. Disable associating lease after deployment 5. UI elements and flow changes 6. Changes based on feedback from demo * Handle pr review comments * address review comments * move instance.lease.enabled config to VMLeaseManager interface * bug fix in edit instance flow and reject api request for invalid values * max allowed lease is for 100 years * log instance ids for expired instance * Fix config validation for value range and code coverage improvement * fix lease expiry request failures in async * dont use forced: true for StopVmCmd * Update server/src/main/java/org/apache/cloudstack/vm/lease/VMLeaseManager.java Co-authored-by: Vishesh <vishesh92@gmail.com> * handle review comments --------- Co-authored-by: Rohit Yadav <rohityadav89@gmail.com> Co-authored-by: Vishesh <vishesh92@gmail.com>
This commit is contained in:
parent
650b5ec3da
commit
7632814cd2
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -164,7 +164,8 @@ jobs:
|
|||||||
component/test_cpu_limits
|
component/test_cpu_limits
|
||||||
component/test_cpu_max_limits
|
component/test_cpu_max_limits
|
||||||
component/test_cpu_project_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_egress_fw_rules
|
||||||
component/test_invalid_gw_nm
|
component/test_invalid_gw_nm
|
||||||
component/test_ip_reservation",
|
component/test_ip_reservation",
|
||||||
|
|||||||
@ -796,6 +796,11 @@ public class EventTypes {
|
|||||||
// Resource Limit
|
// Resource Limit
|
||||||
public static final String EVENT_RESOURCE_LIMIT_UPDATE = "RESOURCE.LIMIT.UPDATE";
|
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 {
|
static {
|
||||||
|
|
||||||
// TODO: need a way to force author adding event types to declare the entity details as well, with out braking
|
// TODO: need a way to force author adding event types to declare the entity details as well, with out braking
|
||||||
@ -1290,6 +1295,12 @@ public class EventTypes {
|
|||||||
entityEventDetails.put(EVENT_SHAREDFS_DESTROY, SharedFS.class);
|
entityEventDetails.put(EVENT_SHAREDFS_DESTROY, SharedFS.class);
|
||||||
entityEventDetails.put(EVENT_SHAREDFS_EXPUNGE, SharedFS.class);
|
entityEventDetails.put(EVENT_SHAREDFS_EXPUNGE, SharedFS.class);
|
||||||
entityEventDetails.put(EVENT_SHAREDFS_RECOVER, 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) {
|
public static boolean isNetworkEvent(String eventType) {
|
||||||
|
|||||||
@ -110,4 +110,8 @@ public interface VmDetailConstants {
|
|||||||
// CPU mode and model, ADMIN only
|
// CPU mode and model, ADMIN only
|
||||||
String GUEST_CPU_MODE = "guest.cpu.mode";
|
String GUEST_CPU_MODE = "guest.cpu.mode";
|
||||||
String GUEST_CPU_MODEL = "guest.cpu.model";
|
String GUEST_CPU_MODEL = "guest.cpu.model";
|
||||||
|
|
||||||
|
String INSTANCE_LEASE_EXPIRY_DATE = "leaseexpirydate";
|
||||||
|
String INSTANCE_LEASE_EXPIRY_ACTION = "leaseexpiryaction";
|
||||||
|
String INSTANCE_LEASE_EXECUTION = "leaseactionexecution";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -269,7 +269,10 @@ public class ApiConstants {
|
|||||||
public static final String INTERNAL_DNS2 = "internaldns2";
|
public static final String INTERNAL_DNS2 = "internaldns2";
|
||||||
public static final String INTERNET_PROTOCOL = "internetprotocol";
|
public static final String INTERNET_PROTOCOL = "internetprotocol";
|
||||||
public static final String INTERVAL_TYPE = "intervaltype";
|
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 = "iopsreadrate";
|
||||||
public static final String IOPS_READ_RATE_MAX = "iopsreadratemax";
|
public static final String IOPS_READ_RATE_MAX = "iopsreadratemax";
|
||||||
public static final String IOPS_READ_RATE_MAX_LENGTH = "iopsreadratemaxlength";
|
public static final String IOPS_READ_RATE_MAX_LENGTH = "iopsreadratemaxlength";
|
||||||
@ -317,11 +320,13 @@ public class ApiConstants {
|
|||||||
public static final String LAST_BOOT = "lastboottime";
|
public static final String LAST_BOOT = "lastboottime";
|
||||||
public static final String LAST_SERVER_START = "lastserverstart";
|
public static final String LAST_SERVER_START = "lastserverstart";
|
||||||
public static final String LAST_SERVER_STOP = "lastserverstop";
|
public static final String LAST_SERVER_STOP = "lastserverstop";
|
||||||
|
public static final String LEASED = "leased";
|
||||||
public static final String LEVEL = "level";
|
public static final String LEVEL = "level";
|
||||||
public static final String LENGTH = "length";
|
public static final String LENGTH = "length";
|
||||||
public static final String LIMIT = "limit";
|
public static final String LIMIT = "limit";
|
||||||
public static final String LIMIT_CPU_USE = "limitcpuuse";
|
public static final String LIMIT_CPU_USE = "limitcpuuse";
|
||||||
public static final String LIST_HOSTS = "listhosts";
|
public static final String LIST_HOSTS = "listhosts";
|
||||||
|
public static final String LOCATION_TYPE = "locationtype";
|
||||||
public static final String LOCK = "lock";
|
public static final String LOCK = "lock";
|
||||||
public static final String LUN = "lun";
|
public static final String LUN = "lun";
|
||||||
public static final String LBID = "lbruleid";
|
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.VsphereStoragePoliciesResponse;
|
||||||
import org.apache.cloudstack.api.response.ZoneResponse;
|
import org.apache.cloudstack.api.response.ZoneResponse;
|
||||||
import org.apache.cloudstack.api.response.DiskOfferingResponse;
|
import org.apache.cloudstack.api.response.DiskOfferingResponse;
|
||||||
|
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||||
import org.apache.commons.collections.MapUtils;
|
import org.apache.commons.collections.MapUtils;
|
||||||
|
import org.apache.commons.lang3.EnumUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
|
|
||||||
@ -251,7 +253,15 @@ public class CreateServiceOfferingCmd extends BaseCmd {
|
|||||||
since="4.20")
|
since="4.20")
|
||||||
private Boolean purgeResources;
|
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 ///////////////////////
|
/////////////////// Accessors ///////////////////////
|
||||||
@ -487,6 +497,22 @@ public class CreateServiceOfferingCmd extends BaseCmd {
|
|||||||
return false;
|
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() {
|
public boolean isPurgeResources() {
|
||||||
return Boolean.TRUE.equals(purgeResources);
|
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.setInstancesDisksStatsRetentionTime((Integer) capabilities.get(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_TIME));
|
||||||
response.setSharedFsVmMinCpuCount((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT));
|
response.setSharedFsVmMinCpuCount((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT));
|
||||||
response.setSharedFsVmMinRamSize((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE));
|
response.setSharedFsVmMinRamSize((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE));
|
||||||
|
response.setInstanceLeaseEnabled((Boolean) capabilities.get(ApiConstants.INSTANCE_LEASE_ENABLED));
|
||||||
response.setObjectName("capability");
|
response.setObjectName("capability");
|
||||||
response.setResponseName(getCommandName());
|
response.setResponseName(getCommandName());
|
||||||
this.setResponseObject(response);
|
this.setResponseObject(response);
|
||||||
|
|||||||
@ -16,17 +16,24 @@
|
|||||||
// under the License.
|
// under the License.
|
||||||
package org.apache.cloudstack.api.command.user.vm;
|
package org.apache.cloudstack.api.command.user.vm;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import com.cloud.agent.api.LogLevel;
|
||||||
import java.util.Arrays;
|
import com.cloud.event.EventTypes;
|
||||||
import java.util.Collection;
|
import com.cloud.exception.ConcurrentOperationException;
|
||||||
import java.util.HashMap;
|
import com.cloud.exception.InsufficientCapacityException;
|
||||||
import java.util.Iterator;
|
import com.cloud.exception.InsufficientServerCapacityException;
|
||||||
import java.util.LinkedHashMap;
|
import com.cloud.exception.InvalidParameterValueException;
|
||||||
import java.util.List;
|
import com.cloud.exception.ResourceAllocationException;
|
||||||
import java.util.Map;
|
import com.cloud.exception.ResourceUnavailableException;
|
||||||
|
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||||
import javax.annotation.Nonnull;
|
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.acl.RoleType;
|
||||||
import org.apache.cloudstack.affinity.AffinityGroupResponse;
|
import org.apache.cloudstack.affinity.AffinityGroupResponse;
|
||||||
import org.apache.cloudstack.api.ACL;
|
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.UserVmResponse;
|
||||||
import org.apache.cloudstack.api.response.ZoneResponse;
|
import org.apache.cloudstack.api.response.ZoneResponse;
|
||||||
import org.apache.cloudstack.context.CallContext;
|
import org.apache.cloudstack.context.CallContext;
|
||||||
|
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.apache.commons.collections.MapUtils;
|
import org.apache.commons.collections.MapUtils;
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
|
import org.apache.commons.lang3.EnumUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import com.cloud.agent.api.LogLevel;
|
import javax.annotation.Nonnull;
|
||||||
import com.cloud.event.EventTypes;
|
import java.util.ArrayList;
|
||||||
import com.cloud.exception.ConcurrentOperationException;
|
import java.util.Arrays;
|
||||||
import com.cloud.exception.InsufficientCapacityException;
|
import java.util.Collection;
|
||||||
import com.cloud.exception.InsufficientServerCapacityException;
|
import java.util.HashMap;
|
||||||
import com.cloud.exception.InvalidParameterValueException;
|
import java.util.Iterator;
|
||||||
import com.cloud.exception.ResourceAllocationException;
|
import java.util.LinkedHashMap;
|
||||||
import com.cloud.exception.ResourceUnavailableException;
|
import java.util.List;
|
||||||
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
import java.util.Map;
|
||||||
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;
|
|
||||||
|
|
||||||
@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},
|
@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)
|
requestHasSensitiveInfo = false, responseHasSensitiveInfo = true)
|
||||||
@ -278,6 +278,14 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG
|
|||||||
description = "Enable packed virtqueues or not.")
|
description = "Enable packed virtqueues or not.")
|
||||||
private Boolean nicPackedVirtQueues;
|
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 ///////////////////////
|
/////////////////// Accessors ///////////////////////
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
@ -475,6 +483,22 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG
|
|||||||
return password;
|
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() {
|
public List<Long> getNetworkIds() {
|
||||||
if (MapUtils.isNotEmpty(vAppNetworks)) {
|
if (MapUtils.isNotEmpty(vAppNetworks)) {
|
||||||
if (CollectionUtils.isNotEmpty(networkIds) || ipAddress != null || getIp6Address() != null || MapUtils.isNotEmpty(ipToNetworkList)) {
|
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")
|
since = "4.20.1")
|
||||||
private String arch;
|
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 ///////////////////////
|
/////////////////// Accessors ///////////////////////
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
@ -303,6 +308,10 @@ public class ListVMsCmd extends BaseListRetrieveOnlyResourceCountCmd implements
|
|||||||
return StringUtils.isBlank(arch) ? null : CPU.CPUArch.fromType(arch);
|
return StringUtils.isBlank(arch) ? null : CPU.CPUArch.fromType(arch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getOnlyLeasedInstances() {
|
||||||
|
return BooleanUtils.toBoolean(onlyLeasedInstances);
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
/////////////// API Implementation///////////////////
|
/////////////// API Implementation///////////////////
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
|
|||||||
@ -16,20 +16,19 @@
|
|||||||
// under the License.
|
// under the License.
|
||||||
package org.apache.cloudstack.api.command.user.vm;
|
package org.apache.cloudstack.api.command.user.vm;
|
||||||
|
|
||||||
import java.util.Collection;
|
import com.cloud.exception.InsufficientCapacityException;
|
||||||
import java.util.HashMap;
|
import com.cloud.exception.InvalidParameterValueException;
|
||||||
import java.util.List;
|
import com.cloud.exception.ResourceUnavailableException;
|
||||||
import java.util.Map;
|
import com.cloud.user.Account;
|
||||||
|
import com.cloud.uservm.UserVm;
|
||||||
import com.cloud.utils.exception.CloudRuntimeException;
|
import com.cloud.utils.exception.CloudRuntimeException;
|
||||||
|
import com.cloud.utils.net.Dhcp;
|
||||||
import org.apache.cloudstack.api.ApiArgValidator;
|
import com.cloud.vm.VirtualMachine;
|
||||||
import org.apache.cloudstack.api.response.UserDataResponse;
|
|
||||||
|
|
||||||
import org.apache.cloudstack.acl.RoleType;
|
import org.apache.cloudstack.acl.RoleType;
|
||||||
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
|
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
|
||||||
import org.apache.cloudstack.api.ACL;
|
import org.apache.cloudstack.api.ACL;
|
||||||
import org.apache.cloudstack.api.APICommand;
|
import org.apache.cloudstack.api.APICommand;
|
||||||
|
import org.apache.cloudstack.api.ApiArgValidator;
|
||||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||||
import org.apache.cloudstack.api.ApiConstants;
|
import org.apache.cloudstack.api.ApiConstants;
|
||||||
import org.apache.cloudstack.api.ApiErrorCode;
|
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.command.user.UserCmd;
|
||||||
import org.apache.cloudstack.api.response.GuestOSResponse;
|
import org.apache.cloudstack.api.response.GuestOSResponse;
|
||||||
import org.apache.cloudstack.api.response.SecurityGroupResponse;
|
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.api.response.UserVmResponse;
|
||||||
import org.apache.cloudstack.context.CallContext;
|
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 java.util.Collection;
|
||||||
import com.cloud.exception.ResourceUnavailableException;
|
import java.util.HashMap;
|
||||||
import com.cloud.user.Account;
|
import java.util.List;
|
||||||
import com.cloud.uservm.UserVm;
|
import java.util.Map;
|
||||||
import com.cloud.utils.net.Dhcp;
|
|
||||||
import com.cloud.vm.VirtualMachine;
|
|
||||||
|
|
||||||
@APICommand(name = "updateVirtualMachine", description="Updates properties of a virtual machine. The VM has to be stopped and restarted for the " +
|
@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. " +
|
"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.")
|
" autoscaling groups or CKS, delete protection will be ignored.")
|
||||||
private Boolean deleteProtection;
|
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 ///////////////////////
|
/////////////////// Accessors ///////////////////////
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
@ -324,4 +333,21 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction,
|
|||||||
public ApiCommandResourceType getApiResourceType() {
|
public ApiCommandResourceType getApiResourceType() {
|
||||||
return ApiCommandResourceType.VirtualMachine;
|
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")
|
@Param(description = "the min Ram size for the service offering used by the shared filesystem instance", since = "4.20.0")
|
||||||
private Integer sharedFsVmMinRamSize;
|
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) {
|
public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) {
|
||||||
this.securityGroupsEnabled = securityGroupsEnabled;
|
this.securityGroupsEnabled = securityGroupsEnabled;
|
||||||
}
|
}
|
||||||
@ -247,4 +251,8 @@ public class CapabilitiesResponse extends BaseResponse {
|
|||||||
public void setSharedFsVmMinRamSize(Integer sharedFsVmMinRamSize) {
|
public void setSharedFsVmMinRamSize(Integer sharedFsVmMinRamSize) {
|
||||||
this.sharedFsVmMinRamSize = 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")
|
@Param(description = "Whether to cleanup VM and its associated resource upon expunge", since = "4.20")
|
||||||
private Boolean purgeResources;
|
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() {
|
public ServiceOfferingResponse() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,6 +513,22 @@ public class ServiceOfferingResponse extends BaseResponseWithAnnotations {
|
|||||||
this.cacheMode = cacheMode;
|
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() {
|
public String getVsphereStoragePolicy() {
|
||||||
return vsphereStoragePolicy;
|
return vsphereStoragePolicy;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -392,7 +392,7 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
|
|||||||
@Param(description = "VNF details", since = "4.19.0")
|
@Param(description = "VNF details", since = "4.19.0")
|
||||||
private Map<String, String> vnfDetails;
|
private Map<String, String> vnfDetails;
|
||||||
|
|
||||||
@SerializedName((ApiConstants.VM_TYPE))
|
@SerializedName(ApiConstants.VM_TYPE)
|
||||||
@Param(description = "User VM type", since = "4.20.0")
|
@Param(description = "User VM type", since = "4.20.0")
|
||||||
private String vmType;
|
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")
|
@Param(description = "CPU arch of the VM", since = "4.20.1")
|
||||||
private String arch;
|
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() {
|
public UserVmResponse() {
|
||||||
securityGroupList = new LinkedHashSet<>();
|
securityGroupList = new LinkedHashSet<>();
|
||||||
nics = new TreeSet<>(Comparator.comparingInt(x -> Integer.parseInt(x.getDeviceId())));
|
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) {
|
public void setArch(String arch) {
|
||||||
this.arch = 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;
|
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.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@ -55,4 +57,25 @@ public class CreateServiceOfferingCmdTest {
|
|||||||
ReflectionTestUtils.setField(createServiceOfferingCmd, "purgeResources", true);
|
ReflectionTestUtils.setField(createServiceOfferingCmd, "purgeResources", true);
|
||||||
Assert.assertTrue(createServiceOfferingCmd.isPurgeResources());
|
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`.`dynamic_scaling_enabled` AS `dynamic_scaling_enabled`,
|
||||||
`service_offering`.`disk_offering_strictness` AS `disk_offering_strictness`,
|
`service_offering`.`disk_offering_strictness` AS `disk_offering_strictness`,
|
||||||
`vsphere_storage_policy`.`value` AS `vsphere_storage_policy`,
|
`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.id)) AS domain_id,
|
||||||
GROUP_CONCAT(DISTINCT(domain.uuid)) AS domain_uuid,
|
GROUP_CONCAT(DISTINCT(domain.uuid)) AS domain_uuid,
|
||||||
GROUP_CONCAT(DISTINCT(domain.name)) AS domain_name,
|
GROUP_CONCAT(DISTINCT(domain.name)) AS domain_name,
|
||||||
@ -109,5 +111,11 @@ FROM
|
|||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
`cloud`.`service_offering_details` AS `vsphere_storage_policy` ON `vsphere_storage_policy`.`service_offering_id` = `service_offering`.`id`
|
`cloud`.`service_offering_details` AS `vsphere_storage_policy` ON `vsphere_storage_policy`.`service_offering_id` = `service_offering`.`id`
|
||||||
AND `vsphere_storage_policy`.`name` = 'storagepolicy'
|
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
|
GROUP BY
|
||||||
`service_offering`.`id`;
|
`service_offering`.`id`;
|
||||||
|
|||||||
@ -169,7 +169,10 @@ SELECT
|
|||||||
`user_data`.`uuid` AS `user_data_uuid`,
|
`user_data`.`uuid` AS `user_data_uuid`,
|
||||||
`user_data`.`name` AS `user_data_name`,
|
`user_data`.`name` AS `user_data_name`,
|
||||||
`user_vm`.`user_data_details` AS `user_data_details`,
|
`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
|
FROM
|
||||||
(((((((((((((((((((((((((((((((((((`user_vm`
|
(((((((((((((((((((((((((((((((((((`user_vm`
|
||||||
JOIN `vm_instance` ON (((`vm_instance`.`id` = `user_vm`.`id`)
|
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`)
|
LEFT JOIN `user_vm_details` `custom_speed` ON (((`custom_speed`.`vm_id` = `vm_instance`.`id`)
|
||||||
AND (`custom_speed`.`name` = 'CpuSpeed'))))
|
AND (`custom_speed`.`name` = 'CpuSpeed'))))
|
||||||
LEFT JOIN `user_vm_details` `custom_ram_size` ON (((`custom_ram_size`.`vm_id` = `vm_instance`.`id`)
|
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.StoragePoolDetailsDao;
|
||||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||||
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
|
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.CollectionUtils;
|
||||||
import org.apache.commons.collections.MapUtils;
|
import org.apache.commons.collections.MapUtils;
|
||||||
import org.apache.commons.lang3.EnumUtils;
|
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);
|
Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(cmd.getDomainId(), cmd.isRecursive(), null);
|
||||||
accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, listAll, false);
|
accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, listAll, false);
|
||||||
Long domainId = domainIdRecursiveListProject.first();
|
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);
|
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) {
|
if (keyPairName != null) {
|
||||||
SearchBuilder<UserVmDetailVO> vmDetailSearchKeys = userVmDetailsDao.createSearchBuilder();
|
SearchBuilder<UserVmDetailVO> vmDetailSearchKeys = userVmDetailsDao.createSearchBuilder();
|
||||||
SearchBuilder<UserVmDetailVO> vmDetailSearchVmIds = 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.api.response.ServiceOfferingResponse;
|
||||||
import org.apache.cloudstack.context.CallContext;
|
import org.apache.cloudstack.context.CallContext;
|
||||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||||
|
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Component;
|
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;
|
long rootDiskSizeInGb = (long) offering.getRootDiskSize() / GB_TO_BYTES;
|
||||||
offeringResponse.setRootDiskSize(rootDiskSizeInGb);
|
offeringResponse.setRootDiskSize(rootDiskSizeInGb);
|
||||||
offeringResponse.setDiskOfferingStrictness(offering.getDiskOfferingStrictness());
|
offeringResponse.setDiskOfferingStrictness(offering.getDiskOfferingStrictness());
|
||||||
|
|||||||
@ -16,18 +16,17 @@
|
|||||||
// under the License.
|
// under the License.
|
||||||
package com.cloud.api.query.dao;
|
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.api.query.vo.UserVmJoinVO;
|
||||||
import com.cloud.user.Account;
|
import com.cloud.user.Account;
|
||||||
import com.cloud.uservm.UserVm;
|
import com.cloud.uservm.UserVm;
|
||||||
import com.cloud.utils.db.GenericDao;
|
import com.cloud.utils.db.GenericDao;
|
||||||
import com.cloud.vm.VirtualMachine;
|
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> {
|
public interface UserVmJoinDao extends GenericDao<UserVmJoinVO, Long> {
|
||||||
|
|
||||||
@ -46,4 +45,8 @@ public interface UserVmJoinDao extends GenericDao<UserVmJoinVO, Long> {
|
|||||||
|
|
||||||
List<UserVmJoinVO> listByAccountServiceOfferingTemplateAndNotInState(long accountId,
|
List<UserVmJoinVO> listByAccountServiceOfferingTemplateAndNotInState(long accountId,
|
||||||
List<VirtualMachine.State> states, List<Long> offeringIds, List<Long> templateIds);
|
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.
|
// under the License.
|
||||||
package com.cloud.api.query.dao;
|
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.ApiDBUtils;
|
||||||
import com.cloud.api.ApiResponseHelper;
|
import com.cloud.api.ApiResponseHelper;
|
||||||
import com.cloud.api.query.vo.UserVmJoinVO;
|
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.NicExtraDhcpOptionDao;
|
||||||
import com.cloud.vm.dao.NicSecondaryIpVO;
|
import com.cloud.vm.dao.NicSecondaryIpVO;
|
||||||
import com.cloud.vm.dao.UserVmDetailsDao;
|
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
|
@Component
|
||||||
public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJoinVO, UserVmResponse> implements UserVmJoinDao {
|
public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJoinVO, UserVmResponse> implements UserVmJoinDao {
|
||||||
@ -108,9 +112,13 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
|
|||||||
VnfTemplateDetailsDao vnfTemplateDetailsDao;
|
VnfTemplateDetailsDao vnfTemplateDetailsDao;
|
||||||
@Inject
|
@Inject
|
||||||
VnfTemplateNicDao vnfTemplateNicDao;
|
VnfTemplateNicDao vnfTemplateNicDao;
|
||||||
|
@Inject
|
||||||
|
ConfigurationDao configurationDao;
|
||||||
|
|
||||||
private final SearchBuilder<UserVmJoinVO> VmDetailSearch;
|
private final SearchBuilder<UserVmJoinVO> VmDetailSearch;
|
||||||
private final SearchBuilder<UserVmJoinVO> activeVmByIsoSearch;
|
private final SearchBuilder<UserVmJoinVO> activeVmByIsoSearch;
|
||||||
|
private final SearchBuilder<UserVmJoinVO> leaseExpiredInstanceSearch;
|
||||||
|
private final SearchBuilder<UserVmJoinVO> remainingLeaseInDaysSearch;
|
||||||
|
|
||||||
protected UserVmJoinDaoImpl() {
|
protected UserVmJoinDaoImpl() {
|
||||||
|
|
||||||
@ -124,6 +132,29 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
|
|||||||
activeVmByIsoSearch.and("isoId", activeVmByIsoSearch.entity().getIsoId(), SearchCriteria.Op.EQ);
|
activeVmByIsoSearch.and("isoId", activeVmByIsoSearch.entity().getIsoId(), SearchCriteria.Op.EQ);
|
||||||
activeVmByIsoSearch.and("stateNotIn", activeVmByIsoSearch.entity().getState(), SearchCriteria.Op.NIN);
|
activeVmByIsoSearch.and("stateNotIn", activeVmByIsoSearch.entity().getState(), SearchCriteria.Op.NIN);
|
||||||
activeVmByIsoSearch.done();
|
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
|
@Override
|
||||||
@ -427,10 +458,10 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
|
|||||||
userVmResponse.setDynamicallyScalable(userVm.isDynamicallyScalable());
|
userVmResponse.setDynamicallyScalable(userVm.isDynamicallyScalable());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userVm.getDeleteProtection() == null) {
|
if (userVm.isDeleteProtection() == null) {
|
||||||
userVmResponse.setDeleteProtection(false);
|
userVmResponse.setDeleteProtection(false);
|
||||||
} else {
|
} else {
|
||||||
userVmResponse.setDeleteProtection(userVm.getDeleteProtection());
|
userVmResponse.setDeleteProtection(userVm.isDeleteProtection());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userVm.getAutoScaleVmGroupName() != null) {
|
if (userVm.getAutoScaleVmGroupName() != null) {
|
||||||
@ -447,6 +478,15 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
|
|||||||
userVmResponse.setUserDataPolicy(userVm.getUserDataPolicy());
|
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);
|
addVmRxTxDataToResponse(userVm, userVmResponse);
|
||||||
|
|
||||||
if (TemplateType.VNF.equals(userVm.getTemplateType()) && (details.contains(VMDetails.all) || details.contains(VMDetails.vnfnics))) {
|
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;
|
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) {
|
private void addVnfInfoToserVmResponse(UserVmJoinVO userVm, UserVmResponse userVmResponse) {
|
||||||
List<VnfTemplateNicVO> vnfNics = vnfTemplateNicDao.listByTemplateId(userVm.getTemplateId());
|
List<VnfTemplateNicVO> vnfNics = vnfTemplateNicDao.listByTemplateId(userVm.getTemplateId());
|
||||||
for (VnfTemplateNicVO nic : vnfNics) {
|
for (VnfTemplateNicVO nic : vnfNics) {
|
||||||
@ -718,4 +765,43 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
|
|||||||
sc.setParameters("displayVm", 1);
|
sc.setParameters("displayVm", 1);
|
||||||
return customSearch(sc, null);
|
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.
|
// under the License.
|
||||||
package com.cloud.api.query.vo;
|
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.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
@ -24,13 +29,7 @@ import javax.persistence.EnumType;
|
|||||||
import javax.persistence.Enumerated;
|
import javax.persistence.Enumerated;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
import java.util.Date;
|
||||||
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;
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "service_offering_view")
|
@Table(name = "service_offering_view")
|
||||||
@ -221,6 +220,13 @@ public class ServiceOfferingJoinVO extends BaseViewVO implements InternalIdentit
|
|||||||
@Column(name = "encrypt_root")
|
@Column(name = "encrypt_root")
|
||||||
private boolean encryptRoot;
|
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() {
|
public ServiceOfferingJoinVO() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,4 +465,12 @@ public class ServiceOfferingJoinVO extends BaseViewVO implements InternalIdentit
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean getEncryptRoot() { return encryptRoot; }
|
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.Enumerated;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.Temporal;
|
||||||
|
import javax.persistence.TemporalType;
|
||||||
import javax.persistence.Transient;
|
import javax.persistence.Transient;
|
||||||
|
|
||||||
import com.cloud.host.Status;
|
import com.cloud.host.Status;
|
||||||
@ -442,6 +444,15 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro
|
|||||||
@Column(name = "arch")
|
@Column(name = "arch")
|
||||||
protected String 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() {
|
public UserVmJoinVO() {
|
||||||
// Empty constructor
|
// Empty constructor
|
||||||
@ -952,7 +963,7 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro
|
|||||||
return isDynamicallyScalable;
|
return isDynamicallyScalable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean getDeleteProtection() {
|
public Boolean isDeleteProtection() {
|
||||||
return deleteProtection;
|
return deleteProtection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -984,4 +995,20 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro
|
|||||||
public String getArch() {
|
public String getArch() {
|
||||||
return arch;
|
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.jsinterpreter.TagAsRuleHelper;
|
||||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||||
import org.apache.cloudstack.vm.UnmanagedVMsManager;
|
import org.apache.cloudstack.vm.UnmanagedVMsManager;
|
||||||
|
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.apache.commons.collections.MapUtils;
|
import org.apache.commons.collections.MapUtils;
|
||||||
import org.apache.commons.lang3.EnumUtils;
|
import org.apache.commons.lang3.EnumUtils;
|
||||||
@ -478,6 +479,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||||||
NsxProviderDao nsxProviderDao;
|
NsxProviderDao nsxProviderDao;
|
||||||
@Inject
|
@Inject
|
||||||
ResourceManager resourceManager;
|
ResourceManager resourceManager;
|
||||||
|
@Inject
|
||||||
|
VMLeaseManager vmLeaseManager;
|
||||||
|
|
||||||
// FIXME - why don't we have interface for DataCenterLinkLocalIpAddressDao?
|
// FIXME - why don't we have interface for DataCenterLinkLocalIpAddressDao?
|
||||||
@Inject
|
@Inject
|
||||||
@ -585,6 +588,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||||||
configValuesForValidation.add(UserDataManager.VM_USERDATA_MAX_LENGTH_STRING);
|
configValuesForValidation.add(UserDataManager.VM_USERDATA_MAX_LENGTH_STRING);
|
||||||
configValuesForValidation.add(UnmanagedVMsManager.RemoteKvmInstanceDisksCopyTimeout.key());
|
configValuesForValidation.add(UnmanagedVMsManager.RemoteKvmInstanceDisksCopyTimeout.key());
|
||||||
configValuesForValidation.add(UnmanagedVMsManager.ConvertVmwareInstanceToKvmTimeout.key());
|
configValuesForValidation.add(UnmanagedVMsManager.ConvertVmwareInstanceToKvmTimeout.key());
|
||||||
|
configValuesForValidation.add(VMLeaseManager.InstanceLeaseSchedulerInterval.key());
|
||||||
|
configValuesForValidation.add(VMLeaseManager.InstanceLeaseExpiryEventSchedulerInterval.key());
|
||||||
|
configValuesForValidation.add(VMLeaseManager.InstanceLeaseExpiryEventDaysBefore.key());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void weightBasedParametersForValidation() {
|
protected void weightBasedParametersForValidation() {
|
||||||
@ -638,6 +644,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||||||
params.put(Config.RouterAggregationCommandEachTimeout.toString(), _configDao.getValue(Config.RouterAggregationCommandEachTimeout.toString()));
|
params.put(Config.RouterAggregationCommandEachTimeout.toString(), _configDao.getValue(Config.RouterAggregationCommandEachTimeout.toString()));
|
||||||
params.put(Config.MigrateWait.toString(), _configDao.getValue(Config.MigrateWait.toString()));
|
params.put(Config.MigrateWait.toString(), _configDao.getValue(Config.MigrateWait.toString()));
|
||||||
_agentManager.propagateChangeToAgents(params);
|
_agentManager.propagateChangeToAgents(params);
|
||||||
|
} else if (VMLeaseManager.InstanceLeaseEnabled.key().equals(globalSettingUpdated)) {
|
||||||
|
vmLeaseManager.onLeaseFeatureToggle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -3367,6 +3375,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(),
|
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.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(),
|
cmd.getNetworkRate(), cmd.getDeploymentPlanner(), details, cmd.getRootDiskSize(), isCustomizedIops, cmd.getMinIops(), cmd.getMaxIops(),
|
||||||
@ -3375,20 +3387,20 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||||||
cmd.getIopsReadRate(), cmd.getIopsReadRateMax(), cmd.getIopsReadRateMaxLength(),
|
cmd.getIopsReadRate(), cmd.getIopsReadRateMax(), cmd.getIopsReadRateMaxLength(),
|
||||||
cmd.getIopsWriteRate(), cmd.getIopsWriteRateMax(), cmd.getIopsWriteRateMaxLength(),
|
cmd.getIopsWriteRate(), cmd.getIopsWriteRateMax(), cmd.getIopsWriteRateMaxLength(),
|
||||||
cmd.getHypervisorSnapshotReserve(), cmd.getCacheMode(), storagePolicyId, cmd.getDynamicScalingEnabled(), diskOfferingId,
|
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,
|
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 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 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,
|
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 bytesReadRate, Long bytesReadRateMax, Long bytesReadRateMaxLength,
|
||||||
Long bytesWriteRate, Long bytesWriteRateMax, Long bytesWriteRateMaxLength,
|
Long bytesWriteRate, Long bytesWriteRateMax, Long bytesWriteRateMaxLength,
|
||||||
Long iopsReadRate, Long iopsReadRateMax, Long iopsReadRateMaxLength,
|
Long iopsReadRate, Long iopsReadRateMax, Long iopsReadRateMaxLength,
|
||||||
Long iopsWriteRate, Long iopsWriteRateMax, Long iopsWriteRateMaxLength,
|
Long iopsWriteRate, Long iopsWriteRateMax, Long iopsWriteRateMaxLength,
|
||||||
final Integer hypervisorSnapshotReserve, String cacheMode, final Long storagePolicyID,
|
final Integer hypervisorSnapshotReserve, String cacheMode, final Long storagePolicyID,
|
||||||
final boolean dynamicScalingEnabled, final Long diskOfferingId, final boolean diskOfferingStrictness,
|
final boolean dynamicScalingEnabled, final Long diskOfferingId, final boolean diskOfferingStrictness,
|
||||||
final boolean isCustomized, final boolean encryptRoot, final boolean purgeResources) {
|
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
|
// Filter child domains when both parent and child domains are present
|
||||||
List<Long> filteredDomainIds = filterChildSubDomains(domainIds);
|
List<Long> filteredDomainIds = filterChildSubDomains(domainIds);
|
||||||
@ -3495,6 +3507,12 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((serviceOffering = _serviceOfferingDao.persist(serviceOffering)) != null) {
|
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) {
|
for (Long domainId : filteredDomainIds) {
|
||||||
detailsVOList.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), ApiConstants.DOMAIN_ID, String.valueOf(domainId), false));
|
detailsVOList.add(new ServiceOfferingDetailsVO(serviceOffering.getId(), ApiConstants.DOMAIN_ID, String.valueOf(domainId), false));
|
||||||
}
|
}
|
||||||
@ -3518,6 +3536,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
|
@Override
|
||||||
public void validateExtraConfigInServiceOfferingDetail(String detailName) {
|
public void validateExtraConfigInServiceOfferingDetail(String detailName) {
|
||||||
if (!detailName.equals(DpdkHelper.DPDK_NUMA) && !detailName.equals(DpdkHelper.DPDK_HUGE_PAGES)
|
if (!detailName.equals(DpdkHelper.DPDK_NUMA) && !detailName.equals(DpdkHelper.DPDK_HUGE_PAGES)
|
||||||
|
|||||||
@ -640,6 +640,7 @@ import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
|
|||||||
import org.apache.cloudstack.userdata.UserDataManager;
|
import org.apache.cloudstack.userdata.UserDataManager;
|
||||||
import org.apache.cloudstack.utils.CloudStackVersion;
|
import org.apache.cloudstack.utils.CloudStackVersion;
|
||||||
import org.apache.cloudstack.utils.identity.ManagementServerNode;
|
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.codec.binary.Base64;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@ -4558,6 +4559,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
|||||||
capabilities.put(ApiConstants.INSTANCES_STATS_USER_ONLY, StatsCollector.vmStatsCollectUserVMOnly.value());
|
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_ENABLED, StatsCollector.vmDiskStatsRetentionEnabled.value());
|
||||||
capabilities.put(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_TIME, StatsCollector.vmDiskStatsMaxRetentionTime.value());
|
capabilities.put(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_TIME, StatsCollector.vmDiskStatsMaxRetentionTime.value());
|
||||||
|
capabilities.put(ApiConstants.INSTANCE_LEASE_ENABLED, VMLeaseManager.InstanceLeaseEnabled.value());
|
||||||
if (apiLimitEnabled) {
|
if (apiLimitEnabled) {
|
||||||
capabilities.put("apiLimitInterval", apiLimitInterval);
|
capabilities.put("apiLimitInterval", apiLimitInterval);
|
||||||
capabilities.put("apiLimitMax", apiLimitMax);
|
capabilities.put("apiLimitMax", apiLimitMax);
|
||||||
|
|||||||
@ -26,6 +26,9 @@ import java.io.IOException;
|
|||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -39,6 +42,7 @@ import java.util.Map;
|
|||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TimeZone;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@ -135,11 +139,13 @@ import org.apache.cloudstack.storage.template.VnfTemplateManager;
|
|||||||
import org.apache.cloudstack.userdata.UserDataManager;
|
import org.apache.cloudstack.userdata.UserDataManager;
|
||||||
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
|
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
|
||||||
import org.apache.cloudstack.utils.security.ParserUtils;
|
import org.apache.cloudstack.utils.security.ParserUtils;
|
||||||
|
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||||
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||||
import org.apache.cloudstack.vm.UnmanagedVMsManager;
|
import org.apache.cloudstack.vm.UnmanagedVMsManager;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.apache.commons.collections.MapUtils;
|
import org.apache.commons.collections.MapUtils;
|
||||||
import org.apache.commons.lang.math.NumberUtils;
|
import org.apache.commons.lang.math.NumberUtils;
|
||||||
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||||
@ -2869,6 +2875,13 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (MapUtils.isNotEmpty(details)) {
|
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")) {
|
if (details.containsKey("extraconfig")) {
|
||||||
throw new InvalidParameterValueException("'extraconfig' should not be included in details as key");
|
throw new InvalidParameterValueException("'extraconfig' should not be included in details as key");
|
||||||
}
|
}
|
||||||
@ -2916,6 +2929,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,
|
return updateVirtualMachine(id, displayName, group, ha, isDisplayVm,
|
||||||
cmd.getDeleteProtection(), osTypeId, userData,
|
cmd.getDeleteProtection(), osTypeId, userData,
|
||||||
userDataId, userDataDetails, isDynamicallyScalable, cmd.getHttpMethod(),
|
userDataId, userDataDetails, isDynamicallyScalable, cmd.getHttpMethod(),
|
||||||
@ -6169,6 +6187,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();
|
List<Long> networkIds = cmd.getNetworkIds();
|
||||||
LinkedHashMap<Integer, Long> userVmNetworkMap = getVmOvfNetworkMapping(zone, owner, template, cmd.getVmNetworkMap());
|
LinkedHashMap<Integer, Long> userVmNetworkMap = getVmOvfNetworkMapping(zone, owner, template, cmd.getVmNetworkMap());
|
||||||
if (MapUtils.isNotEmpty(userVmNetworkMap)) {
|
if (MapUtils.isNotEmpty(userVmNetworkMap)) {
|
||||||
@ -6267,9 +6290,117 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isLeaseFeatureEnabled) {
|
||||||
|
applyLeaseOnCreateInstance(vm, cmd.getLeaseDuration(), cmd.getLeaseExpiryAction(), svcOffering);
|
||||||
|
}
|
||||||
return vm;
|
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
|
* 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
|
* @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 id="reconcileCommandServiceImpl" class="org.apache.cloudstack.command.ReconcileCommandServiceImpl">
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<bean id="vmLeaseManager" class="org.apache.cloudstack.vm.lease.VMLeaseManagerImpl" >
|
||||||
|
<property name="asyncJobDispatcher" ref="ApiAsyncJobDispatcher" />
|
||||||
|
</bean>
|
||||||
|
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@ -80,6 +80,10 @@ import com.cloud.deploy.DataCenterDeployment;
|
|||||||
import com.cloud.deploy.DeployDestination;
|
import com.cloud.deploy.DeployDestination;
|
||||||
import com.cloud.deploy.DeploymentPlanner;
|
import com.cloud.deploy.DeploymentPlanner;
|
||||||
import com.cloud.deploy.DeploymentPlanningManager;
|
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.InsufficientAddressCapacityException;
|
||||||
import com.cloud.exception.InsufficientCapacityException;
|
import com.cloud.exception.InsufficientCapacityException;
|
||||||
import com.cloud.exception.InsufficientServerCapacityException;
|
import com.cloud.exception.InsufficientServerCapacityException;
|
||||||
@ -91,12 +95,28 @@ import com.cloud.host.Host;
|
|||||||
import com.cloud.host.HostVO;
|
import com.cloud.host.HostVO;
|
||||||
import com.cloud.host.dao.HostDao;
|
import com.cloud.host.dao.HostDao;
|
||||||
import com.cloud.hypervisor.Hypervisor;
|
import com.cloud.hypervisor.Hypervisor;
|
||||||
|
import com.cloud.network.Network;
|
||||||
import com.cloud.network.NetworkModel;
|
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.NetworkDao;
|
||||||
import com.cloud.network.dao.NetworkVO;
|
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.network.security.SecurityGroupVO;
|
||||||
import com.cloud.offering.DiskOffering;
|
import com.cloud.offering.DiskOffering;
|
||||||
|
import com.cloud.offering.NetworkOffering;
|
||||||
import com.cloud.offering.ServiceOffering;
|
import com.cloud.offering.ServiceOffering;
|
||||||
|
import com.cloud.offerings.NetworkOfferingVO;
|
||||||
|
import com.cloud.offerings.dao.NetworkOfferingDao;
|
||||||
import com.cloud.server.ManagementService;
|
import com.cloud.server.ManagementService;
|
||||||
import com.cloud.service.ServiceOfferingVO;
|
import com.cloud.service.ServiceOfferingVO;
|
||||||
import com.cloud.service.dao.ServiceOfferingDao;
|
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.dao.UserVmDetailsDao;
|
||||||
import com.cloud.vm.snapshot.VMSnapshotVO;
|
import com.cloud.vm.snapshot.VMSnapshotVO;
|
||||||
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
|
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
|
||||||
|
import org.apache.cloudstack.vm.lease.VMLeaseManager;
|
||||||
import org.mockito.MockedStatic;
|
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.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import com.cloud.domain.DomainVO;
|
import java.util.TimeZone;
|
||||||
import com.cloud.domain.dao.DomainDao;
|
import java.util.UUID;
|
||||||
import com.cloud.event.UsageEventUtils;
|
|
||||||
import com.cloud.network.Network;
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
import com.cloud.network.dao.FirewallRulesDao;
|
import static org.mockito.ArgumentMatchers.anyList;
|
||||||
import com.cloud.network.dao.IPAddressDao;
|
import static org.mockito.Mockito.verify;
|
||||||
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;
|
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class UserVmManagerImplTest {
|
public class UserVmManagerImplTest {
|
||||||
@ -3089,7 +3101,7 @@ public class UserVmManagerImplTest {
|
|||||||
configureDoNothingForMethodsThatWeDoNotWantToTest();
|
configureDoNothingForMethodsThatWeDoNotWantToTest();
|
||||||
|
|
||||||
userVmManagerImpl.executeStepsToChangeOwnershipOfVm(assignVmCmdMock, callerAccount, accountMock, accountMock, userVmVoMock, serviceOfferingVoMock, volumes,
|
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).resourceCountDecrement(Mockito.anyLong(), Mockito.any(), Mockito.any(), Mockito.any());
|
||||||
Mockito.verify(userVmManagerImpl).updateVmOwner(Mockito.any(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
|
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, srcHost.getId());
|
||||||
Mockito.verify(storageManager, times(1)).getStorageAccessGroups(null, null, null, destHost.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
358
test/integration/component/test_deploy_vm_lease.py
Normal file
358
test/integration/component/test_deploy_vm_lease.py
Normal file
@ -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_domain,
|
||||||
get_zone,
|
get_zone,
|
||||||
get_test_template,
|
get_test_template,
|
||||||
list_hosts)
|
list_hosts,
|
||||||
|
is_config_suitable)
|
||||||
from nose.plugins.attrib import attr
|
from nose.plugins.attrib import attr
|
||||||
|
|
||||||
import time
|
import time
|
||||||
@ -356,6 +357,164 @@ class TestCreateServiceOffering(cloudstackTestCase):
|
|||||||
)
|
)
|
||||||
return
|
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):
|
class TestServiceOfferings(cloudstackTestCase):
|
||||||
|
|
||||||
|
|||||||
@ -527,7 +527,8 @@ class VirtualMachine:
|
|||||||
customcpuspeed=None, custommemory=None, rootdisksize=None,
|
customcpuspeed=None, custommemory=None, rootdisksize=None,
|
||||||
rootdiskcontroller=None, vpcid=None, macaddress=None, datadisktemplate_diskoffering_list={},
|
rootdiskcontroller=None, vpcid=None, macaddress=None, datadisktemplate_diskoffering_list={},
|
||||||
properties=None, nicnetworklist=None, bootmode=None, boottype=None, dynamicscalingenabled=None,
|
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"""
|
"""Create the instance"""
|
||||||
|
|
||||||
cmd = deployVirtualMachine.deployVirtualMachineCmd()
|
cmd = deployVirtualMachine.deployVirtualMachineCmd()
|
||||||
@ -691,6 +692,12 @@ class VirtualMachine:
|
|||||||
if extraconfig:
|
if extraconfig:
|
||||||
cmd.extraconfig = extraconfig
|
cmd.extraconfig = extraconfig
|
||||||
|
|
||||||
|
if leaseduration:
|
||||||
|
cmd.leaseduration = leaseduration
|
||||||
|
|
||||||
|
if leaseexpiryaction:
|
||||||
|
cmd.leaseexpiryaction = leaseexpiryaction
|
||||||
|
|
||||||
virtual_machine = apiclient.deployVirtualMachine(cmd, method=method)
|
virtual_machine = apiclient.deployVirtualMachine(cmd, method=method)
|
||||||
|
|
||||||
if 'password' in list(virtual_machine.__dict__.keys()):
|
if 'password' in list(virtual_machine.__dict__.keys()):
|
||||||
|
|||||||
@ -1172,6 +1172,7 @@
|
|||||||
"label.instancename": "Internal name",
|
"label.instancename": "Internal name",
|
||||||
"label.instanceport": "Instance port",
|
"label.instanceport": "Instance port",
|
||||||
"label.instances": "Instances",
|
"label.instances": "Instances",
|
||||||
|
"label.leasedinstances": "Leased Instances",
|
||||||
"label.interface.route.table": "Interface Route Table",
|
"label.interface.route.table": "Interface Route Table",
|
||||||
"label.interface.router.table": "Interface Router Table",
|
"label.interface.router.table": "Interface Router Table",
|
||||||
"label.intermediate.certificate": "Intermediate certificate",
|
"label.intermediate.certificate": "Intermediate certificate",
|
||||||
@ -2672,6 +2673,15 @@
|
|||||||
"label.bucket.policy": "Bucket Policy",
|
"label.bucket.policy": "Bucket Policy",
|
||||||
"label.usersecretkey": "Secret Key",
|
"label.usersecretkey": "Secret Key",
|
||||||
"label.create.bucket": "Create Bucket",
|
"label.create.bucket": "Create Bucket",
|
||||||
|
"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.acquire.ip.failed": "Failed to acquire IP.",
|
||||||
"message.action.acquire.ip": "Please confirm that you want to acquire new 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.",
|
"message.action.cancel.maintenance": "Your host has been successfully canceled for maintenance. This process can take up to several minutes.",
|
||||||
|
|||||||
@ -151,6 +151,13 @@
|
|||||||
<div>{{ $toLocaleDate(dataResource[item]) }}</div>
|
<div>{{ $toLocaleDate(dataResource[item]) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</a-list-item>
|
</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">
|
<a-list-item v-else-if="item === 'details' && $route.meta.name === 'storagepool' && dataResource[item].rbd_default_data_pool">
|
||||||
<div>
|
<div>
|
||||||
<strong>{{ $t('label.data.pool') }}</strong>
|
<strong>{{ $t('label.data.pool') }}</strong>
|
||||||
@ -219,6 +226,9 @@ export default {
|
|||||||
items.push('startdate')
|
items.push('startdate')
|
||||||
items.push('enddate')
|
items.push('enddate')
|
||||||
}
|
}
|
||||||
|
if (this.$route.meta.name === 'vm') {
|
||||||
|
items.push('leaseexpirydate')
|
||||||
|
}
|
||||||
return items
|
return items
|
||||||
},
|
},
|
||||||
vnfAccessMethods () {
|
vnfAccessMethods () {
|
||||||
|
|||||||
@ -96,6 +96,9 @@
|
|||||||
<a-tag v-if="resource.archived" :color="this.$config.theme['@warning-color']">
|
<a-tag v-if="resource.archived" :color="this.$config.theme['@warning-color']">
|
||||||
{{ $t('label.archived') }}
|
{{ $t('label.archived') }}
|
||||||
</a-tag>
|
</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" >
|
<a-tooltip placement="right" >
|
||||||
<template #title>
|
<template #title>
|
||||||
<span>{{ $t('label.view.console') }}</span>
|
<span>{{ $t('label.view.console') }}</span>
|
||||||
@ -226,6 +229,27 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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" v-if="'memory' in resource">
|
||||||
<div class="resource-detail-item__label">{{ $t('label.memory') }}</div>
|
<div class="resource-detail-item__label">{{ $t('label.memory') }}</div>
|
||||||
<div class="resource-detail-item__details">
|
<div class="resource-detail-item__details">
|
||||||
|
|||||||
@ -65,7 +65,6 @@
|
|||||||
<span v-else :style="{ 'margin-right': record.ostypename ? '5px' : '0' }">
|
<span v-else :style="{ 'margin-right': record.ostypename ? '5px' : '0' }">
|
||||||
<os-logo v-if="record.ostypename" :osName="record.ostypename" size="xl" />
|
<os-logo v-if="record.ostypename" :osName="record.ostypename" size="xl" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="record.hasannotations">
|
<span v-if="record.hasannotations">
|
||||||
<span v-if="record.id">
|
<span v-if="record.id">
|
||||||
<router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
|
<router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
|
||||||
@ -101,6 +100,20 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
</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>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'templatetype'">
|
<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)) {
|
if (!(store.getters.project && store.getters.project.id)) {
|
||||||
filters.unshift('self')
|
filters.unshift('self')
|
||||||
}
|
}
|
||||||
|
if (store.getters.features.instanceleaseenabled) {
|
||||||
|
filters.push('leased')
|
||||||
|
}
|
||||||
return filters
|
return filters
|
||||||
},
|
},
|
||||||
columns: () => {
|
columns: () => {
|
||||||
@ -83,7 +86,7 @@ export default {
|
|||||||
var fields = ['name', 'displayname', 'id', 'state', 'ipaddress', 'ip6address', 'templatename', 'ostypename',
|
var fields = ['name', 'displayname', 'id', 'state', 'ipaddress', 'ip6address', 'templatename', 'ostypename',
|
||||||
'serviceofferingname', 'isdynamicallyscalable', 'haenable', 'hypervisor', 'arch', 'boottype', 'bootmode', 'account',
|
'serviceofferingname', 'isdynamicallyscalable', 'haenable', 'hypervisor', 'arch', 'boottype', 'bootmode', 'account',
|
||||||
'domain', 'zonename', 'userdataid', 'userdataname', 'userdataparams', 'userdatadetails', 'userdatapolicy',
|
'domain', 'zonename', 'userdataid', 'userdataname', 'userdataparams', 'userdatadetails', 'userdatapolicy',
|
||||||
'hostcontrolstate', 'deleteprotection']
|
'hostcontrolstate', 'deleteprotection', 'leaseexpirydate', 'leaseexpiryaction']
|
||||||
const listZoneHaveSGEnabled = store.getters.zones.filter(zone => zone.securitygroupsenabled === true)
|
const listZoneHaveSGEnabled = store.getters.zones.filter(zone => zone.securitygroupsenabled === true)
|
||||||
if (!listZoneHaveSGEnabled || listZoneHaveSGEnabled.length === 0) {
|
if (!listZoneHaveSGEnabled || listZoneHaveSGEnabled.length === 0) {
|
||||||
return fields
|
return fields
|
||||||
|
|||||||
@ -40,7 +40,7 @@ export default {
|
|||||||
filters: ['active', 'inactive'],
|
filters: ['active', 'inactive'],
|
||||||
columns: ['name', 'displaytext', 'state', 'cpunumber', 'cpuspeed', 'memory', 'domain', 'zone', 'order'],
|
columns: ['name', 'displaytext', 'state', 'cpunumber', 'cpuspeed', 'memory', 'domain', 'zone', 'order'],
|
||||||
details: () => {
|
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 &&
|
if (store.getters.apis.createServiceOffering &&
|
||||||
store.getters.apis.createServiceOffering.params.filter(x => x.name === 'storagepolicy').length > 0) {
|
store.getters.apis.createServiceOffering.params.filter(x => x.name === 'storagepolicy').length > 0) {
|
||||||
fields.splice(6, 0, 'vspherestoragepolicy')
|
fields.splice(6, 0, 'vspherestoragepolicy')
|
||||||
|
|||||||
@ -1724,6 +1724,7 @@ export default {
|
|||||||
delete query.domainid
|
delete query.domainid
|
||||||
delete query.state
|
delete query.state
|
||||||
delete query.annotationfilter
|
delete query.annotationfilter
|
||||||
|
delete query.leased
|
||||||
if (this.$route.name === 'template') {
|
if (this.$route.name === 'template') {
|
||||||
query.templatefilter = filter
|
query.templatefilter = filter
|
||||||
} else if (this.$route.name === 'iso') {
|
} else if (this.$route.name === 'iso') {
|
||||||
@ -1773,6 +1774,8 @@ export default {
|
|||||||
query.domainid = this.$store.getters.userInfo.domainid
|
query.domainid = this.$store.getters.userInfo.domainid
|
||||||
} else if (['running', 'stopped'].includes(filter)) {
|
} else if (['running', 'stopped'].includes(filter)) {
|
||||||
query.state = filter
|
query.state = filter
|
||||||
|
} else if (filter === 'leased') {
|
||||||
|
query.leased = true
|
||||||
}
|
}
|
||||||
} else if (this.$route.name === 'comment') {
|
} else if (this.$route.name === 'comment') {
|
||||||
query.annotationfilter = filter
|
query.annotationfilter = filter
|
||||||
|
|||||||
@ -603,6 +603,34 @@
|
|||||||
@change="val => { dynamicscalingenabled = val }"/>
|
@change="val => { dynamicscalingenabled = val }"/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</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-form-item :label="$t('label.userdata')">
|
||||||
<a-card>
|
<a-card>
|
||||||
<div v-if="this.template && this.template.userdataid">
|
<div v-if="this.template && this.template.userdataid">
|
||||||
@ -1101,6 +1129,17 @@ export default {
|
|||||||
description: 'ARM 64 bits (aarch64)'
|
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) {
|
if (this.sshKeyPairs && this.sshKeyPairs.length > 0) {
|
||||||
this.vm.keypairs = this.sshKeyPairs
|
this.vm.keypairs = this.sshKeyPairs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.leaseduration < 1) {
|
||||||
|
this.vm.leaseduration = undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1618,7 +1661,8 @@ export default {
|
|||||||
this.form = reactive({})
|
this.form = reactive({})
|
||||||
this.rules = reactive({
|
this.rules = reactive({
|
||||||
zoneid: [{ required: true, message: `${this.$t('message.error.select')}` }],
|
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) {
|
if (this.zoneSelected) {
|
||||||
@ -2193,6 +2237,12 @@ export default {
|
|||||||
if (values.group) {
|
if (values.group) {
|
||||||
deployVmData.group = values.group
|
deployVmData.group = values.group
|
||||||
}
|
}
|
||||||
|
if (values.leaseduration) {
|
||||||
|
deployVmData.leaseduration = values.leaseduration
|
||||||
|
}
|
||||||
|
if (values.leaseexpiryaction) {
|
||||||
|
deployVmData.leaseexpiryaction = values.leaseexpiryaction
|
||||||
|
}
|
||||||
// step 8: enter setup
|
// step 8: enter setup
|
||||||
if ('properties' in values) {
|
if ('properties' in values) {
|
||||||
const keys = Object.keys(values.properties)
|
const keys = Object.keys(values.properties)
|
||||||
@ -2845,6 +2895,16 @@ export default {
|
|||||||
this.rootDiskSizeFixed = offering.rootdisksize
|
this.rootDiskSizeFixed = offering.rootdisksize
|
||||||
this.showRootDiskSizeChanger = false
|
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.form.rootdisksizeitem = this.showRootDiskSizeChanger && this.rootDiskSizeFixed > 0
|
||||||
this.formModel = toRaw(this.form)
|
this.formModel = toRaw(this.form)
|
||||||
},
|
},
|
||||||
@ -2888,6 +2948,23 @@ export default {
|
|||||||
parent.$message.success(parent.$t('label.copied.clipboard'))
|
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 {
|
.vm-info-card {
|
||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
min-height: 250px;
|
min-height: 250px;
|
||||||
max-height: calc(100vh - 150px);
|
max-height: calc(100vh - 140px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -117,6 +117,34 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-switch v-model:checked="form.deleteprotection" />
|
<a-switch v-model:checked="form.deleteprotection" />
|
||||||
</a-form-item>
|
</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">
|
<div :span="24" class="action-button">
|
||||||
<a-button :loading="loading" @click="onCloseAction">{{ $t('label.cancel') }}</a-button>
|
<a-button :loading="loading" @click="onCloseAction">{{ $t('label.cancel') }}</a-button>
|
||||||
@ -165,6 +193,15 @@ export default {
|
|||||||
groups: {
|
groups: {
|
||||||
loading: false,
|
loading: false,
|
||||||
opts: []
|
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,
|
deleteprotection: this.resource.deleteprotection,
|
||||||
group: this.resource.group,
|
group: this.resource.group,
|
||||||
userdata: '',
|
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 () {
|
fetchData () {
|
||||||
this.fetchZoneDetails()
|
this.fetchZoneDetails()
|
||||||
@ -327,7 +369,6 @@ export default {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSubmit () {
|
handleSubmit () {
|
||||||
this.formRef.value.validate().then(() => {
|
this.formRef.value.validate().then(() => {
|
||||||
const values = toRaw(this.form)
|
const values = toRaw(this.form)
|
||||||
@ -354,6 +395,12 @@ export default {
|
|||||||
if (values.userdata && values.userdata.length > 0) {
|
if (values.userdata && values.userdata.length > 0) {
|
||||||
params.userdata = this.$toBase64AndURIEncoded(values.userdata)
|
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
|
this.loading = true
|
||||||
|
|
||||||
api('updateVirtualMachine', {}, 'POST', params).then(json => {
|
api('updateVirtualMachine', {}, 'POST', params).then(json => {
|
||||||
@ -372,6 +419,21 @@ export default {
|
|||||||
},
|
},
|
||||||
onCloseAction () {
|
onCloseAction () {
|
||||||
this.$emit('close-action')
|
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 === 'cpu'"><appstore-outlined /> {{ $t('label.cpu') }}</template>
|
||||||
<template v-if="column.key === 'ram'"><bulb-outlined /> {{ $t('label.memory') }}</template>
|
<template v-if="column.key === 'ram'"><bulb-outlined /> {{ $t('label.memory') }}</template>
|
||||||
</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>
|
</a-table>
|
||||||
|
|
||||||
<div style="display: block; text-align: right;">
|
<div style="display: block; text-align: right;">
|
||||||
@ -119,7 +136,8 @@ export default {
|
|||||||
key: 'name',
|
key: 'name',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
title: this.$t('label.serviceofferingid'),
|
title: this.$t('label.serviceofferingid'),
|
||||||
width: '40%'
|
width: '40%',
|
||||||
|
slots: { customRender: 'displayText' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'cpu',
|
key: 'cpu',
|
||||||
@ -191,7 +209,8 @@ export default {
|
|||||||
name: item.name,
|
name: item.name,
|
||||||
cpu: cpuNumberValue.length > 0 ? `${cpuNumberValue} CPU x ${cpuSpeedValue} Ghz` : '',
|
cpu: cpuNumberValue.length > 0 ? `${cpuNumberValue} CPU x ${cpuSpeedValue} Ghz` : '',
|
||||||
ram: ramValue.length > 0 ? `${ramValue} MB` : '',
|
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)
|
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>
|
</a-statistic>
|
||||||
</router-link>
|
</router-link>
|
||||||
</a-col>
|
</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">
|
<a-col :span="12" v-if="'listKubernetesClusters' in $store.getters.apis">
|
||||||
<router-link :to="{ path: '/kubernetes' }">
|
<router-link :to="{ path: '/kubernetes' }">
|
||||||
<a-statistic
|
<a-statistic
|
||||||
@ -393,8 +405,10 @@ export default {
|
|||||||
networks: 0,
|
networks: 0,
|
||||||
vpcs: 0,
|
vpcs: 0,
|
||||||
ips: 0,
|
ips: 0,
|
||||||
templates: 0
|
templates: 0,
|
||||||
}
|
leasedinstances: 0
|
||||||
|
},
|
||||||
|
isLeaseFeatureEnabled: this.$store.getters.features.instanceleaseenabled
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -556,6 +570,15 @@ export default {
|
|||||||
this.loading = false
|
this.loading = false
|
||||||
this.data.stopped = json?.listvirtualmachinesresponse?.count
|
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 () {
|
listEvents () {
|
||||||
if (!('listEvents' in this.$store.getters.apis)) {
|
if (!('listEvents' in this.$store.getters.apis)) {
|
||||||
|
|||||||
@ -349,6 +349,34 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-switch v-model:checked="form.purgeresources"/>
|
<a-switch v-model:checked="form.purgeresources"/>
|
||||||
</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" 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">
|
<a-form-item name="computeonly" ref="computeonly">
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.computeonly.offering')" :tooltip="$t('label.computeonly.offering.tooltip')"/>
|
<tooltip-label :title="$t('label.computeonly.offering')" :tooltip="$t('label.computeonly.offering.tooltip')"/>
|
||||||
@ -695,7 +723,14 @@ export default {
|
|||||||
diskOfferings: [],
|
diskOfferings: [],
|
||||||
selectedDiskOfferingId: '',
|
selectedDiskOfferingId: '',
|
||||||
qosType: '',
|
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 () {
|
beforeCreate () {
|
||||||
@ -734,7 +769,9 @@ export default {
|
|||||||
iscustomizeddiskiops: this.isCustomizedDiskIops,
|
iscustomizeddiskiops: this.isCustomizedDiskIops,
|
||||||
diskofferingid: this.selectedDiskOfferingId,
|
diskofferingid: this.selectedDiskOfferingId,
|
||||||
diskofferingstrictness: this.diskofferingstrictness,
|
diskofferingstrictness: this.diskofferingstrictness,
|
||||||
encryptdisk: this.encryptdisk
|
encryptdisk: this.encryptdisk,
|
||||||
|
leaseduration: this.leaseduration,
|
||||||
|
leaseexpiryaction: this.leaseexpiryaction
|
||||||
})
|
})
|
||||||
this.rules = reactive({
|
this.rules = reactive({
|
||||||
name: [{ required: true, message: this.$t('message.error.required.input') }],
|
name: [{ required: true, message: this.$t('message.error.required.input') }],
|
||||||
@ -785,7 +822,8 @@ export default {
|
|||||||
}
|
}
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
}]
|
}],
|
||||||
|
leaseduration: [this.naturalNumberRule]
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
fetchData () {
|
fetchData () {
|
||||||
@ -965,7 +1003,9 @@ export default {
|
|||||||
dynamicscalingenabled: values.dynamicscalingenabled,
|
dynamicscalingenabled: values.dynamicscalingenabled,
|
||||||
diskofferingstrictness: values.diskofferingstrictness,
|
diskofferingstrictness: values.diskofferingstrictness,
|
||||||
encryptroot: values.encryptdisk,
|
encryptroot: values.encryptdisk,
|
||||||
purgeresources: values.purgeresources
|
purgeresources: values.purgeresources,
|
||||||
|
leaseduration: values.leaseduration,
|
||||||
|
leaseexpiryaction: values.leaseexpiryaction
|
||||||
}
|
}
|
||||||
if (values.diskofferingid) {
|
if (values.diskofferingid) {
|
||||||
params.diskofferingid = values.diskofferingid
|
params.diskofferingid = values.diskofferingid
|
||||||
@ -1061,6 +1101,15 @@ export default {
|
|||||||
if ('systemvmtype' in values && values.systemvmtype !== undefined) {
|
if ('systemvmtype' in values && values.systemvmtype !== undefined) {
|
||||||
params.systemvmtype = values.systemvmtype
|
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) {
|
if (values.ispublic !== true) {
|
||||||
var domainIndexes = values.domainid
|
var domainIndexes = values.domainid
|
||||||
var domainId = null
|
var domainId = null
|
||||||
@ -1112,6 +1161,17 @@ export default {
|
|||||||
return Promise.reject(this.$t('message.error.number'))
|
return Promise.reject(this.$t('message.error.number'))
|
||||||
}
|
}
|
||||||
return Promise.resolve()
|
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…
x
Reference in New Issue
Block a user