diff --git a/api/src/main/java/com/cloud/agent/manager/allocator/HostAllocator.java b/api/src/main/java/com/cloud/agent/manager/allocator/HostAllocator.java index 604720aaa29..5d028d31d5b 100644 --- a/api/src/main/java/com/cloud/agent/manager/allocator/HostAllocator.java +++ b/api/src/main/java/com/cloud/agent/manager/allocator/HostAllocator.java @@ -22,19 +22,11 @@ import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.host.Host; import com.cloud.host.Host.Type; -import com.cloud.offering.ServiceOffering; import com.cloud.utils.component.Adapter; -import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; public interface HostAllocator extends Adapter { - /** - * @param UserVm vm - * @param ServiceOffering offering - **/ - boolean isVirtualMachineUpgradable(final VirtualMachine vm, final ServiceOffering offering); - /** * Determines which physical hosts are suitable to * allocate the guest virtual machines on @@ -49,31 +41,6 @@ public interface HostAllocator extends Adapter { public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo); - /** - * Determines which physical hosts are suitable to allocate the guest - * virtual machines on - * - * Allocators must set any other hosts not considered for allocation in the - * ExcludeList avoid. Thus the avoid set and the list of hosts suitable, - * together must cover the entire host set in the cluster. - * - * @param VirtualMachineProfile - * vmProfile - * @param DeploymentPlan - * plan - * @param GuestType - * type - * @param ExcludeList - * avoid - * @param int returnUpTo (use -1 to return all possible hosts) - * @param boolean considerReservedCapacity (default should be true, set to - * false if host capacity calculation should not look at reserved - * capacity) - * @return List List of hosts that are suitable for VM allocation - **/ - - public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo, boolean considerReservedCapacity); - /** * Determines which physical hosts are suitable to allocate the guest * virtual machines on diff --git a/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java b/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java index 8f7e773070f..22d796d4a77 100644 --- a/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java +++ b/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java @@ -70,7 +70,7 @@ public interface DeploymentPlanner extends Adapter { boolean canHandle(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid); public enum AllocationAlgorithm { - random, firstfit, userdispersing; + random, firstfit, userdispersing, firstfitleastconsumed; } public enum PlannerResourceUsage { diff --git a/api/src/main/java/com/cloud/network/Network.java b/api/src/main/java/com/cloud/network/Network.java index 0846306f70f..e41eb880ffd 100644 --- a/api/src/main/java/com/cloud/network/Network.java +++ b/api/src/main/java/com/cloud/network/Network.java @@ -510,4 +510,6 @@ public interface Network extends ControlledEntity, StateObject, I Integer getPrivateMtu(); Integer getNetworkCidrSize(); + + boolean getKeepMacAddressOnPublicNic(); } diff --git a/api/src/main/java/com/cloud/network/NetworkProfile.java b/api/src/main/java/com/cloud/network/NetworkProfile.java index 2e8efb48930..d690344a0e3 100644 --- a/api/src/main/java/com/cloud/network/NetworkProfile.java +++ b/api/src/main/java/com/cloud/network/NetworkProfile.java @@ -385,6 +385,11 @@ public class NetworkProfile implements Network { return networkCidrSize; } + @Override + public boolean getKeepMacAddressOnPublicNic() { + return true; + } + @Override public String toString() { return String.format("NetworkProfile %s", diff --git a/api/src/main/java/com/cloud/network/vpc/Vpc.java b/api/src/main/java/com/cloud/network/vpc/Vpc.java index b94089d2d43..a0686e2bf7d 100644 --- a/api/src/main/java/com/cloud/network/vpc/Vpc.java +++ b/api/src/main/java/com/cloud/network/vpc/Vpc.java @@ -107,4 +107,6 @@ public interface Vpc extends ControlledEntity, Identity, InternalIdentity { String getIp6Dns2(); boolean useRouterIpAsResolver(); + + boolean getKeepMacAddressOnPublicNic(); } diff --git a/api/src/main/java/com/cloud/network/vpc/VpcService.java b/api/src/main/java/com/cloud/network/vpc/VpcService.java index c1546609d2b..3d0ba43263f 100644 --- a/api/src/main/java/com/cloud/network/vpc/VpcService.java +++ b/api/src/main/java/com/cloud/network/vpc/VpcService.java @@ -58,7 +58,7 @@ public interface VpcService { */ Vpc createVpc(long zoneId, long vpcOffId, long vpcOwnerId, String vpcName, String displayText, String cidr, String networkDomain, String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Boolean displayVpc, Integer publicMtu, Integer cidrSize, - Long asNumber, List bgpPeerIds, Boolean useVrIpResolver) throws ResourceAllocationException; + Long asNumber, List bgpPeerIds, Boolean useVrIpResolver, boolean keepMacAddressOnPublicNic) throws ResourceAllocationException; /** * Persists VPC record in the database @@ -104,7 +104,7 @@ public interface VpcService { * @throws ResourceUnavailableException if during restart some resources may not be available * @throws InsufficientCapacityException if for instance no address space, compute or storage is sufficiently available */ - Vpc updateVpc(long vpcId, String vpcName, String displayText, String customId, Boolean displayVpc, Integer mtu, String sourceNatIp) throws ResourceUnavailableException, InsufficientCapacityException; + Vpc updateVpc(long vpcId, String vpcName, String displayText, String customId, Boolean displayVpc, Integer mtu, String sourceNatIp, Boolean keepMacAddressOnPublicNic) throws ResourceUnavailableException, InsufficientCapacityException; /** * Lists VPC(s) based on the parameters passed to the API call diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 19af0831c83..f059c0e80a5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1361,6 +1361,13 @@ public class ApiConstants { public static final String OBJECT_STORAGE_LIMIT = "objectstoragelimit"; public static final String OBJECT_STORAGE_TOTAL = "objectstoragetotal"; + public static final String KEEP_MAC_ADDRESS_ON_PUBLIC_NIC = "keepmacaddressonpublicnic"; + + public static final String PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC = + "Indicates whether to use the same MAC address for the public NIC of VRs on the same network. If \"true\", when creating redundant routers or recreating" + + " a VR, CloudStack will use the same MAC address for the public NIC of all VRs. Otherwise, if \"false\", new public NICs will always have " + + " a new MAC address."; + public static final String PARAMETER_DESCRIPTION_ACTIVATION_RULE = "Quota tariff's activation rule. It can receive a JS script that results in either " + "a boolean or a numeric value: if it results in a boolean value, the tariff value will be applied according to the result; if it results in a numeric value, the " + "numeric value will be applied; if the result is neither a boolean nor a numeric value, the tariff will not be applied. If the rule is not informed, the tariff " + diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/FindHostsForMigrationCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/FindHostsForMigrationCmd.java index abca619f82a..4d6ef741961 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/FindHostsForMigrationCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/FindHostsForMigrationCmd.java @@ -78,7 +78,7 @@ public class FindHostsForMigrationCmd extends BaseListCmd { for (Host host : result.first()) { HostForMigrationResponse hostResponse = _responseGenerator.createHostForMigrationResponse(host); Boolean suitableForMigration = false; - if (hostsWithCapacity.contains(host)) { + if (hostsWithCapacity != null && hostsWithCapacity.contains(host)) { suitableForMigration = true; } hostResponse.setSuitableForMigration(suitableForMigration); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java index e202dfad77b..8f5e6c784d6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java @@ -252,7 +252,7 @@ public class ListHostsCmd extends BaseListCmd { for (Host host : result.first()) { HostResponse hostResponse = _responseGenerator.createHostResponse(host, getDetails()); Boolean suitableForMigration = false; - if (hostsWithCapacity.contains(host)) { + if (hostsWithCapacity != null && hostsWithCapacity.contains(host)) { suitableForMigration = true; } hostResponse.setSuitableForMigration(suitableForMigration); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java index cbf6df081b3..ee5b8568e83 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java @@ -199,6 +199,11 @@ public class CreateNetworkCmd extends BaseCmd implements UserCmd { @Parameter(name=ApiConstants.AS_NUMBER, type=CommandType.LONG, since = "4.20.0", description="the AS Number of the network") private Long asNumber; + @Parameter(name = ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + type = CommandType.BOOLEAN, since = "4.23.0", authorized = {RoleType.Admin}) + private Boolean keepMacAddressOnPublicNic; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -286,6 +291,10 @@ public class CreateNetworkCmd extends BaseCmd implements UserCmd { return sourceNatIP; } + public Boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + @Override public boolean isDisplay() { if(displayNetwork == null) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmd.java index 2e638f1e2f7..7b6841d6097 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmd.java @@ -105,6 +105,11 @@ public class UpdateNetworkCmd extends BaseAsyncCustomIdCmd implements UserCmd { @Parameter(name = ApiConstants.SOURCE_NAT_IP, type = CommandType.STRING, description = "IPV4 address to be assigned to the public interface of the network router. This address must already be acquired for this network", since = "4.19") private String sourceNatIP; + @Parameter(name = ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + type = CommandType.BOOLEAN, since = "4.23.0", authorized = {RoleType.Admin}) + private Boolean keepMacAddressOnPublicNic; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -186,6 +191,10 @@ public class UpdateNetworkCmd extends BaseAsyncCustomIdCmd implements UserCmd { return sourceNatIP; } + public Boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmd.java index 2adbbd66408..86309d7f28a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmd.java @@ -130,6 +130,11 @@ public class CreateVPCCmd extends BaseAsyncCreateCmd implements UserCmd { description="(optional) for NSX based VPCs: when set to true, use the VR IP as nameserver, otherwise use DNS1 and DNS2") private Boolean useVrIpResolver; + @Parameter(name = ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + type = CommandType.BOOLEAN, since = "4.23.0", authorized = {RoleType.Admin}) + private boolean keepMacAddressOnPublicNic = true; + // /////////////////////////////////////////////////// // ///////////////// Accessors /////////////////////// // /////////////////////////////////////////////////// @@ -214,6 +219,10 @@ public class CreateVPCCmd extends BaseAsyncCreateCmd implements UserCmd { return BooleanUtils.toBoolean(useVrIpResolver); } + public boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmd.java index f2327c9073f..6cfdb895977 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmd.java @@ -69,6 +69,11 @@ public class UpdateVPCCmd extends BaseAsyncCustomIdCmd implements UserCmd { since = "4.19") private String sourceNatIP; + @Parameter(name = ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + type = CommandType.BOOLEAN, since = "4.23.0", authorized = {RoleType.Admin}) + private Boolean keepMacAddressOnPublicNic; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -97,6 +102,10 @@ public class UpdateVPCCmd extends BaseAsyncCustomIdCmd implements UserCmd { return sourceNatIP; } + public Boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java index 7c4b733a80f..3a3663af255 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java @@ -331,6 +331,10 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement @Param(description = "The BGP peers for the network", since = "4.20.0") private Set bgpPeers; + @SerializedName(ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC) + @Param(description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, since = "4.23.0") + private Boolean keepMacAddressOnPublicNic; + public NetworkResponse() {} public Boolean getDisplayNetwork() { @@ -702,4 +706,8 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement public void setIpv6Dns2(String ipv6Dns2) { this.ipv6Dns2 = ipv6Dns2; } + + public void setKeepMacAddressOnPublicNic(Boolean keepMacAddressOnPublicNic) { + this.keepMacAddressOnPublicNic = keepMacAddressOnPublicNic; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java index acfabb11350..34d50d5b9f9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java @@ -185,6 +185,10 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll @Param(description = "The BGP peers for the VPC", since = "4.20.0") private Set bgpPeers; + @SerializedName(ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC) + @Param(description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, since = "4.23.0") + private Boolean keepMacAddressOnPublicNic; + public void setId(final String id) { this.id = id; } @@ -366,4 +370,8 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll } this.bgpPeers.add(bgpPeer); } + + public void setKeepMacAddressOnPublicNic(Boolean keepMacAddressOnPublicNic) { + this.keepMacAddressOnPublicNic = keepMacAddressOnPublicNic; + } } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmdTest.java index acb2dc68597..9d56cffd671 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmdTest.java @@ -93,7 +93,7 @@ public class UpdateVPCCmdTest extends TestCase { responseGenerator = Mockito.mock(ResponseGenerator.class); cmd._responseGenerator = responseGenerator; Mockito.verify(_vpcService, Mockito.times(0)).updateVpc(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), - Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt(), Mockito.anyString()); + Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt(), Mockito.anyString(), Mockito.anyBoolean()); } } diff --git a/client/pom.xml b/client/pom.xml index 4cbda81db25..23a83f37435 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -377,11 +377,6 @@ cloud-plugin-explicit-dedication ${project.version} - - org.apache.cloudstack - cloud-plugin-host-allocator-random - ${project.version} - org.apache.cloudstack cloud-plugin-outofbandmanagement-driver-ipmitool diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java index 57dc1b7bf72..e871bd8672f 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -181,15 +181,6 @@ public interface VirtualMachineManager extends Manager { void advanceReboot(String vmUuid, Map params) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, OperationTimedoutException; - /** - * Check to see if a virtual machine can be upgraded to the given service offering - * - * @param vm - * @param offering - * @return true if the host can handle the upgrade, false otherwise - */ - boolean isVirtualMachineUpgradable(final VirtualMachine vm, final ServiceOffering offering); - VirtualMachine findById(long vmId); void storageMigration(String vmUuid, Map volumeToPool); diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java index b7b548fb940..030c1277efe 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java @@ -220,6 +220,11 @@ public interface NetworkOrchestrationService { Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException; + Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, boolean bypassVlanOverlapCheck, String networkDomain, Account owner, + Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String ip6Gateway, String ip6Cidr, + Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, + String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize, boolean keepMacAddressOnPublicNic) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException; + UserDataServiceProvider getPasswordResetProvider(Network network); UserDataServiceProvider getSSHKeyResetProvider(Network network); diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index 2958ffffe0a..b96baf70b21 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -4023,19 +4023,6 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } } - @Override - public boolean isVirtualMachineUpgradable(final VirtualMachine vm, final ServiceOffering offering) { - boolean isMachineUpgradable = true; - for (final HostAllocator allocator : hostAllocators) { - isMachineUpgradable = allocator.isVirtualMachineUpgradable(vm, offering); - if (!isMachineUpgradable) { - break; - } - } - - return isMachineUpgradable; - } - @Override public void reboot(final String vmUuid, final Map params) throws InsufficientCapacityException, ResourceUnavailableException { try { @@ -4475,11 +4462,6 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac throw new InvalidParameterValueException("isSystem property is different for current service offering and new service offering"); } - if (!isVirtualMachineUpgradable(vmInstance, newServiceOffering)) { - throw new InvalidParameterValueException("Unable to upgrade virtual machine, not enough resources available " + "for an offering of " + - newServiceOffering.getCpu() + " cpu(s) at " + newServiceOffering.getSpeed() + " Mhz, and " + newServiceOffering.getRamSize() + " MB of memory"); - } - final List currentTags = StringUtils.csvTagsToList(currentDiskOffering.getTags()); final List newTags = StringUtils.csvTagsToList(newDiskOffering.getTags()); if (VolumeApiServiceImpl.MatchStoragePoolTagsWithDiskOffering.valueIn(vmInstance.getDataCenterId())) { diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index 7d455e7d6dc..4262ee701aa 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -863,6 +863,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra vpcId, offering.isRedundantRouter(), predefined.getExternalId()); vo.setDisplayNetwork(isDisplayNetworkEnabled == null || isDisplayNetworkEnabled); vo.setStrechedL2Network(offering.isSupportingStrechedL2()); + vo.setKeepMacAddressOnPublicNic(predefined.getKeepMacAddressOnPublicNic()); return vo; } @@ -2724,7 +2725,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra return createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, bypassVlanOverlapCheck, null, owner, null, pNtwk, pNtwk.getDataCenterId(), ACLType.Account, null, vpcId, null, null, true, null, null, null, true, null, null, - null, null, null, null, null, null); + null, null, null, null, null, null, true); } @Override @@ -2735,10 +2736,25 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra final Boolean isDisplayNetworkEnabled, final String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { + return createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, bypassVlanOverlapCheck, + networkDomain, owner, domainId, pNtwk, zoneId, aclType, subdomainAccess, vpcId, ip6Gateway, ip6Cidr, + isDisplayNetworkEnabled, isolatedPvlan, isolatedPvlanType, externalId, false, routerIp, routerIpv6, + ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize, true); + } + + @Override + @DB + public Network createGuestNetwork(final long networkOfferingId, final String name, final String displayText, final String gateway, final String cidr, String vlanId, + boolean bypassVlanOverlapCheck, String networkDomain, final Account owner, final Long domainId, final PhysicalNetwork pNtwk, + final long zoneId, final ACLType aclType, Boolean subdomainAccess, final Long vpcId, final String ip6Gateway, final String ip6Cidr, + final Boolean isDisplayNetworkEnabled, final String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, + String routerIp, String routerIpv6, String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, + Pair vrIfaceMTUs, Integer networkCidrSize, boolean keepMacAddressOnPublicNic) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { // create Isolated/Shared/L2 network return createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, bypassVlanOverlapCheck, networkDomain, owner, domainId, pNtwk, zoneId, aclType, subdomainAccess, vpcId, ip6Gateway, ip6Cidr, - isDisplayNetworkEnabled, isolatedPvlan, isolatedPvlanType, externalId, false, routerIp, routerIpv6, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize); + isDisplayNetworkEnabled, isolatedPvlan, isolatedPvlanType, externalId, false, routerIp, routerIpv6, + ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize, keepMacAddressOnPublicNic); } @DB @@ -2747,7 +2763,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra final long zoneId, final ACLType aclType, Boolean subdomainAccess, final Long vpcId, final String ip6Gateway, final String ip6Cidr, final Boolean isDisplayNetworkEnabled, final String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, final Boolean isPrivateNetwork, String routerIp, String routerIpv6, final String ip4Dns1, final String ip4Dns2, - final String ip6Dns1, final String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { + final String ip6Dns1, final String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize, + boolean keepMacAddressOnPublicNic) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { final NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId); final DataCenterVO zone = _dcDao.findById(zoneId); @@ -3095,6 +3112,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra } } userNetwork.setNetworkCidrSize(networkCidrSize); + userNetwork.setKeepMacAddressOnPublicNic(keepMacAddressOnPublicNic); final List networks = setupNetwork(owner, ntwkOff, userNetwork, plan, name, displayText, true, domainId, aclType, subdomainAccessFinal, vpcId, isDisplayNetworkEnabled); Network network; diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java b/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java index 090b019334f..5f5b2affee0 100644 --- a/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java @@ -218,7 +218,7 @@ public interface HostDao extends GenericDao, StateDao listOrderedHostsHypervisorVersionsInDatacenter(long datacenterId, HypervisorType hypervisorType); - List findHostsWithTagRuleThatMatchComputeOferringTags(String computeOfferingTags); + List findHostsWithTagRuleThatMatchComputeOfferingTags(String computeOfferingTags); List findClustersThatMatchHostTagRule(String computeOfferingTags); diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java b/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java index 2d8fcca6cdb..99c9a979c3b 100644 --- a/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java @@ -1520,7 +1520,7 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao } } - public List findHostsWithTagRuleThatMatchComputeOferringTags(String computeOfferingTags) { + public List findHostsWithTagRuleThatMatchComputeOfferingTags(String computeOfferingTags) { List hostTagVOList = _hostTagsDao.findHostRuleTags(); List result = new ArrayList<>(); for (HostTagVO rule: hostTagVOList) { @@ -1534,7 +1534,7 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao public List findClustersThatMatchHostTagRule(String computeOfferingTags) { Set result = new HashSet<>(); - List hosts = findHostsWithTagRuleThatMatchComputeOferringTags(computeOfferingTags); + List hosts = findHostsWithTagRuleThatMatchComputeOfferingTags(computeOfferingTags); for (HostVO host: hosts) { result.add(host.getClusterId()); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java index 02abaacd854..f2572ba91c2 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java @@ -203,9 +203,13 @@ public class NetworkVO implements Network { @Column(name = "private_mtu") Integer privateMtu; + @Column(name = "keep_mac_address_on_public_nic") + private boolean keepMacAddressOnPublicNic = true; + @Transient Integer networkCidrSize; + public NetworkVO() { uuid = UUID.randomUUID().toString(); } @@ -773,4 +777,13 @@ public class NetworkVO implements Network { public void setNetworkCidrSize(Integer networkCidrSize) { this.networkCidrSize = networkCidrSize; } + + @Override + public boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + + public void setKeepMacAddressOnPublicNic(boolean keepMacAddressOnPublicNic) { + this.keepMacAddressOnPublicNic = keepMacAddressOnPublicNic; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/VpcVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/VpcVO.java index e942eadb8ff..742d3f2f82e 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcVO.java @@ -108,6 +108,9 @@ public class VpcVO implements Vpc { @Column(name = "use_router_ip_resolver") boolean useRouterIpResolver = false; + @Column(name = "keep_mac_address_on_public_nic") + private boolean keepMacAddressOnPublicNic = true; + @Transient boolean rollingRestart = false; @@ -321,4 +324,13 @@ public class VpcVO implements Vpc { public void setUseRouterIpResolver(boolean useRouterIpResolver) { this.useRouterIpResolver = useRouterIpResolver; } + + @Override + public boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + + public void setKeepMacAddressOnPublicNic(boolean keepMacAddressOnPublicNic) { + this.keepMacAddressOnPublicNic = keepMacAddressOnPublicNic; + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java index 13579d79cde..ae1e838649b 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -358,7 +358,8 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem IdsPowerStateSelectSearch.entity().getPowerHostId(), IdsPowerStateSelectSearch.entity().getPowerState(), IdsPowerStateSelectSearch.entity().getPowerStateUpdateCount(), - IdsPowerStateSelectSearch.entity().getPowerStateUpdateTime()); + IdsPowerStateSelectSearch.entity().getPowerStateUpdateTime(), + IdsPowerStateSelectSearch.entity().getState()); IdsPowerStateSelectSearch.done(); CountByOfferingId = createSearchBuilder(Integer.class); @@ -1105,10 +1106,14 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem private boolean isPowerStateInSyncWithInstanceState(final VirtualMachine.PowerState powerState, final long powerHostId, final VMInstanceVO instance) { State instanceState = instance.getState(); + if (instanceState == null) { + logger.warn("VM {} has null instance state during power state sync check, treating as out of sync", instance); + return false; + } if ((powerState == VirtualMachine.PowerState.PowerOff && instanceState == State.Running) || (powerState == VirtualMachine.PowerState.PowerOn && instanceState == State.Stopped)) { HostVO instanceHost = hostDao.findById(instance.getHostId()); - HostVO powerHost = powerHostId == instance.getHostId() ? instanceHost : hostDao.findById(powerHostId); + HostVO powerHost = instance.getHostId() != null && powerHostId == instance.getHostId() ? instanceHost : hostDao.findById(powerHostId); logger.debug("VM: {} on host: {} and power host : {} is in {} state, but power state is {}", instance, instanceHost, powerHost, instanceState, powerState); return false; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42200to42210-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-42200to42210-cleanup.sql index 54baf226ac4..2f104568c14 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42200to42210-cleanup.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42200to42210-cleanup.sql @@ -18,3 +18,9 @@ --; -- Schema upgrade cleanup from 4.22.0.0 to 4.22.1.0 --; + +-- Entries remaining on `cloud`.`resource_reservation` during the upgrade process are stale, so delete them. +-- This script was added to normalize volume/primary storage reservations that got stuck due to a bug on VM deployment, +-- but it is more interesting to introduce a smarter logic to clean these stale reservations in the future without the need +-- for upgrades (for instance, by having a heartbeat_time column for the reservations and automatically cleaning old entries). +DELETE FROM `cloud`.`resource_reservation`; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql index 6b2c80c99a6..dc6e0fa7f3a 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -128,6 +128,10 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` ( CONSTRAINT `fk_quota_tariff_usage__tariff_id` FOREIGN KEY (`tariff_id`) REFERENCES `cloud_usage`.`quota_tariff` (`id`), CONSTRAINT `fk_quota_tariff_usage__quota_usage_id` FOREIGN KEY (`quota_usage_id`) REFERENCES `cloud_usage`.`quota_usage` (`id`)); +-- Add the 'keep_mac_address_on_public_nic' column to the 'cloud.networks' and 'cloud.vpc' tables +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.networks', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1'); + -- Add management_server_details table to allow ManagementServer scope configs CREATE TABLE IF NOT EXISTS `management_server_details` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java index cd87052e878..23020292027 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java @@ -35,6 +35,7 @@ import com.cloud.network.vpc.VpcVO; import javax.inject.Inject; import com.cloud.storage.StoragePoolTagVO; +import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.acl.RoleVO; import org.apache.cloudstack.acl.dao.RoleDao; import org.apache.cloudstack.backup.BackupOfferingVO; @@ -783,10 +784,21 @@ public class PresetVariableHelper { value.setId(network.getUuid()); value.setName(network.getName()); value.setState(usageRecord.getState()); - + value.setResourceCounting(getPresetVariableValueNetworkResourceCounting(networkId)); value.setNetworkOffering(getPresetVariableValueNetworkOffering(network.getNetworkOfferingId())); } + protected ResourceCounting getPresetVariableValueNetworkResourceCounting(Long networkId) { + ResourceCounting resourceCounting = new ResourceCounting(); + List vmInstancesVO = vmInstanceDao.listNonRemovedVmsByTypeAndNetwork(networkId, VirtualMachine.Type.User); + int runningVms = (int) vmInstancesVO.stream().filter(vm -> vm.getState().equals(VirtualMachine.State.Running)).count(); + int stoppedVms = (int) vmInstancesVO.stream().filter(vm -> vm.getState().equals(VirtualMachine.State.Stopped)).count(); + + resourceCounting.setRunningVms(runningVms); + resourceCounting.setStoppedVms(stoppedVms); + return resourceCounting; + } + protected GenericPresetVariable getPresetVariableValueNetworkOffering(Long networkOfferingId) { NetworkOfferingVO networkOfferingVo = networkOfferingDao.findByIdIncludingRemoved(networkOfferingId); validateIfObjectIsNull(networkOfferingVo, networkOfferingId, "network offering"); diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/ResourceCounting.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/ResourceCounting.java new file mode 100644 index 00000000000..75049c3486a --- /dev/null +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/ResourceCounting.java @@ -0,0 +1,52 @@ +// 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.quota.activationrule.presetvariables; + + +import org.apache.cloudstack.quota.constant.QuotaTypes; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +public class ResourceCounting { + + @PresetVariableDefinition(description = "The number of running user instances.", supportedTypes = {QuotaTypes.NETWORK}) + private int runningVms; + @PresetVariableDefinition(description = "The number of stopped user instances.", supportedTypes = {QuotaTypes.NETWORK}) + private int stoppedVms; + + public int getRunningVms() { + return runningVms; + } + + public void setRunningVms(int runningVms) { + this.runningVms = runningVms; + } + + public int getStoppedVms() { + return stoppedVms; + } + + public void setStoppedVms(int stoppedVms) { + this.stoppedVms = stoppedVms; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); + } +} diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java index aff2c040e74..286fe5c60fd 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java @@ -84,6 +84,9 @@ public class Value extends GenericPresetVariable { @PresetVariableDefinition(description = "Backup offering of the backup.", supportedTypes = {QuotaTypes.BACKUP}) private BackupOffering backupOffering; + @PresetVariableDefinition(description = "The amount of resources of the usage type.") + private ResourceCounting resourceCounting; + @PresetVariableDefinition(description = "The hypervisor where the resource was deployed. Values can be: XenServer, KVM, VMware, Hyperv, BareMetal, Ovm, Ovm3 and LXC.", supportedTypes = {QuotaTypes.RUNNING_VM, QuotaTypes.ALLOCATED_VM, QuotaTypes.VM_SNAPSHOT, QuotaTypes.SNAPSHOT}) private String hypervisorType; @@ -262,6 +265,14 @@ public class Value extends GenericPresetVariable { this.state = state; } + public ResourceCounting getResourceCounting() { + return resourceCounting; + } + + public void setResourceCounting(ResourceCounting resourceCounting) { + this.resourceCounting = resourceCounting; + } + public GenericPresetVariable getNetworkOffering() { return networkOffering; } diff --git a/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java b/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java index fb093a45679..bcdbc3b46ce 100644 --- a/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java +++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java @@ -31,11 +31,13 @@ import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; import com.cloud.host.HostTagVO; import com.cloud.hypervisor.Hypervisor; +import com.cloud.network.Network; import com.cloud.network.dao.NetworkVO; import com.cloud.network.vpc.VpcOfferingVO; import com.cloud.network.vpc.VpcVO; import com.cloud.network.vpc.dao.VpcOfferingDao; import com.cloud.storage.StoragePoolTagVO; +import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.RoleVO; import org.apache.cloudstack.acl.dao.RoleDao; @@ -238,6 +240,8 @@ public class PresetVariableHelperTest { value.setVmSnapshotType(VMSnapshot.Type.Disk.toString()); value.setComputingResources(getComputingResourcesForTests()); value.setVolumeType(Volume.Type.DATADISK.toString()); + value.setState(Network.State.Implemented.toString()); + value.setResourceCounting(getResourceCountingForTests()); value.setNetworkOffering(getNetworkOfferingForTests()); value.setVpcOffering(getVpcOfferingForTests()); return value; @@ -276,6 +280,13 @@ public class PresetVariableHelperTest { return configuration; } + private ResourceCounting getResourceCountingForTests() { + ResourceCounting resourceCounting = new ResourceCounting(); + resourceCounting.setRunningVms(1); + resourceCounting.setStoppedVms(1); + return resourceCounting; + } + private List getHostTagsForTests() { return Arrays.asList(new HostTagVO(1, "tag1", false), new HostTagVO(1, "tag2", false)); } @@ -1343,8 +1354,8 @@ public class PresetVariableHelperTest { Mockito.doReturn(expected.getId()).when(networkVoMock).getUuid(); Mockito.doReturn(expected.getName()).when(networkVoMock).getName(); Mockito.doReturn(expected.getState()).when(usageVoMock).getState(); + Mockito.doReturn(expected.getResourceCounting()).when(presetVariableHelperSpy).getPresetVariableValueNetworkResourceCounting(Mockito.anyLong()); Mockito.doReturn(expected.getNetworkOffering()).when(presetVariableHelperSpy).getPresetVariableValueNetworkOffering(Mockito.anyLong()); - Mockito.doReturn(UsageTypes.NETWORK).when(usageVoMock).getUsageType(); Value result = new Value(); @@ -1352,7 +1363,27 @@ public class PresetVariableHelperTest { assertPresetVariableIdAndName(expected, result); Assert.assertEquals(expected.getState(), result.getState()); - Assert.assertEquals(expected.getNetworkOffering(), result.getNetworkOffering()); + Assert.assertEquals(expected.getResourceCounting(), result.getResourceCounting()); + } + + @Test + public void getPresetVariableValueNetworkResourceCountingTestSetValueAndReturnObject() { + VMInstanceVO vmInstanceVoMock1 = Mockito.spy(VMInstanceVO.class); + vmInstanceVoMock1.setState(VirtualMachine.State.Stopped); + + VMInstanceVO vmInstanceVoMock2 = Mockito.spy(VMInstanceVO.class); + vmInstanceVoMock2.setState(VirtualMachine.State.Running); + + Mockito.doReturn(List.of(vmInstanceVoMock1, vmInstanceVoMock2)).when(vmInstanceDaoMock).listNonRemovedVmsByTypeAndNetwork(Mockito.anyLong(), Mockito.any()); + + mockMethodValidateIfObjectIsNull(); + + ResourceCounting expected = getResourceCountingForTests(); + + ResourceCounting result = presetVariableHelperSpy.getPresetVariableValueNetworkResourceCounting(1L); + + Assert.assertEquals(expected.getRunningVms(), result.getRunningVms()); + Assert.assertEquals(expected.getStoppedVms(), result.getStoppedVms()); } @Test @@ -1362,7 +1393,7 @@ public class PresetVariableHelperTest { presetVariableHelperSpy.loadPresetVariableValueForVpc(usageVoMock, null); }); - Mockito.verifyNoInteractions(networkDaoMock); + Mockito.verifyNoInteractions(vpcDaoMock); } @Test diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java index 173c0723731..c919bb5887c 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java @@ -98,6 +98,7 @@ import org.apache.cloudstack.quota.activationrule.presetvariables.ComputingResou import org.apache.cloudstack.quota.activationrule.presetvariables.GenericPresetVariable; import org.apache.cloudstack.quota.activationrule.presetvariables.PresetVariableDefinition; import org.apache.cloudstack.quota.activationrule.presetvariables.PresetVariables; +import org.apache.cloudstack.quota.activationrule.presetvariables.ResourceCounting; import org.apache.cloudstack.quota.activationrule.presetvariables.Value; import org.apache.cloudstack.quota.constant.QuotaConfig; import org.apache.cloudstack.quota.constant.QuotaTypes; @@ -185,7 +186,7 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { private VolumeDao volumeDao; - private final Class[] assignableClasses = {GenericPresetVariable.class, ComputingResources.class}; + private final Class[] assignableClasses = {GenericPresetVariable.class, ComputingResources.class, ResourceCounting.class}; private Set accountTypesThatCanListAllQuotaSummaries = Sets.newHashSet(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN); diff --git a/plugins/host-allocators/random/pom.xml b/plugins/host-allocators/random/pom.xml deleted file mode 100644 index 061caf1573f..00000000000 --- a/plugins/host-allocators/random/pom.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - 4.0.0 - cloud-plugin-host-allocator-random - Apache CloudStack Plugin - Host Allocator Random - - org.apache.cloudstack - cloudstack-plugins - 4.23.0.0-SNAPSHOT - ../../pom.xml - - diff --git a/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java b/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java deleted file mode 100644 index 42129944a19..00000000000 --- a/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java +++ /dev/null @@ -1,196 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package com.cloud.agent.manager.allocator.impl; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.inject.Inject; - -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.collections.ListUtils; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; - -import com.cloud.agent.manager.allocator.HostAllocator; -import com.cloud.capacity.CapacityManager; -import com.cloud.dc.ClusterDetailsDao; -import com.cloud.dc.dao.ClusterDao; -import com.cloud.deploy.DeploymentPlan; -import com.cloud.deploy.DeploymentPlanner.ExcludeList; -import com.cloud.host.Host; -import com.cloud.host.Host.Type; -import com.cloud.host.HostVO; -import com.cloud.host.dao.HostDao; -import com.cloud.offering.ServiceOffering; -import com.cloud.resource.ResourceManager; -import com.cloud.storage.VMTemplateVO; -import com.cloud.utils.Pair; -import com.cloud.utils.component.AdapterBase; -import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VirtualMachineProfile; - -@Component -public class RandomAllocator extends AdapterBase implements HostAllocator { - @Inject - private HostDao _hostDao; - @Inject - private ResourceManager _resourceMgr; - @Inject - private ClusterDao clusterDao; - @Inject - private ClusterDetailsDao clusterDetailsDao; - @Inject - private CapacityManager capacityManager; - - protected List listHostsByTags(Host.Type type, long dcId, Long podId, Long clusterId, String offeringHostTag, String templateTag) { - List taggedHosts = new ArrayList<>(); - if (offeringHostTag != null) { - taggedHosts.addAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, offeringHostTag)); - } - if (templateTag != null) { - List templateTaggedHosts = _hostDao.listByHostTag(type, clusterId, podId, dcId, templateTag); - if (taggedHosts.isEmpty()) { - taggedHosts = templateTaggedHosts; - } else { - taggedHosts.retainAll(templateTaggedHosts); - } - } - if (logger.isDebugEnabled()) { - logger.debug(String.format("Found %d hosts %s with type: %s, zone ID: %d, pod ID: %d, cluster ID: %s, offering host tag(s): %s, template tag: %s", - taggedHosts.size(), - (taggedHosts.isEmpty() ? "" : String.format("(%s)", StringUtils.join(taggedHosts.stream().map(HostVO::toString).toArray(), ","))), - type.name(), dcId, podId, clusterId, offeringHostTag, templateTag)); - } - return taggedHosts; - } - private List findSuitableHosts(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, - ExcludeList avoid, List hosts, int returnUpTo, - boolean considerReservedCapacity) { - long dcId = plan.getDataCenterId(); - Long podId = plan.getPodId(); - Long clusterId = plan.getClusterId(); - ServiceOffering offering = vmProfile.getServiceOffering(); - List hostsCopy = null; - List suitableHosts = new ArrayList<>(); - - if (type == Host.Type.Storage) { - return suitableHosts; - } - String offeringHostTag = offering.getHostTag(); - - VMTemplateVO template = (VMTemplateVO)vmProfile.getTemplate(); - String templateTag = template.getTemplateTag(); - String hostTag = null; - if (ObjectUtils.anyNotNull(offeringHostTag, templateTag)) { - hostTag = ObjectUtils.allNotNull(offeringHostTag, templateTag) ? - String.format("%s, %s", offeringHostTag, templateTag) : - ObjectUtils.firstNonNull(offeringHostTag, templateTag); - logger.debug("Looking for hosts in dc [{}], pod [{}], cluster [{}] and complying with host tag(s): [{}]", dcId, podId, clusterId, hostTag); - } else { - logger.debug("Looking for hosts in dc: {} pod: {} cluster: {}", dcId , podId, clusterId); - } - if (hosts != null) { - // retain all computing hosts, regardless of whether they support routing...it's random after all - hostsCopy = new ArrayList<>(hosts); - if (ObjectUtils.anyNotNull(offeringHostTag, templateTag)) { - hostsCopy.retainAll(listHostsByTags(type, dcId, podId, clusterId, offeringHostTag, templateTag)); - } else { - hostsCopy.retainAll(_hostDao.listAllHostsThatHaveNoRuleTag(type, clusterId, podId, dcId)); - } - } else { - // list all computing hosts, regardless of whether they support routing...it's random after all - if (offeringHostTag != null) { - hostsCopy = listHostsByTags(type, dcId, podId, clusterId, offeringHostTag, templateTag); - } else { - hostsCopy = _hostDao.listAllHostsThatHaveNoRuleTag(type, clusterId, podId, dcId); - } - } - hostsCopy = ListUtils.union(hostsCopy, _hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(offeringHostTag)); - - if (hostsCopy.isEmpty()) { - logger.info("No suitable host found for VM [{}] in {}.", vmProfile, hostTag); - return null; - } - - logger.debug("Random Allocator found {} hosts", hostsCopy.size()); - if (hostsCopy.isEmpty()) { - return suitableHosts; - } - - Collections.shuffle(hostsCopy); - for (Host host : hostsCopy) { - if (suitableHosts.size() == returnUpTo) { - break; - } - if (avoid.shouldAvoid(host)) { - if (logger.isDebugEnabled()) { - logger.debug(String.format("Host %s is in avoid set, skipping this and trying other available hosts", host)); - } - continue; - } - Pair cpuCapabilityAndCapacity = capacityManager.checkIfHostHasCpuCapabilityAndCapacity(host, offering, considerReservedCapacity); - if (!cpuCapabilityAndCapacity.first() || !cpuCapabilityAndCapacity.second()) { - if (logger.isDebugEnabled()) { - logger.debug(String.format("Not using host %s; host has cpu capability? %s, host has capacity? %s", host, cpuCapabilityAndCapacity.first(), cpuCapabilityAndCapacity.second())); - } - continue; - } - if (logger.isDebugEnabled()) { - logger.debug(String.format("Found a suitable host, adding to list: %s", host)); - } - suitableHosts.add(host); - } - if (logger.isDebugEnabled()) { - logger.debug("Random Host Allocator returning " + suitableHosts.size() + " suitable hosts"); - } - return suitableHosts; - } - - @Override - public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo) { - return allocateTo(vmProfile, plan, type, avoid, returnUpTo, true); - } - - @Override - public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, - ExcludeList avoid, List hosts, int returnUpTo, - boolean considerReservedCapacity) { - if (CollectionUtils.isEmpty(hosts)) { - if (logger.isDebugEnabled()) { - logger.debug("Random Allocator found 0 hosts as given host list is empty"); - } - return new ArrayList<>(); - } - return findSuitableHosts(vmProfile, plan, type, avoid, hosts, returnUpTo, considerReservedCapacity); - } - - @Override - public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, - Type type, ExcludeList avoid, int returnUpTo, boolean considerReservedCapacity) { - return findSuitableHosts(vmProfile, plan, type, avoid, null, returnUpTo, considerReservedCapacity); - } - - @Override - public boolean isVirtualMachineUpgradable(VirtualMachine vm, ServiceOffering offering) { - // currently we do no special checks to rule out a VM being upgradable to an offering, so - // return true - return true; - } -} diff --git a/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/module.properties b/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/module.properties deleted file mode 100644 index dcfe8d3537f..00000000000 --- a/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/module.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -name=host-allocator-random -parent=allocator diff --git a/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/spring-host-allocator-random-context.xml b/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/spring-host-allocator-random-context.xml deleted file mode 100644 index d84eaafaa5a..00000000000 --- a/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/spring-host-allocator-random-context.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - diff --git a/plugins/host-allocators/random/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java b/plugins/host-allocators/random/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java deleted file mode 100644 index 538d7157184..00000000000 --- a/plugins/host-allocators/random/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java +++ /dev/null @@ -1,80 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package com.cloud.agent.manager.allocator.impl; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.collections.CollectionUtils; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; - -import com.cloud.host.Host; -import com.cloud.host.HostVO; -import com.cloud.host.dao.HostDao; - -@RunWith(MockitoJUnitRunner.class) -public class RandomAllocatorTest { - - @Mock - HostDao hostDao; - @InjectMocks - RandomAllocator randomAllocator; - - @Test - public void testListHostsByTags() { - Host.Type type = Host.Type.Routing; - Long id = 1L; - String templateTag = "tag1"; - String offeringTag = "tag2"; - HostVO host1 = Mockito.mock(HostVO.class); - HostVO host2 = Mockito.mock(HostVO.class); - Mockito.when(hostDao.listByHostTag(type, id, id, id, offeringTag)).thenReturn(List.of(host1, host2)); - - // No template tagged host - Mockito.when(hostDao.listByHostTag(type, id, id, id, templateTag)).thenReturn(new ArrayList<>()); - List result = randomAllocator.listHostsByTags(type, id, id, id, offeringTag, templateTag); - Assert.assertTrue(CollectionUtils.isEmpty(result)); - - // Different template tagged host - HostVO host3 = Mockito.mock(HostVO.class); - Mockito.when(hostDao.listByHostTag(type, id, id, id, templateTag)).thenReturn(List.of(host3)); - result = randomAllocator.listHostsByTags(type, id, id, id, offeringTag, templateTag); - Assert.assertTrue(CollectionUtils.isEmpty(result)); - - // Matching template tagged host - Mockito.when(hostDao.listByHostTag(type, id, id, id, templateTag)).thenReturn(List.of(host1)); - result = randomAllocator.listHostsByTags(type, id, id, id, offeringTag, templateTag); - Assert.assertFalse(CollectionUtils.isEmpty(result)); - Assert.assertEquals(1, result.size()); - - // No template tag - result = randomAllocator.listHostsByTags(type, id, id, id, offeringTag, null); - Assert.assertFalse(CollectionUtils.isEmpty(result)); - Assert.assertEquals(2, result.size()); - - // No offering tag - result = randomAllocator.listHostsByTags(type, id, id, id, null, templateTag); - Assert.assertFalse(CollectionUtils.isEmpty(result)); - Assert.assertEquals(1, result.size()); - } -} diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java index 5a027425776..140302590ba 100644 --- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java +++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java @@ -57,7 +57,7 @@ public class QemuImgTest { Connect conn = new Connect("qemu:///system", false); conn.getVersion(); libVirtAvailable = true; - } catch (LibvirtException ignored) {} + } catch (LibvirtException | UnsatisfiedLinkError | ExceptionInInitializerError ignored) {} Assume.assumeTrue("libvirt not available", libVirtAvailable); } diff --git a/plugins/pom.xml b/plugins/pom.xml index b1ce441a84b..8c68df8ff18 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -84,8 +84,6 @@ ha-planners/skip-heurestics - host-allocators/random - hypervisors/baremetal hypervisors/external hypervisors/hyperv diff --git a/server/src/main/java/com/cloud/agent/manager/allocator/impl/BaseAllocator.java b/server/src/main/java/com/cloud/agent/manager/allocator/impl/BaseAllocator.java new file mode 100644 index 00000000000..58fcc62cdc3 --- /dev/null +++ b/server/src/main/java/com/cloud/agent/manager/allocator/impl/BaseAllocator.java @@ -0,0 +1,90 @@ +// 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 com.cloud.agent.manager.allocator.impl; + +import com.cloud.agent.manager.allocator.HostAllocator; +import com.cloud.capacity.CapacityManager; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.offering.ServiceOffering; +import com.cloud.utils.Pair; +import com.cloud.utils.component.AdapterBase; +import org.apache.commons.collections.CollectionUtils; + +import javax.inject.Inject; +import java.util.List; + +public abstract class BaseAllocator extends AdapterBase implements HostAllocator { + + @Inject + protected HostDao hostDao; + + @Inject + protected CapacityManager capacityManager; + + protected void retainHostsMatchingServiceOfferingAndTemplateTags(List availableHosts, Host.Type type, long dcId, Long podId, Long clusterId, String offeringHostTag, String templateTag) { + logger.debug("Hosts {} will be checked for template and host tags compatibility.", availableHosts); + + if (offeringHostTag != null) { + logger.debug("Looking for hosts having the tag [{}] specified in the Service Offering.", offeringHostTag); + List hostsWithHostTag = hostDao.listByHostTag(type, clusterId, podId, dcId, offeringHostTag); + logger.debug("Retaining hosts {} because they match the offering host tag {}.", hostsWithHostTag, offeringHostTag); + availableHosts.retainAll(hostsWithHostTag); + } + + if (templateTag != null) { + logger.debug("Looking for hosts having the tag [{}] specified in the Template.", templateTag); + List hostsWithTemplateTag = hostDao.listByHostTag(type, clusterId, podId, dcId, templateTag); + logger.debug("Retaining hosts {} because they match the template tag {}.", hostsWithTemplateTag, templateTag); + availableHosts.retainAll(hostsWithTemplateTag); + } + + logger.debug("Remaining hosts after template tag and host tags validations are {}.", availableHosts); + } + + protected void addHostsBasedOnTagRules(String hostTagOnOffering, List clusterHosts) { + List hostsWithTagRules = hostDao.findHostsWithTagRuleThatMatchComputeOfferingTags(hostTagOnOffering); + + if (CollectionUtils.isEmpty(hostsWithTagRules)) { + logger.info("No hosts found with tag rules matching the compute offering tag [{}].", hostTagOnOffering); + return; + } + + logger.info("Found hosts {} with tag rules matching the compute offering tag [{}].", hostsWithTagRules, hostTagOnOffering); + clusterHosts.addAll(hostsWithTagRules); + } + + /** + * Adds hosts with enough CPU capability and enough CPU capacity to the suitable hosts list. + */ + protected boolean hostHasCpuCapabilityAndCapacity(boolean considerReservedCapacity, ServiceOffering offering, Host host) { + logger.debug("Looking for CPU frequency {} MHz and RAM {} MB.", () -> offering.getCpu() * offering.getSpeed(), offering::getRamSize); + Pair cpuCapabilityAndCapacity = capacityManager.checkIfHostHasCpuCapabilityAndCapacity(host, offering, considerReservedCapacity); + Boolean hasCpuCapability = cpuCapabilityAndCapacity.first(); + Boolean hasCpuCapacity = cpuCapabilityAndCapacity.second(); + + if (hasCpuCapability && hasCpuCapacity) { + logger.debug("Host {} is a suitable host as it has enough CPU capability and CPU capacity.", () -> host); + return true; + } + + logger.debug("Cannot use host {}. Does the host have CPU capability? {}. Does the host have CPU capacity? {}..", () -> host, () -> hasCpuCapability, () -> hasCpuCapacity); + return false; + } + +} diff --git a/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java b/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java index 1110804959e..8b4ca318bf7 100644 --- a/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java +++ b/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java @@ -16,6 +16,41 @@ // under the License. package com.cloud.agent.manager.allocator.impl; +import static com.cloud.deploy.DeploymentPlanner.AllocationAlgorithm.firstfitleastconsumed; +import static com.cloud.deploy.DeploymentPlanner.AllocationAlgorithm.random; +import static com.cloud.deploy.DeploymentPlanner.AllocationAlgorithm.userdispersing; + +import com.cloud.capacity.Capacity; +import com.cloud.capacity.CapacityVO; +import com.cloud.capacity.dao.CapacityDao; +import com.cloud.configuration.Config; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.deploy.DeploymentClusterPlanner; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.deploy.FirstFitPlanner; +import com.cloud.gpu.GPU; +import com.cloud.host.DetailVO; +import com.cloud.host.Host; +import com.cloud.host.Host.Type; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDetailsDao; +import com.cloud.offering.ServiceOffering; +import com.cloud.resource.ResourceManager; +import com.cloud.service.dao.ServiceOfferingDetailsDao; +import com.cloud.storage.GuestOSCategoryVO; +import com.cloud.storage.GuestOSVO; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.dao.GuestOSCategoryDao; +import com.cloud.storage.dao.GuestOSDao; +import com.cloud.user.Account; +import com.cloud.utils.Pair; +import com.cloud.utils.StringUtils; +import com.cloud.vm.VMInstanceDetailVO; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; + import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; @@ -29,59 +64,19 @@ import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.gpu.dao.VgpuProfileDao; - +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import org.springframework.stereotype.Component; - -import com.cloud.agent.manager.allocator.HostAllocator; -import com.cloud.capacity.Capacity; -import com.cloud.capacity.CapacityManager; -import com.cloud.capacity.CapacityVO; -import com.cloud.capacity.dao.CapacityDao; -import com.cloud.configuration.Config; -import com.cloud.configuration.ConfigurationManager; -import com.cloud.dc.ClusterDetailsDao; -import com.cloud.dc.dao.ClusterDao; -import com.cloud.deploy.DeploymentPlan; -import com.cloud.deploy.DeploymentClusterPlanner; -import com.cloud.deploy.DeploymentPlanner.ExcludeList; -import com.cloud.deploy.FirstFitPlanner; -import com.cloud.gpu.GPU; -import com.cloud.host.DetailVO; -import com.cloud.host.Host; -import com.cloud.host.Host.Type; -import com.cloud.host.HostVO; -import com.cloud.host.dao.HostDao; -import com.cloud.host.dao.HostDetailsDao; -import com.cloud.offering.ServiceOffering; -import com.cloud.resource.ResourceManager; -import com.cloud.service.ServiceOfferingDetailsVO; -import com.cloud.service.dao.ServiceOfferingDetailsDao; -import com.cloud.storage.GuestOSCategoryVO; -import com.cloud.storage.GuestOSVO; -import com.cloud.storage.VMTemplateVO; -import com.cloud.storage.dao.GuestOSCategoryDao; -import com.cloud.storage.dao.GuestOSDao; -import com.cloud.user.Account; -import com.cloud.utils.Pair; -import com.cloud.utils.component.AdapterBase; -import com.cloud.vm.VMInstanceDetailVO; -import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VirtualMachineProfile; -import com.cloud.vm.dao.VMInstanceDetailsDao; -import com.cloud.vm.dao.VMInstanceDao; - +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; /** * An allocator that tries to find a fit on a computing host. This allocator does not care whether or not the host supports routing. */ @Component -public class FirstFitAllocator extends AdapterBase implements HostAllocator { - @Inject - protected HostDao _hostDao = null; +public class FirstFitAllocator extends BaseAllocator { @Inject HostDetailsDao _hostDetailsDao = null; @Inject @@ -95,289 +90,183 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { @Inject protected ResourceManager _resourceMgr; @Inject - ClusterDao _clusterDao; - @Inject - ClusterDetailsDao _clusterDetailsDao; - @Inject ServiceOfferingDetailsDao _serviceOfferingDetailsDao; @Inject - CapacityManager _capacityMgr; - @Inject CapacityDao _capacityDao; @Inject - VMInstanceDetailsDao _vmInstanceDetailsDao; - @Inject - private VgpuProfileDao vgpuProfileDao; + VMInstanceDetailsDao vmInstanceDetailsDao; boolean _checkHvm = true; + static DecimalFormat decimalFormat = new DecimalFormat("#.##"); @Override public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo) { - return allocateTo(vmProfile, plan, type, avoid, returnUpTo, true); - } - - @Override - public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo, boolean considerReservedCapacity) { - - long dcId = plan.getDataCenterId(); - Long podId = plan.getPodId(); - Long clusterId = plan.getClusterId(); - ServiceOffering offering = vmProfile.getServiceOffering(); - VMTemplateVO template = (VMTemplateVO)vmProfile.getTemplate(); - Account account = vmProfile.getOwner(); - - boolean isVMDeployedWithUefi = false; - VMInstanceDetailVO vmInstanceDetailVO = _vmInstanceDetailsDao.findDetail(vmProfile.getId(), "UEFI"); - if(vmInstanceDetailVO != null){ - if ("secure".equalsIgnoreCase(vmInstanceDetailVO.getValue()) || "legacy".equalsIgnoreCase(vmInstanceDetailVO.getValue())) { - isVMDeployedWithUefi = true; - } - } - logger.info(" Guest VM is requested with Custom[UEFI] Boot Type "+ isVMDeployedWithUefi); - - - if (type == Host.Type.Storage) { - // FirstFitAllocator should be used for user VMs only since it won't care whether the host is capable of routing or not - return new ArrayList<>(); - } - String paramAsStringToLog = String.format("zone [%s], pod [%s], cluster [%s]", dcId, podId, clusterId); - logger.debug("Looking for hosts in {}", paramAsStringToLog); - - String hostTagOnOffering = offering.getHostTag(); - String hostTagOnTemplate = template.getTemplateTag(); - String hostTagUefi = "UEFI"; - - boolean hasSvcOfferingTag = hostTagOnOffering != null ? true : false; - boolean hasTemplateTag = hostTagOnTemplate != null ? true : false; - - List clusterHosts = new ArrayList<>(); - List hostsMatchingUefiTag = new ArrayList<>(); - if(isVMDeployedWithUefi){ - hostsMatchingUefiTag = _hostDao.listByHostCapability(type, clusterId, podId, dcId, Host.HOST_UEFI_ENABLE); - if (logger.isDebugEnabled()) { - logger.debug("Hosts with tag '" + hostTagUefi + "' are:" + hostsMatchingUefiTag); - } - } - - - String haVmTag = (String)vmProfile.getParameter(VirtualMachineProfile.Param.HaTag); - if (haVmTag != null) { - clusterHosts = _hostDao.listByHostTag(type, clusterId, podId, dcId, haVmTag); - } else { - if (hostTagOnOffering == null && hostTagOnTemplate == null) { - clusterHosts = _resourceMgr.listAllUpAndEnabledNonHAHosts(type, clusterId, podId, dcId); - } else { - List hostsMatchingOfferingTag = new ArrayList<>(); - List hostsMatchingTemplateTag = new ArrayList<>(); - if (hasSvcOfferingTag) { - if (logger.isDebugEnabled()) { - logger.debug("Looking for hosts having tag specified on SvcOffering:" + hostTagOnOffering); - } - hostsMatchingOfferingTag = _hostDao.listByHostTag(type, clusterId, podId, dcId, hostTagOnOffering); - if (logger.isDebugEnabled()) { - logger.debug("Hosts with tag '" + hostTagOnOffering + "' are:" + hostsMatchingOfferingTag); - } - } - if (hasTemplateTag) { - if (logger.isDebugEnabled()) { - logger.debug("Looking for hosts having tag specified on Template:" + hostTagOnTemplate); - } - hostsMatchingTemplateTag = _hostDao.listByHostTag(type, clusterId, podId, dcId, hostTagOnTemplate); - if (logger.isDebugEnabled()) { - logger.debug("Hosts with tag '" + hostTagOnTemplate + "' are:" + hostsMatchingTemplateTag); - } - } - - if (hasSvcOfferingTag && hasTemplateTag) { - hostsMatchingOfferingTag.retainAll(hostsMatchingTemplateTag); - if (logger.isDebugEnabled()) { - logger.debug("Found " + hostsMatchingOfferingTag.size() + " Hosts satisfying both tags, host ids are:" + hostsMatchingOfferingTag); - } - - clusterHosts = hostsMatchingOfferingTag; - } else { - if (hasSvcOfferingTag) { - clusterHosts = hostsMatchingOfferingTag; - } else { - clusterHosts = hostsMatchingTemplateTag; - } - } - } - } - - if (isVMDeployedWithUefi) { - clusterHosts.retainAll(hostsMatchingUefiTag); - } - - clusterHosts.addAll(_hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(hostTagOnOffering)); - - - if (clusterHosts.isEmpty()) { - logger.warn("No suitable host found for VM [{}] with tags {} in {}.", vmProfile, hostTagOnOffering, paramAsStringToLog); - return null; - } - // add all hosts that we are not considering to the avoid list - List allhostsInCluster = _hostDao.listAllUpAndEnabledNonHAHosts(type, clusterId, podId, dcId, null); - allhostsInCluster.removeAll(clusterHosts); - - logger.debug(() -> String.format("Adding hosts [%s] to the avoid set because these hosts do not support HA.", - ReflectionToStringBuilderUtils.reflectOnlySelectedFields(allhostsInCluster, "uuid", "name"))); - - for (HostVO host : allhostsInCluster) { - avoid.addHost(host.getId()); - } - - return allocateTo(vmProfile, plan, offering, template, avoid, clusterHosts, returnUpTo, considerReservedCapacity, account); + return allocateTo(vmProfile, plan, type, avoid, null, returnUpTo, true); } @Override public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, List hosts, int returnUpTo, - boolean considerReservedCapacity) { + boolean considerReservedCapacity) { + if (type == Host.Type.Storage) { + return null; + } + long dcId = plan.getDataCenterId(); Long podId = plan.getPodId(); Long clusterId = plan.getClusterId(); ServiceOffering offering = vmProfile.getServiceOffering(); - VMTemplateVO template = (VMTemplateVO)vmProfile.getTemplate(); + VMTemplateVO template = (VMTemplateVO) vmProfile.getTemplate(); Account account = vmProfile.getOwner(); - List suitableHosts = new ArrayList<>(); - List hostsCopy = new ArrayList<>(hosts); - - if (type == Host.Type.Storage) { - // FirstFitAllocator should be used for user VMs only since it won't care whether the host is capable of - // routing or not. - return suitableHosts; - } String hostTagOnOffering = offering.getHostTag(); String hostTagOnTemplate = template.getTemplateTag(); - boolean hasSvcOfferingTag = hostTagOnOffering != null ? true : false; - boolean hasTemplateTag = hostTagOnTemplate != null ? true : false; + String paramAsStringToLog = String.format("zone [%s], pod [%s], cluster [%s]", dcId, podId, clusterId); - String haVmTag = (String)vmProfile.getParameter(VirtualMachineProfile.Param.HaTag); - if (haVmTag != null) { - hostsCopy.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, haVmTag)); + List suitableHosts = retrieveHosts(vmProfile, type, (List) hosts, clusterId, podId, dcId, hostTagOnOffering, hostTagOnTemplate); + + if (suitableHosts.isEmpty()) { + logger.info("No suitable host found for VM [{}] in {}.", vmProfile, paramAsStringToLog); + return null; + } + + if (CollectionUtils.isEmpty(hosts)) { + addHostsToAvoidSet(type, avoid, clusterId, podId, dcId, suitableHosts); + } + + return allocateTo(vmProfile, plan, offering, template, avoid, suitableHosts, returnUpTo, considerReservedCapacity, account); + } + + protected List retrieveHosts(VirtualMachineProfile vmProfile, Type type, List hostsToFilter, Long clusterId, Long podId, long dcId, String hostTagOnOffering, + String hostTagOnTemplate) { + String haVmTag = (String) vmProfile.getParameter(VirtualMachineProfile.Param.HaTag); + List clusterHosts; + + if (CollectionUtils.isNotEmpty(hostsToFilter)) { + clusterHosts = new ArrayList<>(hostsToFilter); } else { - if (hostTagOnOffering == null && hostTagOnTemplate == null) { - hostsCopy.retainAll(_resourceMgr.listAllUpAndEnabledNonHAHosts(type, clusterId, podId, dcId)); - } else { - if (hasSvcOfferingTag) { - if (logger.isDebugEnabled()) { - logger.debug("Looking for hosts having tag specified on SvcOffering:" + hostTagOnOffering); - } - hostsCopy.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, hostTagOnOffering)); - - if (logger.isDebugEnabled()) { - logger.debug("Hosts with tag '" + hostTagOnOffering + "' are:" + hostsCopy); - } - } - - if (hasTemplateTag) { - if (logger.isDebugEnabled()) { - logger.debug("Looking for hosts having tag specified on Template:" + hostTagOnTemplate); - } - - hostsCopy.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, hostTagOnTemplate)); - - if (logger.isDebugEnabled()) { - logger.debug("Hosts with tag '" + hostTagOnTemplate + "' are:" + hostsCopy); - } - } - } + clusterHosts = _resourceMgr.listAllUpAndEnabledHosts(type, clusterId, podId, dcId); } - hostsCopy.addAll(_hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(hostTagOnOffering)); - - if (!hostsCopy.isEmpty()) { - suitableHosts = allocateTo(vmProfile, plan, offering, template, avoid, hostsCopy, returnUpTo, considerReservedCapacity, account); + if (haVmTag != null) { + clusterHosts.retainAll(hostDao.listByHostTag(type, clusterId, podId, dcId, haVmTag)); + } else if (ObjectUtils.allNull(hostTagOnOffering, hostTagOnTemplate)) { + clusterHosts.retainAll(_resourceMgr.listAllUpAndEnabledNonHAHosts(type, clusterId, podId, dcId)); + } else { + retainHostsMatchingServiceOfferingAndTemplateTags(clusterHosts, type, dcId, podId, clusterId, hostTagOnOffering, hostTagOnTemplate); } + filterHostsWithUefiEnabled(type, vmProfile, clusterId, podId, dcId, clusterHosts); + + addHostsBasedOnTagRules(hostTagOnOffering, clusterHosts); + + return clusterHosts; + + } + + /** + * Add all hosts to the avoid set that were not considered during the allocation + */ + protected void addHostsToAvoidSet(Type type, ExcludeList avoid, Long clusterId, Long podId, long dcId, List suitableHosts) { + List allHostsInCluster = hostDao.listAllUpAndEnabledNonHAHosts(type, clusterId, podId, dcId, null); + + allHostsInCluster.removeAll(suitableHosts); + + logger.debug("Adding hosts [{}] to the avoid set because these hosts were not considered for allocation.", + () -> ReflectionToStringBuilderUtils.reflectOnlySelectedFields(allHostsInCluster, "uuid", "name")); + + for (HostVO host : allHostsInCluster) { + avoid.addHost(host.getId()); + } + } + + protected void filterHostsWithUefiEnabled(Type type, VirtualMachineProfile vmProfile, Long clusterId, Long podId, long dcId, List clusterHosts) { + VMInstanceDetailVO vmInstanceDetailVO = vmInstanceDetailsDao.findDetail(vmProfile.getId(), "UEFI"); + + if (vmInstanceDetailVO == null) { + return; + } + + if (!StringUtils.equalsAnyIgnoreCase(vmInstanceDetailVO.getValue(), ApiConstants.BootMode.SECURE.toString(), ApiConstants.BootMode.LEGACY.toString())) { + return; + } + + logger.info("Guest VM is requested with Custom[UEFI] Boot Type enabled."); + + List hostsMatchingUefiTag = hostDao.listByHostCapability(type, clusterId, podId, dcId, Host.HOST_UEFI_ENABLE); + + logger.debug("Hosts with UEFI enabled are {}.", hostsMatchingUefiTag); + clusterHosts.retainAll(hostsMatchingUefiTag); + } + + protected List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, ServiceOffering offering, VMTemplateVO template, ExcludeList avoid, List hosts, int returnUpTo, + boolean considerReservedCapacity, Account account) { + String vmAllocationAlgorithm = DeploymentClusterPlanner.VmAllocationAlgorithm.value(); + if (random.toString().equals(vmAllocationAlgorithm)) { + Collections.shuffle(hosts); + } else if (userdispersing.toString().equals(vmAllocationAlgorithm)) { + hosts = reorderHostsByNumberOfVms(plan, hosts, account); + } else if (firstfitleastconsumed.toString().equals(vmAllocationAlgorithm)) { + hosts = reorderHostsByCapacity(plan, hosts); + } + + logger.debug("FirstFitAllocator has {} hosts to check for allocation {}.", hosts.size(), hosts); + hosts = prioritizeHosts(template, offering, hosts); + logger.debug("Found {} hosts for allocation after prioritization: {}.", hosts.size(), hosts); + + List suitableHosts = checkHostsCompatibilities(offering, vmProfile, avoid, hosts, returnUpTo, considerReservedCapacity); + logger.debug("Host Allocator returning {} suitable hosts.", suitableHosts.size()); + return suitableHosts; } - protected List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, ServiceOffering offering, VMTemplateVO template, ExcludeList avoid, List hosts, int returnUpTo, - boolean considerReservedCapacity, Account account) { - String vmAllocationAlgorithm = DeploymentClusterPlanner.VmAllocationAlgorithm.value(); - if (vmAllocationAlgorithm.equals("random")) { - // Shuffle this so that we don't check the hosts in the same order. - Collections.shuffle(hosts); - } else if (vmAllocationAlgorithm.equals("userdispersing")) { - hosts = reorderHostsByNumberOfVms(plan, hosts, account); - } else if(vmAllocationAlgorithm.equals("firstfitleastconsumed")){ - hosts = reorderHostsByCapacity(plan, hosts); - } - - if (logger.isDebugEnabled()) { - logger.debug("FirstFitAllocator has " + hosts.size() + " hosts to check for allocation: " + hosts); - } - - // We will try to reorder the host lists such that we give priority to hosts that have - // the minimums to support a VM's requirements - hosts = prioritizeHosts(template, offering, hosts); - - if (logger.isDebugEnabled()) { - logger.debug("Found " + hosts.size() + " hosts for allocation after prioritization: " + hosts); - } - - if (logger.isDebugEnabled()) { - logger.debug("Looking for speed=" + (offering.getCpu() * offering.getSpeed()) + "Mhz, Ram=" + offering.getRamSize() + " MB"); - } - - long serviceOfferingId = offering.getId(); + protected List checkHostsCompatibilities(ServiceOffering offering, VirtualMachineProfile vmProfile, ExcludeList avoid, List hosts, int returnUpTo, boolean considerReservedCapacity) { List suitableHosts = new ArrayList<>(); - ServiceOfferingDetailsVO offeringDetails = null; + logger.debug("Checking compatibility for the following hosts {}.", suitableHosts); for (Host host : hosts) { if (suitableHosts.size() == returnUpTo) { break; } + if (avoid.shouldAvoid(host)) { - if (logger.isDebugEnabled()) { - logger.debug("Host: {} is in avoid set, skipping this and trying other available hosts", host); - } + logger.debug("Host [{}] is in avoid set, skipping this and trying other available hosts", host); continue; } - //find number of guest VMs occupying capacity on this host. - if (_capacityMgr.checkIfHostReachMaxGuestLimit(host)) { + if (capacityManager.checkIfHostReachMaxGuestLimit(host)) { logger.debug("Adding host [{}] to the avoid set because this host already has the max number of running (user and/or system) VMs.", host); avoid.addHost(host.getId()); continue; } - // Check if GPU device is required by offering and host has the availability - if (_resourceMgr.isGPUDeviceAvailable(offering, host, vmProfile.getId())) { - logger.debug("Host [{}] has required GPU devices available.", host); - } else { - // If GPU is not available, skip this host - logger.debug("Adding host [{}] to avoid set, because this host does not have required GPU devices available.", host); - avoid.addHost(host.getId()); + if (offeringRequestedVGpuAndHostDoesNotHaveIt(offering, vmProfile, avoid, host)) { continue; } - Pair cpuCapabilityAndCapacity = _capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(host, offering, considerReservedCapacity); - if (cpuCapabilityAndCapacity.first() && cpuCapabilityAndCapacity.second()) { - if (logger.isDebugEnabled()) { - logger.debug("Found a suitable host, adding to list: {}", host); - } + if (hostHasCpuCapabilityAndCapacity(considerReservedCapacity, offering, host)) { suitableHosts.add(host); - } else { - if (logger.isDebugEnabled()) { - logger.debug("Not using host {}; host has cpu capability? {}, host has capacity?{}", - host, cpuCapabilityAndCapacity.first(), cpuCapabilityAndCapacity.second()); - } - avoid.addHost(host.getId()); + continue; } + avoid.addHost(host.getId()); } - - if (logger.isDebugEnabled()) { - logger.debug("Host Allocator returning " + suitableHosts.size() + " suitable hosts"); - } - return suitableHosts; } - // Reorder hosts in the decreasing order of free capacity. + protected boolean offeringRequestedVGpuAndHostDoesNotHaveIt(ServiceOffering offering, VirtualMachineProfile vmProfile, ExcludeList avoid, Host host) { + if (_resourceMgr.isGPUDeviceAvailable(offering, host, vmProfile.getId())) { + logger.debug("Host [{}] has required GPU devices available.", () -> host); + return false; + } + + logger.debug("Adding host [{}] to avoid set, because this host does not have required GPU devices available.", () -> host); + avoid.addHost(host.getId()); + return true; + } + + /** + * Reorder hosts in the decreasing order of free capacity. + */ private List reorderHostsByCapacity(DeploymentPlan plan, List hosts) { Long zoneId = plan.getDataCenterId(); Long clusterId = plan.getClusterId(); @@ -388,26 +277,10 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) .collect(Collectors.toMap(Map.Entry::getKey, entry -> decimalFormat.format(entry.getValue() * 100) + "%", (e1, e2) -> e1, LinkedHashMap::new)); - if (logger.isDebugEnabled()) { - logger.debug("List of hosts: [{}] in descending order of free capacity (percentage) in the cluster: {}", - hostIdsByFreeCapacity, sortedHostByCapacity); - } + logger.debug("List of hosts: [{}] in descending order of free capacity (percentage) in the cluster: {}.", + hostIdsByFreeCapacity, sortedHostByCapacity); - //now filter the given list of Hosts by this ordered list - Map hostMap = new HashMap<>(); - for (Host host : hosts) { - hostMap.put(host.getId(), host); - } - List matchingHostIds = new ArrayList<>(hostMap.keySet()); - - hostIdsByFreeCapacity.retainAll(matchingHostIds); - - List reorderedHosts = new ArrayList<>(); - for(Long id: hostIdsByFreeCapacity){ - reorderedHosts.add(hostMap.get(id)); - } - - return reorderedHosts; + return filterHosts(hosts, hostIdsByFreeCapacity); } private Pair, Map> getOrderedHostsByCapacity(Long zoneId, Long clusterId) { @@ -450,116 +323,154 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { Long clusterId = plan.getClusterId(); List hostIdsByVmCount = _vmInstanceDao.listHostIdsByVmCount(dcId, podId, clusterId, account.getAccountId()); - if (logger.isDebugEnabled()) { - logger.debug("List of hosts in ascending order of number of VMs: " + hostIdsByVmCount); - } + logger.debug("List of hosts in ascending order of number of VMs: {}.", hostIdsByVmCount); - //now filter the given list of Hosts by this ordered list + return filterHosts(hosts, hostIdsByVmCount); + } + + /** + * Filter the given list of Hosts considering the ordered list + */ + private List filterHosts(List hosts, List orderedHostIdsList) { Map hostMap = new HashMap<>(); + for (Host host : hosts) { hostMap.put(host.getId(), host); } List matchingHostIds = new ArrayList<>(hostMap.keySet()); - - hostIdsByVmCount.retainAll(matchingHostIds); + orderedHostIdsList.retainAll(matchingHostIds); List reorderedHosts = new ArrayList<>(); - for (Long id : hostIdsByVmCount) { + for(Long id: orderedHostIdsList){ reorderedHosts.add(hostMap.get(id)); } return reorderedHosts; } - @Override - public boolean isVirtualMachineUpgradable(VirtualMachine vm, ServiceOffering offering) { - // currently we do no special checks to rule out a VM being upgradable to an offering, so - // return true - return true; - } - + /** + * Reorder the host list giving priority to hosts that have the minimum to support the VM's requirements. + */ protected List prioritizeHosts(VMTemplateVO template, ServiceOffering offering, List hosts) { if (template == null) { return hosts; } - // Determine the guest OS category of the template - String templateGuestOSCategory = getTemplateGuestOSCategory(template); + List hostsToCheck = filterHostWithNoHvmIfTemplateRequested(template, hosts); List prioritizedHosts = new ArrayList<>(); - List noHvmHosts = new ArrayList<>(); - - // If a template requires HVM and a host doesn't support HVM, remove it from consideration - List hostsToCheck = new ArrayList<>(); - if (template.isRequiresHvm()) { - for (Host host : hosts) { - if (hostSupportsHVM(host)) { - hostsToCheck.add(host); - } else { - noHvmHosts.add(host); - } - } - } else { - hostsToCheck.addAll(hosts); - } - - if (logger.isDebugEnabled()) { - if (noHvmHosts.size() > 0) { - logger.debug("Not considering hosts: " + noHvmHosts + " to deploy template: " + template + " as they are not HVM enabled"); - } - } - // If a host is tagged with the same guest OS category as the template, move it to a high priority list - // If a host is tagged with a different guest OS category than the template, move it to a low priority list List highPriorityHosts = new ArrayList<>(); List lowPriorityHosts = new ArrayList<>(); - for (Host host : hostsToCheck) { - String hostGuestOSCategory = getHostGuestOSCategory(host); - if (hostGuestOSCategory == null) { - continue; - } else if (templateGuestOSCategory != null && templateGuestOSCategory.equals(hostGuestOSCategory)) { - highPriorityHosts.add(host); - } else { - lowPriorityHosts.add(host); - } - } + prioritizeHostsWithMatchingGuestOs(template, hostsToCheck, highPriorityHosts, lowPriorityHosts); hostsToCheck.removeAll(highPriorityHosts); hostsToCheck.removeAll(lowPriorityHosts); - // Prioritize the remaining hosts by HVM capability - for (Host host : hostsToCheck) { - if (!template.isRequiresHvm() && !hostSupportsHVM(host)) { - // Host and template both do not support hvm, put it as first consideration - prioritizedHosts.add(0, host); - } else { - // Template doesn't require hvm, but the machine supports it, make it last for consideration - prioritizedHosts.add(host); - } - } - - // Merge the lists + prioritizeHostsByHvmCapability(template, hostsToCheck, prioritizedHosts); prioritizedHosts.addAll(0, highPriorityHosts); prioritizedHosts.addAll(lowPriorityHosts); - // if service offering is not GPU enabled then move all the GPU enabled hosts to the end of priority list. - if (_serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString()) == null && offering.getVgpuProfileId() == null) { + prioritizeHostsByGpuEnabled(offering, prioritizedHosts); - List gpuEnabledHosts = new ArrayList<>(); - // Check for GPU enabled hosts. - for (Host host : prioritizedHosts) { - if (_resourceMgr.isHostGpuEnabled(host.getId())) { - gpuEnabledHosts.add(host); - } - } - // Move GPU enabled hosts to the end of list - if(!gpuEnabledHosts.isEmpty()) { - prioritizedHosts.removeAll(gpuEnabledHosts); - prioritizedHosts.addAll(gpuEnabledHosts); - } - } return prioritizedHosts; } + + /** + * If a template requires HVM and a host doesn't support HVM, remove it from consideration. + */ + protected List filterHostWithNoHvmIfTemplateRequested(VMTemplateVO template, List hosts) { + List hostsToCheck = new ArrayList<>(); + + if (!template.isRequiresHvm()) { + logger.debug("Template [{}] does not require HVM, therefore, the hosts {} will not be checked for HVM compatibility.", template, hostsToCheck); + hostsToCheck.addAll(hosts); + return hostsToCheck; + } + + List noHvmHosts = new ArrayList<>(); + logger.debug("Template [{}] requires HVM, therefore, the hosts %s will be checked for HVM compatibility.", template, hostsToCheck); + + for (Host host : hosts) { + if (hostSupportsHVM(host)) { + hostsToCheck.add(host); + } else { + noHvmHosts.add(host); + } + } + + if (!noHvmHosts.isEmpty()) { + logger.debug("Not considering hosts {} to deploy VM using template {} as they are not HVM enabled.", noHvmHosts, template); + } + + return hostsToCheck; + } + + + /** + * If service offering did not request for vGPU, then move all host with GPU to the end of the host priority list. + */ + protected void prioritizeHostsByGpuEnabled(ServiceOffering offering, List prioritizedHosts) { + boolean serviceOfferingRequestedVGpu = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString()) != null || offering.getVgpuProfileId() != null; + + if (serviceOfferingRequestedVGpu) { + return; + } + + List gpuEnabledHosts = new ArrayList<>(); + + for (Host host : prioritizedHosts) { + if (_resourceMgr.isHostGpuEnabled(host.getId())) { + gpuEnabledHosts.add(host); + } + } + + if (!gpuEnabledHosts.isEmpty()) { + prioritizedHosts.removeAll(gpuEnabledHosts); + prioritizedHosts.addAll(gpuEnabledHosts); + } + } + + /** + * Prioritize remaining host by HVM capability. + * + *
    + *
  • If host and template both do not support HVM, put it at the start of the list.
  • + *
  • If the template doesn't require HVM, but the machine supports it, append it to the list.
  • + *
+ */ + protected void prioritizeHostsByHvmCapability(VMTemplateVO template, List hostsToCheck, List prioritizedHosts) { + for (Host host : hostsToCheck) { + if (!template.isRequiresHvm() && !hostSupportsHVM(host)) { + prioritizedHosts.add(0, host); + } else { + prioritizedHosts.add(host); + } + } + } + + + /** + *
    + *
  • If a host is tagged with the same guest OS category as the template, move it to a high priority list.
  • + *
  • If a host is tagged with a different guest OS category than the template, move it to a low priority list.
  • + *
+ */ + protected void prioritizeHostsWithMatchingGuestOs(VMTemplateVO template, List hostsToCheck, List highPriorityHosts, List lowPriorityHosts) { + String templateGuestOSCategory = getTemplateGuestOSCategory(template); + + for (Host host : hostsToCheck) { + String hostGuestOSCategory = getHostGuestOSCategory(host); + + if (StringUtils.equals(templateGuestOSCategory, hostGuestOSCategory)) { + highPriorityHosts.add(host); + } else if (hostGuestOSCategory != null) { + lowPriorityHosts.add(host); + } + } + } + + protected boolean hostSupportsHVM(Host host) { if (!_checkHvm) { return true; @@ -621,19 +532,8 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { if (_configDao != null) { Map configs = _configDao.getConfiguration(params); String value = configs.get("xenserver.check.hvm"); - _checkHvm = value == null ? true : Boolean.parseBoolean(value); + _checkHvm = value == null || Boolean.parseBoolean(value); } return true; } - - @Override - public boolean start() { - return true; - } - - @Override - public boolean stop() { - return true; - } - } diff --git a/server/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java b/server/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java new file mode 100644 index 00000000000..96b4255b876 --- /dev/null +++ b/server/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java @@ -0,0 +1,132 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.agent.manager.allocator.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.stereotype.Component; + +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.host.Host; +import com.cloud.host.Host.Type; +import com.cloud.host.HostVO; +import com.cloud.offering.ServiceOffering; +import com.cloud.resource.ResourceManager; +import com.cloud.storage.VMTemplateVO; +import com.cloud.vm.VirtualMachineProfile; + +@Component +public class RandomAllocator extends BaseAllocator { + @Inject + private ResourceManager _resourceMgr; + + protected List findSuitableHosts(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, List hosts, int returnUpTo, + boolean considerReservedCapacity) { + if (type == Host.Type.Storage) { + return null; + } + + long dcId = plan.getDataCenterId(); + Long podId = plan.getPodId(); + Long clusterId = plan.getClusterId(); + ServiceOffering offering = vmProfile.getServiceOffering(); + + String offeringHostTag = offering.getHostTag(); + VMTemplateVO template = (VMTemplateVO) vmProfile.getTemplate(); + logger.debug("Looking for hosts in zone [{}], pod [{}], cluster [{}].", dcId, podId, clusterId); + + List availableHosts = retrieveHosts(type, (List) hosts, template, offeringHostTag, clusterId, podId, dcId); + + if (availableHosts.isEmpty()) { + logger.info("No suitable host found for VM [{}] in zone [{}], pod [{}], cluster [{}].", vmProfile, dcId, podId, clusterId); + return null; + } + + return filterAvailableHosts(avoid, returnUpTo, considerReservedCapacity, availableHosts, offering); + } + + protected List filterAvailableHosts(ExcludeList avoid, int returnUpTo, boolean considerReservedCapacity, List availableHosts, ServiceOffering offering) { + logger.debug("Random Allocator found [{}] available hosts. They will be checked if they are in the avoid set and for CPU capability and capacity.", availableHosts::size); + List suitableHosts = new ArrayList<>(); + + Collections.shuffle(availableHosts); + for (Host host : availableHosts) { + if (suitableHosts.size() == returnUpTo) { + break; + } + + if (avoid.shouldAvoid(host)) { + logger.debug("Host [{}] is in the avoid set, skipping it and trying other available hosts.", () -> host); + continue; + } + + if (!hostHasCpuCapabilityAndCapacity(considerReservedCapacity, offering, host)) { + continue; + } + + logger.debug("Found the suitable host [{}], adding to list.", () -> host); + suitableHosts.add(host); + } + + logger.debug("Random Host Allocator returning {} suitable hosts.", suitableHosts::size); + return suitableHosts; + } + + /** + * @return all computing hosts, regardless of whether they support routing. + */ + protected List retrieveHosts(Type type, List hosts, VMTemplateVO template, String offeringHostTag, Long clusterId, Long podId, long dcId) { + List availableHosts; + String templateTag = template.getTemplateTag(); + + if (CollectionUtils.isNotEmpty(hosts)) { + availableHosts = new ArrayList<>(hosts); + } else { + availableHosts = _resourceMgr.listAllUpAndEnabledHosts(type, clusterId, podId, dcId); + } + + if (ObjectUtils.anyNotNull(offeringHostTag, templateTag)) { + retainHostsMatchingServiceOfferingAndTemplateTags(availableHosts, type, dcId, podId, clusterId, offeringHostTag, templateTag); + } else { + List hostsWithNoRuleTag = hostDao.listAllHostsThatHaveNoRuleTag(type, clusterId, podId, dcId); + logger.debug("Retaining hosts {} because they do not have rule tags.", hostsWithNoRuleTag); + availableHosts.retainAll(hostsWithNoRuleTag); + } + + addHostsBasedOnTagRules(offeringHostTag, availableHosts); + + return availableHosts; + } + + @Override + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo) { + return allocateTo(vmProfile, plan, type, avoid, null, returnUpTo, true); + } + + @Override + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, List hosts, int returnUpTo, + boolean considerReservedCapacity) { + return findSuitableHosts(vmProfile, plan, type, avoid, hosts, returnUpTo, considerReservedCapacity); + } +} diff --git a/server/src/main/java/com/cloud/agent/manager/allocator/impl/TestingAllocator.java b/server/src/main/java/com/cloud/agent/manager/allocator/impl/TestingAllocator.java index fd8e65e2476..04a0b182b7b 100644 --- a/server/src/main/java/com/cloud/agent/manager/allocator/impl/TestingAllocator.java +++ b/server/src/main/java/com/cloud/agent/manager/allocator/impl/TestingAllocator.java @@ -28,32 +28,24 @@ import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.host.Host; import com.cloud.host.Host.Type; import com.cloud.host.dao.HostDao; -import com.cloud.offering.ServiceOffering; import com.cloud.utils.component.AdapterBase; -import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; public class TestingAllocator extends AdapterBase implements HostAllocator { @Inject HostDao _hostDao; - Long _computingHost; Long _storageHost; Long _routingHost; @Override public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo) { - return allocateTo(vmProfile, plan, type, avoid, returnUpTo, true); + return allocateTo(vmProfile, plan, type, avoid, null, returnUpTo, true); } @Override public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, List hosts, int returnUpTo, - boolean considerReservedCapacity) { - return allocateTo(vmProfile, plan, type, avoid, returnUpTo, considerReservedCapacity); - } - - @Override - public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo, boolean considerReservedCapacity) { - List availableHosts = new ArrayList(); + boolean considerReservedCapacity) { + List availableHosts = new ArrayList<>(); Host host = null; if (type == Host.Type.Routing && _routingHost != null) { host = _hostDao.findById(_routingHost); @@ -66,13 +58,6 @@ public class TestingAllocator extends AdapterBase implements HostAllocator { return availableHosts; } - @Override - public boolean isVirtualMachineUpgradable(VirtualMachine vm, ServiceOffering offering) { - // currently we do no special checks to rule out a VM being upgradable to an offering, so - // return true - return true; - } - @Override public boolean configure(String name, Map params) { String value = (String)params.get(Host.Type.Routing.toString()); diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 11ac3d639e9..a8551b4c669 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -2890,6 +2890,11 @@ public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport { } } + if (CallContext.current().getCallingAccount().getType() == Account.Type.ADMIN && + network.getVpcId() == null && network.getGuestType() == Network.GuestType.Isolated) { + response.setKeepMacAddressOnPublicNic(network.getKeepMacAddressOnPublicNic()); + } + response.setObjectName("network"); return response; } @@ -3653,6 +3658,9 @@ public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport { } } + if (CallContext.current().getCallingAccount().getType() == Account.Type.ADMIN) { + response.setKeepMacAddressOnPublicNic(vpc.getKeepMacAddressOnPublicNic()); + } response.setObjectName("vpc"); return response; } diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index f163a3d52a5..7fb4a97e149 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -20,7 +20,6 @@ import static com.cloud.utils.NumbersUtil.toHumanReadableSize; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @@ -1714,20 +1713,19 @@ StateListener, Configurable { @Override public void reorderHostsByPriority(Map priorities, List hosts) { - logger.debug("Re-ordering hosts {} by priorities {}", hosts, priorities); + if (CollectionUtils.isEmpty(hosts)){ + logger.debug("Hosts list is empty; therefore, there is nothing to reorder."); + return; + } + logger.info("Re-ordering hosts [{}] by priorities [{}].", hosts, priorities); hosts.removeIf(host -> DataCenterDeployment.PROHIBITED_HOST_PRIORITY.equals(getHostPriority(priorities, host.getId()))); + hosts.sort((host1, host2) -> { + int res = getHostPriority(priorities, host1.getId()).compareTo(getHostPriority(priorities, host2.getId())); + return -res; + }); - Collections.sort(hosts, new Comparator<>() { - @Override - public int compare(Host host1, Host host2) { - int res = getHostPriority(priorities, host1.getId()).compareTo(getHostPriority(priorities, host2.getId())); - return -res; - } - } - ); - - logger.debug("Hosts after re-ordering are: {}", hosts); + logger.info("Hosts after re-ordering are: [{}].", hosts); } private Integer getHostPriority(Map priorities, Long hostId) { diff --git a/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java index 450f08c46e3..a09867b8ffc 100644 --- a/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java @@ -301,7 +301,7 @@ public class NetworkMigrationManagerImpl implements NetworkMigrationManager { copyOfVpc = _vpcService.createVpc(vpc.getZoneId(), vpcOfferingId, vpc.getAccountId(), vpc.getName(), vpc.getDisplayText(), vpc.getCidr(), vpc.getNetworkDomain(), vpc.getIp4Dns1(), vpc.getIp4Dns2(), - vpc.getIp6Dns1(), vpc.getIp6Dns2(), vpc.isDisplay(), vpc.getPublicMtu(), null, null, null, vpc.useRouterIpAsResolver()); + vpc.getIp6Dns1(), vpc.getIp6Dns2(), vpc.isDisplay(), vpc.getPublicMtu(), null, null, null, vpc.useRouterIpAsResolver(), vpc.getKeepMacAddressOnPublicNic()); copyOfVpcId = copyOfVpc.getId(); //on resume of migration the uuid will be swapped already. So the copy will have the value of the original vpcid. diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index c9884f8c469..d18fd043f69 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -1549,6 +1549,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C DataCenter zone = getAndValidateZone(cmd, pNtwk); + boolean keepMacAddressOnPublicNic = getAndValidateSupportForKeepMacAddressOnPublicNicParameter(cmd.getKeepMacAddressOnPublicNic(), ntwkOff); + _accountMgr.checkAccess(owner, ntwkOff, zone); validateZoneAvailability(caller, zone); @@ -1834,7 +1836,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C Network network = commitNetwork(networkOfferingId, gateway, startIP, endIP, netmask, networkDomain, vlanId, bypassVlanOverlapCheck, name, displayText, caller, physicalNetworkId, zone.getId(), domainId, isDomainSpecific, subdomainAccess, vpcId, startIPv6, endIPv6, ip6Gateway, ip6Cidr, displayNetwork, aclId, secondaryVlanId, privateVlanType, ntwkOff, pNtwk, aclType, owner, cidr, createVlan, - externalId, routerIPv4, routerIPv6, associatedNetwork, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, interfaceMTUs, networkCidrSize); + externalId, routerIPv4, routerIPv6, associatedNetwork, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, interfaceMTUs, networkCidrSize, keepMacAddressOnPublicNic); // retrieve, acquire and associate the correct IP addresses checkAndSetRouterSourceNatIp(owner, cmd, network); @@ -1879,6 +1881,24 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } } + protected boolean getAndValidateSupportForKeepMacAddressOnPublicNicParameter(Boolean keepMacAddressOnPublicNic, NetworkOffering networkOffering) { + if (networkOffering.isForVpc() && keepMacAddressOnPublicNic != null) { + throw new InvalidParameterValueException( + String.format("The [%s] parameter cannot be specified on the creation of VPC tiers.", ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC) + ); + } + + GuestType guestType = networkOffering.getGuestType(); + if (guestType != GuestType.Isolated && keepMacAddressOnPublicNic != null) { + throw new InvalidParameterValueException(String.format( + "The [%s] parameter can only be specified on the creation of [%s] networks.", + ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, GuestType.Isolated + )); + } + + return keepMacAddressOnPublicNic == null || keepMacAddressOnPublicNic; + } + @Override @DB @ActionEvent(eventType = EventTypes.EVENT_NETWORK_CREATE, eventDescription = "creating network") @@ -2282,7 +2302,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C final Boolean displayNetwork, final Long aclId, final String isolatedPvlan, final PVlanType isolatedPvlanType, final NetworkOffering ntwkOff, final PhysicalNetwork pNtwk, final ACLType aclType, final Account ownerFinal, final String cidr, final boolean createVlan, final String externalId, String routerIp, String routerIpv6, final Network associatedNetwork, final String ip4Dns1, final String ip4Dns2, final String ip6Dns1, final String ip6Dns2, Pair vrIfaceMTUs, - final Integer networkCidrSize) throws InsufficientCapacityException, ResourceAllocationException { + final Integer networkCidrSize, final boolean keepMacAddressOnPublicNic) throws InsufficientCapacityException, ResourceAllocationException { try { Network network = Transaction.execute(new TransactionCallbackWithException() { @Override @@ -2348,7 +2368,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } network = _networkMgr.createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, bypassVlanOverlapCheck, networkDomain, owner, sharedDomainId, pNtwk, zoneId, aclType, subdomainAccess, vpcId, ip6Gateway, ip6Cidr, displayNetwork, isolatedPvlan, isolatedPvlanType, externalId, routerIp, routerIpv6, ip4Dns1, ip4Dns2, - ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize); + ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize, keepMacAddressOnPublicNic); } if (createVlan && network != null) { @@ -3156,6 +3176,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C String ip4Dns2 = cmd.getIp4Dns2(); String ip6Dns1 = cmd.getIp6Dns1(); String ip6Dns2 = cmd.getIp6Dns2(); + Boolean keepMacAddressOnPublicNic = cmd.getKeepMacAddressOnPublicNic(); boolean restartNetwork = false; @@ -3214,6 +3235,10 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C network.setUuid(customId); } + if (keepMacAddressOnPublicNic != null) { + network.setKeepMacAddressOnPublicNic(getAndValidateSupportForKeepMacAddressOnPublicNicParameter(keepMacAddressOnPublicNic, offering)); + } + // display flag is not null and has changed if (displayNetwork != null && displayNetwork != network.getDisplayNetwork()) { // Update resource count if it needs to be updated diff --git a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java index 684f196ce9f..56534a8ee7d 100644 --- a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java +++ b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java @@ -737,55 +737,91 @@ public class NetworkHelperImpl implements NetworkHelper { } protected LinkedHashMap> configurePublicNic(final RouterDeploymentDefinition routerDeploymentDefinition, final boolean hasGuestNic) throws InsufficientAddressCapacityException { - final LinkedHashMap> publicConfig = new LinkedHashMap>(3); - - if (routerDeploymentDefinition.isPublicNetwork()) { - logger.debug("Adding NIC for Virtual Router in Public network "); - // if source nat service is supported by the network, get the source - // nat ip address - final NicProfile defaultNic = new NicProfile(); - defaultNic.setDefaultNic(true); - final PublicIp sourceNatIp = routerDeploymentDefinition.getSourceNatIP(); - defaultNic.setIPv4Address(sourceNatIp.getAddress().addr()); - defaultNic.setIPv4Gateway(sourceNatIp.getGateway()); - defaultNic.setIPv4Netmask(sourceNatIp.getNetmask()); - defaultNic.setMacAddress(sourceNatIp.getMacAddress()); - // get broadcast from public network - final Network pubNet = _networkDao.findById(sourceNatIp.getNetworkId()); - if (pubNet.getBroadcastDomainType() == BroadcastDomainType.Vxlan) { - defaultNic.setBroadcastType(BroadcastDomainType.Vxlan); - defaultNic.setBroadcastUri(BroadcastDomainType.Vxlan.toUri(sourceNatIp.getVlanTag())); - defaultNic.setIsolationUri(BroadcastDomainType.Vxlan.toUri(sourceNatIp.getVlanTag())); - } else { - defaultNic.setBroadcastType(BroadcastDomainType.Vlan); - defaultNic.setBroadcastUri(sourceNatIp.getVlanTag() != null ? BroadcastDomainType.Vlan.toUri(sourceNatIp.getVlanTag()) : null); - defaultNic.setIsolationUri(sourceNatIp.getVlanTag() != null ? IsolationType.Vlan.toUri(sourceNatIp.getVlanTag()) : null); - } - - //If guest nic has already been added we will have 2 devices in the list. - if (hasGuestNic) { - defaultNic.setDeviceId(2); - } - - final NetworkOffering publicOffering = _networkModel.getSystemAccountNetworkOfferings(NetworkOffering.SystemPublicNetwork).get(0); - final List publicNetworks = _networkMgr.setupNetwork(s_systemAccount, publicOffering, routerDeploymentDefinition.getPlan(), null, null, false); - final String publicIp = defaultNic.getIPv4Address(); - // We want to use the identical MAC address for RvR on public - // interface if possible - final NicVO peerNic = _nicDao.findByIp4AddressAndNetworkId(publicIp, publicNetworks.get(0).getId()); - if (peerNic != null) { - logger.info("Use same MAC as previous RvR, the MAC is " + peerNic.getMacAddress()); - defaultNic.setMacAddress(peerNic.getMacAddress()); - } - if (routerDeploymentDefinition.getGuestNetwork() != null) { - ipv6Service.updateNicIpv6(defaultNic, routerDeploymentDefinition.getDest().getDataCenter(), routerDeploymentDefinition.getGuestNetwork()); - } - publicConfig.put(publicNetworks.get(0), new ArrayList(Arrays.asList(defaultNic))); + final LinkedHashMap> publicConfig = new LinkedHashMap<>(3); + if (!routerDeploymentDefinition.isPublicNetwork()) { + return publicConfig; } + final PublicIp sourceNatIp = routerDeploymentDefinition.getSourceNatIP(); + final NicProfile defaultNic = new NicProfile(); + configurePublicVrNicBasedOnSourceNatIp(defaultNic, sourceNatIp); + if (hasGuestNic) { + logger.debug("Guest NIC has already been configured, therefore setting device ID of new VR (with source NAT [{}]) public NIC to [2].", sourceNatIp); + defaultNic.setDeviceId(2); + } + + final NetworkOffering publicOffering = _networkModel.getSystemAccountNetworkOfferings(NetworkOffering.SystemPublicNetwork).get(0); + final List publicNetworks = _networkMgr.setupNetwork(s_systemAccount, publicOffering, routerDeploymentDefinition.getPlan(), null, null, false); + + setPublicNicMacAddressSameAsPeerNic(defaultNic, publicNetworks.get(0), routerDeploymentDefinition); + + if (routerDeploymentDefinition.getGuestNetwork() != null) { + ipv6Service.updateNicIpv6(defaultNic, routerDeploymentDefinition.getDest().getDataCenter(), routerDeploymentDefinition.getGuestNetwork()); + } + publicConfig.put(publicNetworks.get(0), List.of(defaultNic)); return publicConfig; } + /** + * Configures the public NIC of a Virtual Router based on the provided source NAT IP. + * @param nic Virtual Router public NIC to be configured. + * @param sourceNatIp Source NAT IP which should be used to configure the Virtual Router's public NIC. + */ + protected void configurePublicVrNicBasedOnSourceNatIp(NicProfile nic, PublicIp sourceNatIp) { + logger.debug("Configuring public NIC of VR with source NAT IP equal to [{}]", sourceNatIp.getAddress().addr()); + nic.setDefaultNic(true); + nic.setIPv4Address(sourceNatIp.getAddress().addr()); + nic.setIPv4Gateway(sourceNatIp.getGateway()); + nic.setIPv4Netmask(sourceNatIp.getNetmask()); + nic.setMacAddress(sourceNatIp.getMacAddress()); + + Network publicNetwork = _networkDao.findById(sourceNatIp.getNetworkId()); + if (publicNetwork.getBroadcastDomainType() == BroadcastDomainType.Vxlan) { + nic.setBroadcastType(BroadcastDomainType.Vxlan); + nic.setBroadcastUri(BroadcastDomainType.Vxlan.toUri(sourceNatIp.getVlanTag())); + nic.setIsolationUri(BroadcastDomainType.Vxlan.toUri(sourceNatIp.getVlanTag())); + } else { + nic.setBroadcastType(BroadcastDomainType.Vlan); + nic.setBroadcastUri(sourceNatIp.getVlanTag() != null ? BroadcastDomainType.Vlan.toUri(sourceNatIp.getVlanTag()) : null); + nic.setIsolationUri(sourceNatIp.getVlanTag() != null ? IsolationType.Vlan.toUri(sourceNatIp.getVlanTag()) : null); + } + } + + /** + * Sets the MAC address of the new VR's public NIC the same as the previous VR's public NIC MAC address if + * {@link RouterDeploymentDefinition#getKeepMacAddressOnPublicNic()} is set to {@code true} and a peer NIC is found; otherwise, + * does nothing. + */ + protected void setPublicNicMacAddressSameAsPeerNic(NicProfile defaultNic, Network publicNetwork, RouterDeploymentDefinition routerDeploymentDefinition) throws InsufficientAddressCapacityException { + String publicIp = defaultNic.getIPv4Address(); + logger.info("Verifying if we will use same MAC address for public NIC of new VR (with source NAT [{}]).", publicIp); + + logger.debug("Searching for peer NIC for public IP [{}] and network [{}].", publicIp, publicNetwork.getUuid()); + NicVO peerNic = _nicDao.findByIp4AddressAndNetworkId(publicIp, publicNetwork.getId()); + if (peerNic == null) { + logger.info("We will not use the same MAC address for public NIC of new VR as we have not found a peer NIC for public IP [{}] and network [{}].", + publicIp, publicNetwork.getUuid()); + return; + } + + logger.info("Found peer NIC [{}] for public IP [{}] and network [{}].", peerNic.getUuid(), publicIp, publicNetwork.getUuid()); + boolean keepMacAddressOnPublicNic = routerDeploymentDefinition.getKeepMacAddressOnPublicNic(); + String macAddressLog = String.format("same MAC address for public NIC of new VR (with source NAT [%s]) as the [keep_mac_address_on_public_nic] property is configured as [%s].", publicIp, keepMacAddressOnPublicNic); + if (keepMacAddressOnPublicNic) { + logger.info("Using {}", macAddressLog); + defaultNic.setMacAddress(peerNic.getMacAddress()); + return; + } + + logger.info("Not using {}", macAddressLog); + boolean fetchNewMacAddress = peerNic.getMacAddress().equals(defaultNic.getMacAddress()); + if (fetchNewMacAddress) { + logger.debug("Fetching new MAC address for public NIC of new VR, since the MAC address of the peer NIC [UUID: {}, MAC address: {}] is equal to the predefined MAC address of the current NIC.", peerNic.getUuid(), peerNic.getMacAddress()); + routerDeploymentDefinition.findSourceNatIP(); + configurePublicVrNicBasedOnSourceNatIp(defaultNic, routerDeploymentDefinition.getSourceNatIP()); + } + } + @Override public LinkedHashMap> configureDefaultNics(final RouterDeploymentDefinition routerDeploymentDefinition) throws ConcurrentOperationException, InsufficientAddressCapacityException { diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index 5b4ab908ca4..c5f36e85655 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -1568,7 +1568,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis @ActionEvent(eventType = EventTypes.EVENT_VPC_CREATE, eventDescription = "creating vpc", create = true) public Vpc createVpc(final long zoneId, final long vpcOffId, final long vpcOwnerId, final String vpcName, final String displayText, final String cidr, String networkDomain, final String ip4Dns1, final String ip4Dns2, final String ip6Dns1, final String ip6Dns2, final Boolean displayVpc, Integer publicMtu, - final Integer cidrSize, final Long asNumber, final List bgpPeerIds, Boolean useVrIpResolver) throws ResourceAllocationException { + final Integer cidrSize, final Long asNumber, final List bgpPeerIds, Boolean useVrIpResolver, boolean keepMacAddressOnPublicNic) throws ResourceAllocationException { final Account caller = CallContext.current().getCallingAccount(); final Account owner = _accountMgr.getAccount(vpcOwnerId); @@ -1668,6 +1668,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis vpc.setPublicMtu(publicMtu); vpc.setDisplay(Boolean.TRUE.equals(displayVpc)); vpc.setUseRouterIpResolver(Boolean.TRUE.equals(useVrIpResolver)); + vpc.setKeepMacAddressOnPublicNic(keepMacAddressOnPublicNic); try (CheckedReservation vpcReservation = new CheckedReservation(owner, ResourceType.vpc, null, null, 1L, reservationDao, _resourceLimitMgr)) { if (vpc.getCidr() == null && cidrSize != null) { @@ -1728,7 +1729,8 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis List bgpPeerIds = (cmd instanceof CreateVPCCmdByAdmin) ? ((CreateVPCCmdByAdmin)cmd).getBgpPeerIds() : null; Vpc vpc = createVpc(cmd.getZoneId(), cmd.getVpcOffering(), cmd.getEntityOwnerId(), cmd.getVpcName(), cmd.getDisplayText(), cmd.getCidr(), cmd.getNetworkDomain(), cmd.getIp4Dns1(), cmd.getIp4Dns2(), cmd.getIp6Dns1(), - cmd.getIp6Dns2(), cmd.isDisplay(), cmd.getPublicMtu(), cmd.getCidrSize(), cmd.getAsNumber(), bgpPeerIds, cmd.getUseVrIpResolver()); + cmd.getIp6Dns2(), cmd.isDisplay(), cmd.getPublicMtu(), cmd.getCidrSize(), cmd.getAsNumber(), bgpPeerIds, + cmd.getUseVrIpResolver(), cmd.getKeepMacAddressOnPublicNic()); String sourceNatIP = cmd.getSourceNatIP(); boolean forNsx = isVpcForProvider(Provider.Nsx, vpc); @@ -1940,12 +1942,14 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis @Override public Vpc updateVpc(UpdateVPCCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException { - return updateVpc(cmd.getId(), cmd.getVpcName(), cmd.getDisplayText(), cmd.getCustomId(), cmd.isDisplayVpc(), cmd.getPublicMtu(), cmd.getSourceNatIP()); + return updateVpc(cmd.getId(), cmd.getVpcName(), cmd.getDisplayText(), cmd.getCustomId(), + cmd.isDisplayVpc(), cmd.getPublicMtu(), cmd.getSourceNatIP(), cmd.getKeepMacAddressOnPublicNic()); } @Override @ActionEvent(eventType = EventTypes.EVENT_VPC_UPDATE, eventDescription = "updating vpc") - public Vpc updateVpc(final long vpcId, final String vpcName, final String displayText, final String customId, final Boolean displayVpc, Integer mtu, String sourceNatIp) throws ResourceUnavailableException, InsufficientCapacityException { + public Vpc updateVpc(final long vpcId, final String vpcName, final String displayText, final String customId, + final Boolean displayVpc, Integer mtu, String sourceNatIp, Boolean keepMacAddressOnPublicNic) throws ResourceUnavailableException, InsufficientCapacityException { final Account caller = CallContext.current().getCallingAccount(); // Verify input parameters @@ -1977,6 +1981,10 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis vpc.setDisplay(displayVpc); } + if (keepMacAddressOnPublicNic != null) { + vpc.setKeepMacAddressOnPublicNic(keepMacAddressOnPublicNic); + } + mtu = validateMtu(vpcToUpdate, mtu); if (mtu != null) { updateMtuOfVpcNetwork(vpcToUpdate, vpc, mtu); diff --git a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java index 729da7caa33..27facb304eb 100644 --- a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java +++ b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java @@ -1042,7 +1042,8 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim ResourceLimitVO limit = _resourceLimitDao.findByOwnerIdAndTypeAndTag(ownerId, ownerType, resourceType, tag); - ActionEventUtils.onActionEvent(caller.getId(), caller.getAccountId(), + Long callingUserId = CallContext.current().getCallingUserId(); + ActionEventUtils.onActionEvent(callingUserId, caller.getAccountId(), caller.getDomainId(), EventTypes.EVENT_RESOURCE_LIMIT_UPDATE, "Resource limit updated. Resource Type: " + resourceType + ", New Value: " + max, ownerResourceId, ownerResourceType.toString()); diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index f0b257670ef..25de45667ef 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -1696,11 +1696,7 @@ public class ManagementServerImpl extends MutualExclusiveIdsManagerBase implemen List suitableHosts = new ArrayList<>(); for (final HostAllocator allocator : hostAllocators) { - if (CollectionUtils.isNotEmpty(compatibleHosts)) { - suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes, compatibleHosts, HostAllocator.RETURN_UPTO_ALL, false); - } else { - suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes, HostAllocator.RETURN_UPTO_ALL, false); - } + suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes, compatibleHosts, HostAllocator.RETURN_UPTO_ALL, false); if (CollectionUtils.isNotEmpty(suitableHosts)) { break; @@ -1709,10 +1705,10 @@ public class ManagementServerImpl extends MutualExclusiveIdsManagerBase implemen _dpMgr.reorderHostsByPriority(plan.getHostPriorities(), suitableHosts); - if (suitableHosts.isEmpty()) { + if (CollectionUtils.isEmpty(suitableHosts)) { logger.warn("No suitable hosts found."); } else { - logger.debug("Hosts having capacity and suitable for migration: {}", suitableHosts); + logger.debug("Hosts having capacity and are suitable for migration: {}", suitableHosts); } // Only list hosts of the same architecture as the source Host in a multi-arch zone diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 2909262849c..1b1a5310e9b 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -4359,9 +4359,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return resourceLimitService.getResourceLimitStorageTags(diskOfferingVO); } - private List reserveStorageResourcesForVm(Account owner, Long diskOfferingId, Long diskSize, List dataDiskInfoList, Long rootDiskOfferingId, ServiceOfferingVO offering, Long rootDiskSize) throws ResourceAllocationException { - List checkedReservations = new ArrayList<>(); - + private void reserveStorageResourcesForVm(List checkedReservations, Account owner, Long diskOfferingId, Long diskSize, List dataDiskInfoList, Long rootDiskOfferingId, ServiceOfferingVO offering, Long rootDiskSize) throws ResourceAllocationException { List rootResourceLimitStorageTags = getResourceLimitStorageTags(rootDiskOfferingId != null ? rootDiskOfferingId : offering.getDiskOfferingId()); CheckedReservation rootVolumeReservation = new CheckedReservation(owner, ResourceType.volume, rootResourceLimitStorageTags, 1L, reservationDao, resourceLimitService); checkedReservations.add(rootVolumeReservation); @@ -4369,12 +4367,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir checkedReservations.add(rootPrimaryStorageReservation); if (diskOfferingId != null) { - List additionalResourceLimitStorageTags = diskOfferingId != null ? getResourceLimitStorageTags(diskOfferingId) : null; + List additionalResourceLimitStorageTags = getResourceLimitStorageTags(diskOfferingId); DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); Long size = verifyAndGetDiskSize(diskOffering, diskSize); - CheckedReservation additionalVolumeReservation = diskOfferingId != null ? new CheckedReservation(owner, ResourceType.volume, additionalResourceLimitStorageTags, 1L, reservationDao, resourceLimitService) : null; + CheckedReservation additionalVolumeReservation = new CheckedReservation(owner, ResourceType.volume, additionalResourceLimitStorageTags, 1L, reservationDao, resourceLimitService); checkedReservations.add(additionalVolumeReservation); - CheckedReservation additionalPrimaryStorageReservation = diskOfferingId != null ? new CheckedReservation(owner, ResourceType.primary_storage, additionalResourceLimitStorageTags, size, reservationDao, resourceLimitService) : null; + CheckedReservation additionalPrimaryStorageReservation = new CheckedReservation(owner, ResourceType.primary_storage, additionalResourceLimitStorageTags, size, reservationDao, resourceLimitService); checkedReservations.add(additionalPrimaryStorageReservation); } @@ -4390,7 +4388,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir checkedReservations.add(additionalPrimaryStorageReservation); } } - return checkedReservations; } private UserVm getUncheckedUserVmResource(DataCenter zone, String hostName, String displayName, Account owner, @@ -4402,10 +4399,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String vmType, VMTemplateVO template, HypervisorType hypervisorType, long accountId, ServiceOfferingVO offering, boolean isIso, Long rootDiskOfferingId, long volumesSize, Volume volume, Snapshot snapshot) throws ResourceAllocationException { - List checkedReservations = new ArrayList<>(); + List checkedReservations = new ArrayList<>(); try { - checkedReservations = reserveStorageResourcesForVm(owner, diskOfferingId, diskSize, dataDiskInfoList, rootDiskOfferingId, offering, volumesSize); + reserveStorageResourcesForVm(checkedReservations, owner, diskOfferingId, diskSize, dataDiskInfoList, rootDiskOfferingId, offering, volumesSize); // verify security group ids if (securityGroupIdList != null) { @@ -4701,14 +4698,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir logger.error("error during resource reservation and allocation", e); throw new CloudRuntimeException(e); } finally { - for (CheckedReservation checkedReservation : checkedReservations) { - try { - checkedReservation.close(); - } catch (Exception e) { - logger.error("error during resource reservation and allocation", e); - throw new CloudRuntimeException(e); - } - } + ReservationHelper.closeAll(checkedReservations); } } diff --git a/server/src/main/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinition.java b/server/src/main/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinition.java index 4187586736d..77cd7890e41 100644 --- a/server/src/main/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinition.java +++ b/server/src/main/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinition.java @@ -394,7 +394,7 @@ public class RouterDeploymentDefinition { } } - protected void findSourceNatIP() throws InsufficientAddressCapacityException, ConcurrentOperationException { + public void findSourceNatIP() throws InsufficientAddressCapacityException, ConcurrentOperationException { sourceNatIp = null; DataCenter zone = dest.getDataCenter(); Long zoneId = null; @@ -548,4 +548,8 @@ public class RouterDeploymentDefinition { return needReset; } + + public boolean getKeepMacAddressOnPublicNic() { + return guestNetwork == null || guestNetwork.getKeepMacAddressOnPublicNic(); + } } diff --git a/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java b/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java index 063565ebf46..9e432265f2a 100644 --- a/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java +++ b/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java @@ -117,7 +117,7 @@ public class VpcRouterDeploymentDefinition extends RouterDeploymentDefinition { } @Override - protected void findSourceNatIP() throws InsufficientAddressCapacityException, ConcurrentOperationException { + public void findSourceNatIP() throws InsufficientAddressCapacityException, ConcurrentOperationException { sourceNatIp = null; DataCenter zone = dest.getDataCenter(); Long zoneId = null; @@ -219,4 +219,9 @@ public class VpcRouterDeploymentDefinition extends RouterDeploymentDefinition { public boolean isRollingRestart() { return vpc.isRollingRestart(); } + + @Override + public boolean getKeepMacAddressOnPublicNic() { + return vpc == null || vpc.getKeepMacAddressOnPublicNic(); + } } diff --git a/server/src/main/resources/META-INF/cloudstack/server-allocator/spring-server-allocator-context.xml b/server/src/main/resources/META-INF/cloudstack/server-allocator/spring-server-allocator-context.xml index d3781649845..28b96b8d194 100644 --- a/server/src/main/resources/META-INF/cloudstack/server-allocator/spring-server-allocator-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/server-allocator/spring-server-allocator-context.xml @@ -34,6 +34,9 @@ + + diff --git a/server/src/test/java/com/cloud/agent/manager/allocator/impl/BaseAllocatorTest.java b/server/src/test/java/com/cloud/agent/manager/allocator/impl/BaseAllocatorTest.java new file mode 100644 index 00000000000..ddd1e3959d5 --- /dev/null +++ b/server/src/test/java/com/cloud/agent/manager/allocator/impl/BaseAllocatorTest.java @@ -0,0 +1,219 @@ +// 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 com.cloud.agent.manager.allocator.impl; + +import com.cloud.capacity.CapacityManager; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.offering.ServiceOffering; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.utils.Pair; +import com.cloud.vm.VirtualMachineProfile; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@RunWith(MockitoJUnitRunner.class) +public class BaseAllocatorTest { + @Mock + HostDao hostDaoMock; + + @Mock + CapacityManager capacityManagerMock; + + @InjectMocks + @Spy + BaseAllocator baseAllocator = new MockBaseAllocator(); + + private final Host.Type type = Host.Type.Routing; + + private final Long clusterId = 1L; + + private final Long podId = 2L; + + private final Long dcId = 3L; + + private final HostVO host1 = Mockito.mock(HostVO.class); + + private final HostVO host2 = Mockito.mock(HostVO.class); + + private final HostVO host3 = Mockito.mock(HostVO.class); + + private final ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class); + + private final String hostTag = "hostTag"; + + @Test + public void retainHostsMatchingServiceOfferingAndTemplateTagsTestHasServiceOfferingTagShouldRetainHostsWithServiceOfferingTag() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List hostsWithMathingTags = new ArrayList<>(Arrays.asList(host1, host3)); + String hostTagOnTemplate = "hostTagOnTemplate"; + String hostTagOnOffering = null; + + Mockito.doReturn(hostsWithMathingTags).when(hostDaoMock).listByHostTag(type, clusterId, podId, dcId, hostTagOnTemplate); + baseAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(suitableHosts, type, dcId, podId, clusterId, hostTagOnOffering, hostTagOnTemplate); + + Assert.assertEquals(2, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host3, suitableHosts.get(1)); + } + + @Test + public void retainHostsMatchingServiceOfferingAndTemplateTagsTestHasServiceOfferingTagAndHasHostTagOnTemplateShouldRetainHostsWithServiceOfferingTagAndTemplateTag() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List hostsWithMathingServiceTags = new ArrayList<>(Arrays.asList(host1, host3)); + List hostsWithMathingTemplateTags = new ArrayList<>(Arrays.asList(host1, host2)); + String hostTagOnTemplate = "hostTagOnTemplate"; + String hostTagOnOffering = "hostTagOnOffering"; + + Mockito.doReturn(hostsWithMathingTemplateTags).when(hostDaoMock).listByHostTag(type, clusterId, podId, dcId, hostTagOnTemplate); + Mockito.doReturn(hostsWithMathingServiceTags).when(hostDaoMock).listByHostTag(type, clusterId, podId, dcId, hostTagOnOffering); + baseAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(suitableHosts, type, dcId, podId, clusterId, hostTagOnOffering, hostTagOnTemplate); + + Assert.assertEquals(1, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + } + + @Test + public void retainHostsMatchingServiceOfferingAndTemplateTagsTestHasHostTagOnTemplateShouldRetainHostsWithTemplateTag() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List hostsWithMathingServiceTags = new ArrayList<>(Arrays.asList(host1, host3)); + String hostTagOnTemplate = null; + String hostTagOnOffering = "hostTagOnOffering"; + + Mockito.doReturn(hostsWithMathingServiceTags).when(hostDaoMock).listByHostTag(type, clusterId, podId, dcId, hostTagOnOffering); + baseAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(suitableHosts, type, dcId, podId, clusterId, hostTagOnOffering, hostTagOnTemplate); + + Assert.assertEquals(2, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host3, suitableHosts.get(1)); + } + + @Test + public void retainHostsMatchingServiceOfferingAndTemplateTagsTestNoServiceTagAndNoTemplateTagShouldHaveAllSuitableHosts() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + String hostTagOnTemplate = null; + String hostTagOnOffering = null; + + baseAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(suitableHosts, type, dcId, podId, clusterId, hostTagOnOffering, hostTagOnTemplate); + + Assert.assertEquals(3, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host2, suitableHosts.get(1)); + Assert.assertEquals(host3, suitableHosts.get(2)); + } + + @Test + public void addHostsBasedOnTagRulesTestHostsWithTagRuleIsEmptyShouldNotAddToSuitableHosts() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List emptyList = new ArrayList<>(); + + Mockito.doReturn(emptyList).when(hostDaoMock).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.anyString()); + baseAllocator.addHostsBasedOnTagRules(hostTag, suitableHosts); + + Assert.assertEquals(2, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host2, suitableHosts.get(1)); + } + + @Test + public void addHostsBasedOnTagRulesTestHostsWithTagRuleIsNotEmptyShouldAddToSuitableHosts() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsMatchingRuleTag = new ArrayList<>(Arrays.asList(host3)); + + Mockito.doReturn(hostsMatchingRuleTag).when(hostDaoMock).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.anyString()); + baseAllocator.addHostsBasedOnTagRules(hostTag, suitableHosts); + + Assert.assertEquals(3, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host2, suitableHosts.get(1)); + Assert.assertEquals(host3, suitableHosts.get(2)); + } + + @Test + public void hostHasCpuCapabilityAndCapacityTestHostHasCpuCapabilityAndCpuCapacityShouldReturnTrue() { + Boolean hasCpuCapability = true; + Boolean hasCpuCapacity = true; + Pair pair = new Pair<>(hasCpuCapability, hasCpuCapacity); + + Mockito.doReturn(pair).when(capacityManagerMock).checkIfHostHasCpuCapabilityAndCapacity(Mockito.any(Host.class), Mockito.any(ServiceOffering.class), Mockito.anyBoolean()); + boolean result = baseAllocator.hostHasCpuCapabilityAndCapacity(true, serviceOffering, host1); + + Assert.assertTrue(result); + } + + @Test + public void hostHasCpuCapabilityAndCapacityTestHostHasCpuCapabilityButNoCpuCapacityShouldReturnFalse() { + Boolean hasCpuCapability = true; + Boolean hasCpuCapacity = false; + Pair pair = new Pair<>(hasCpuCapability, hasCpuCapacity); + + Mockito.doReturn(pair).when(capacityManagerMock).checkIfHostHasCpuCapabilityAndCapacity(Mockito.any(Host.class), Mockito.any(ServiceOffering.class), Mockito.anyBoolean()); + boolean result = baseAllocator.hostHasCpuCapabilityAndCapacity(true, serviceOffering, host1); + + Assert.assertFalse(result); + } + + @Test + public void hostHasCpuCapabilityAndCapacityTestHostDoesNotHaveCpuCapabilityButHasCpuCapacityShouldReturnFalse() { + Boolean hasCpuCapability = false; + Boolean hasCpuCapacity = true; + Pair pair = new Pair<>(hasCpuCapability, hasCpuCapacity); + + Mockito.doReturn(pair).when(capacityManagerMock).checkIfHostHasCpuCapabilityAndCapacity(Mockito.any(Host.class), Mockito.any(ServiceOffering.class), Mockito.anyBoolean()); + boolean result = baseAllocator.hostHasCpuCapabilityAndCapacity(true, serviceOffering, host1); + + Assert.assertFalse(result); + } + + @Test + public void hostHasCpuCapabilityAndCapacityTestHostDoesNotHaveCpuCapabilityAndCpuCapacityShouldReturnFalse() { + Boolean hasCpuCapability = false; + Boolean hasCpuCapacity = false; + Pair pair = new Pair<>(hasCpuCapability, hasCpuCapacity); + + Mockito.doReturn(pair).when(capacityManagerMock).checkIfHostHasCpuCapabilityAndCapacity(Mockito.any(Host.class), Mockito.any(ServiceOffering.class), Mockito.anyBoolean()); + boolean result = baseAllocator.hostHasCpuCapabilityAndCapacity(true, serviceOffering, host1); + + Assert.assertFalse(result); + } + + class MockBaseAllocator extends BaseAllocator { + + @Override + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Host.Type type, DeploymentPlanner.ExcludeList avoid, int returnUpTo) { + return null; + } + + @Override + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Host.Type type, DeploymentPlanner.ExcludeList avoid, List hosts, int returnUpTo, boolean considerReservedCapacity) { + return null; + } + } +} diff --git a/server/src/test/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocatorTest.java b/server/src/test/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocatorTest.java index 0d6a6fa8ff9..00cac5bbd52 100644 --- a/server/src/test/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocatorTest.java +++ b/server/src/test/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocatorTest.java @@ -18,214 +18,596 @@ package com.cloud.agent.manager.allocator.impl; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.cloud.agent.manager.allocator.HostAllocator; import com.cloud.capacity.CapacityManager; import com.cloud.capacity.CapacityVO; import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner; import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; import com.cloud.offering.ServiceOffering; import com.cloud.resource.ResourceManager; +import com.cloud.service.ServiceOfferingDetailsVO; +import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDetailsDao; +import com.cloud.storage.VMTemplateVO; import com.cloud.user.Account; import com.cloud.utils.Pair; +import com.cloud.vm.VMInstanceDetailVO; import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.VMInstanceDetailsDao; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.junit.Assert; -import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - - +@RunWith(MockitoJUnitRunner.class) public class FirstFitAllocatorTest { - private static final double TOLERANCE = 0.0001; - private FirstFitAllocator allocator; - private CapacityManager capacityMgr; - private ServiceOfferingDetailsDao offeringDetailsDao; - private ResourceManager resourceMgr; + private static final double TOLERANCE = 0.0001; - private DeploymentPlan plan; - private ServiceOffering offering; - private DeploymentPlanner.ExcludeList avoid; - private Account account; + @Mock + HostDao hostDaoMock; - private Host host1; - private Host host2; - private VirtualMachineProfile vmProfile; - ConfigurationDao configDao; + @Mock + ResourceManager resourceManagerMock; - @Before - public void setUp() { - allocator = new FirstFitAllocator(); - capacityMgr = mock(CapacityManager.class); - offeringDetailsDao = mock(ServiceOfferingDetailsDao.class); - resourceMgr = mock(ResourceManager.class); - configDao = mock(ConfigurationDao.class); + @Mock + VMInstanceDetailsDao userVmDetailsDaoMock; - allocator._capacityMgr = capacityMgr; - allocator._serviceOfferingDetailsDao = offeringDetailsDao; - allocator._resourceMgr = resourceMgr; - allocator._configDao = configDao; + @Mock + ServiceOfferingDetailsDao serviceOfferingDetailsDao; - plan = mock(DeploymentPlan.class); - offering = mock(ServiceOffering.class); - avoid = mock(DeploymentPlanner.ExcludeList.class); - account = mock(Account.class); + @Mock + CapacityManager capacityMgr; - host1 = mock(Host.class); - host2 = mock(Host.class); + @Mock + ConfigurationDao configDao; - vmProfile = mock(VirtualMachineProfile.class); - when(vmProfile.getId()).thenReturn(1L); + @Spy + @InjectMocks + FirstFitAllocator firstFitAllocatorSpy; - when(plan.getDataCenterId()).thenReturn(1L); - when(offering.getCpu()).thenReturn(2); - when(offering.getSpeed()).thenReturn(1000); - when(offering.getRamSize()).thenReturn(2048); - when(offering.getId()).thenReturn(123L); - when(offering.getHostTag()).thenReturn(null); - when(offering.getVgpuProfileId()).thenReturn(null); - } + private final Host.Type type = Host.Type.Routing; - @Test - public void testConfigure() throws Exception { - when(configDao.getConfiguration(anyMap())).thenReturn(new HashMap<>()); - assertTrue(allocator._checkHvm); - assertTrue(allocator.configure("test", new HashMap<>())); - } + private final Long clusterId = 1L; - @Test - public void testAllocateTo_SuccessfulMatch() { - List inputHosts = Arrays.asList(host1, host2); + private final Long podId = 2L; - // All hosts are allowed - when(avoid.shouldAvoid(host1)).thenReturn(false); - when(avoid.shouldAvoid(host2)).thenReturn(false); + private final Long dcId = 3L; - // No GPU requirement - when(offeringDetailsDao.findDetail(eq(123L), anyString())).thenReturn(null); + private final List emptyList = new ArrayList<>(); - // CPU capability and capacity is met - when(capacityMgr.checkIfHostReachMaxGuestLimit(any())).thenReturn(false); - when(capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(eq(host1), eq(offering), eq(true))) - .thenReturn(new Pair<>(true, true)); - when(capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(eq(host2), eq(offering), eq(true))) - .thenReturn(new Pair<>(true, false)); + private final String hostTag = "hostTag"; + private final String templateTag = "templateTag"; - when(resourceMgr.isGPUDeviceAvailable(offering, host1, vmProfile.getId())).thenReturn(true); - when(resourceMgr.isGPUDeviceAvailable(offering, host2, vmProfile.getId())).thenReturn(true); + private final HostVO host1 = mock(HostVO.class); - List result = allocator.allocateTo(vmProfile, plan, offering, null, avoid, inputHosts, 2, true, account); + private final HostVO host2 = mock(HostVO.class); - // Only host1 should be returned - assertEquals(1, result.size()); - assertTrue(result.contains(host1)); - assertFalse(result.contains(host2)); - } + private final HostVO host3 = mock(HostVO.class); - @Test - public void testAllocateTo_AvoidSetAndGuestLimit() { - List inputHosts = Arrays.asList(host1, host2); + private final ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class); - when(avoid.shouldAvoid(host1)).thenReturn(true); // Avoided - when(avoid.shouldAvoid(host2)).thenReturn(false); + private final DeploymentPlanner.ExcludeList excludeList = mock(DeploymentPlanner.ExcludeList.class); - when(capacityMgr.checkIfHostReachMaxGuestLimit(host2)).thenReturn(true); // Reached limit + private final VirtualMachineProfile virtualMachineProfile = mock(VirtualMachineProfile.class); - List result = allocator.allocateTo(vmProfile, plan, offering, null, avoid, inputHosts, 2, true, account); + private final VMTemplateVO vmTemplateVO = mock(VMTemplateVO.class); - assertTrue(result.isEmpty()); - } + private final Account account = mock(Account.class); - @Test - public void testAllocateTo_GPUNotAvailable() { - List inputHosts = Arrays.asList(host1); - when(avoid.shouldAvoid(host1)).thenReturn(false); + private final DeploymentPlan deploymentPlan = mock(DeploymentPlan.class); - // GPU required but not available - var vgpuDetail = mock(com.cloud.service.ServiceOfferingDetailsVO.class); - var pciDetail = mock(com.cloud.service.ServiceOfferingDetailsVO.class); - when(offeringDetailsDao.findDetail(eq(123L), eq("vgpuType"))).thenReturn(vgpuDetail); - when(offeringDetailsDao.findDetail(eq(123L), eq("pciDevice"))).thenReturn(pciDetail); - when(pciDetail.getValue()).thenReturn("NVIDIA"); - when(vgpuDetail.getValue()).thenReturn("GRID"); + private final DeploymentPlanner.ExcludeList avoid = mock(DeploymentPlanner.ExcludeList.class); - when(resourceMgr.isGPUDeviceAvailable(offering, host1, vmProfile.getId())).thenReturn(false); + private final ServiceOffering offering = mock(ServiceOffering.class); - List result = allocator.allocateTo(vmProfile, plan, offering, null, avoid, inputHosts, 1, true, account); + private final boolean considerReservedCapacity = true; - assertTrue(result.isEmpty()); - } + @Test + public void testConfigure() throws Exception { + when(configDao.getConfiguration(Mockito.anyMap())).thenReturn(new HashMap<>()); + Assert.assertTrue(firstFitAllocatorSpy._checkHvm); + Assert.assertTrue(firstFitAllocatorSpy.configure("test", new HashMap<>())); + } - @Test - public void testHostByCombinedCapacityOrder() { - // Test scenario 1: Default capacity usage (0.5 weight) - List mockCapacity = getHostCapacities(); - Map hostByCombinedCapacity = FirstFitAllocator.getHostByCombinedCapacities(mockCapacity, 0.5); + @Test + public void testAllocateTo_SuccessfulMatch() { + List inputHosts = Arrays.asList(host1, host2); - // Verify host ordering and capacity values - Long firstHostId = hostByCombinedCapacity.keySet().iterator().next(); - Assert.assertEquals("Host with ID 1 should be first in ordering", Long.valueOf(1L), firstHostId); - Assert.assertEquals("Host 1 combined capacity should match expected value", - 0.9609375, hostByCombinedCapacity.get(1L), TOLERANCE); - Assert.assertEquals("Host 2 combined capacity should match expected value", - 0.9296875, hostByCombinedCapacity.get(2L), TOLERANCE); + // All hosts are allowed + when(avoid.shouldAvoid(host1)).thenReturn(false); + when(avoid.shouldAvoid(host2)).thenReturn(false); - // Test scenario 2: Modified capacity usage (0.7 weight) - when(mockCapacity.get(0).getUsedCapacity()).thenReturn(1500L); - hostByCombinedCapacity = FirstFitAllocator.getHostByCombinedCapacities(mockCapacity, 0.7); + // CPU capability and capacity is met + when(capacityMgr.checkIfHostReachMaxGuestLimit(Mockito.any(Host.class))).thenReturn(false); + when(capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(host1, offering, true)) + .thenReturn(new Pair<>(true, true)); + when(capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(host2, offering,true)) + .thenReturn(new Pair<>(true, false)); - // Verify new ordering after capacity change - firstHostId = hostByCombinedCapacity.keySet().iterator().next(); - Assert.assertEquals("Host with ID 2 should be first after capacity change", Long.valueOf(2L), firstHostId); - Assert.assertEquals("Host 2 combined capacity should match expected value after change", - 0.9515625, hostByCombinedCapacity.get(2L), TOLERANCE); - Assert.assertEquals("Host 1 combined capacity should match expected value after change", - 0.9484375, hostByCombinedCapacity.get(1L), TOLERANCE); - } + when(resourceManagerMock.isGPUDeviceAvailable(offering, host1, virtualMachineProfile.getId())).thenReturn(true); + when(resourceManagerMock.isGPUDeviceAvailable(offering, host2, virtualMachineProfile.getId())).thenReturn(true); - List getHostCapacities() { - CapacityVO cpuCapacity1 = mock(CapacityVO.class); - when(cpuCapacity1.getHostOrPoolId()).thenReturn(1L); - when(cpuCapacity1.getTotalCapacity()).thenReturn(32000L); - when(cpuCapacity1.getReservedCapacity()).thenReturn(0L); - when(cpuCapacity1.getUsedCapacity()).thenReturn(500L); - when(cpuCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_CPU); + List result = firstFitAllocatorSpy.allocateTo(virtualMachineProfile, deploymentPlan, offering, null, avoid, inputHosts, 2, true, account); - CapacityVO cpuCapacity2 = mock(CapacityVO.class); - when(cpuCapacity2.getHostOrPoolId()).thenReturn(2L); - when(cpuCapacity2.getTotalCapacity()).thenReturn(32000L); - when(cpuCapacity2.getReservedCapacity()).thenReturn(0L); - when(cpuCapacity2.getUsedCapacity()).thenReturn(500L); - when(cpuCapacity2.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_CPU); + // Only host1 should be returned + Assert.assertEquals(1, result.size()); + Assert.assertTrue(result.contains(host1)); + Assert.assertFalse(result.contains(host2)); + } - CapacityVO memCapacity1 = mock(CapacityVO.class); - when(memCapacity1.getHostOrPoolId()).thenReturn(1L); - when(memCapacity1.getTotalCapacity()).thenReturn(8589934592L); - when(memCapacity1.getReservedCapacity()).thenReturn(0L); - when(memCapacity1.getUsedCapacity()).thenReturn(536870912L); - when(memCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_MEMORY); + @Test + public void testAllocateTo_AvoidSetAndGuestLimit() { + List inputHosts = Arrays.asList(host1, host2); - CapacityVO memCapacity2 = mock(CapacityVO.class); - when(memCapacity2.getHostOrPoolId()).thenReturn(2L); - when(memCapacity2.getTotalCapacity()).thenReturn(8589934592L); - when(memCapacity2.getReservedCapacity()).thenReturn(0L); - when(memCapacity2.getUsedCapacity()).thenReturn(1073741824L); - when(memCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_MEMORY); - return Arrays.asList(cpuCapacity1, memCapacity1, cpuCapacity2, memCapacity2); - } + when(avoid.shouldAvoid(host1)).thenReturn(true); // Avoided + when(avoid.shouldAvoid(host2)).thenReturn(false); + + when(capacityMgr.checkIfHostReachMaxGuestLimit(host2)).thenReturn(true); // Reached limit + + List result = firstFitAllocatorSpy.allocateTo(virtualMachineProfile, deploymentPlan, offering, null, avoid, inputHosts, 2, true, account); + + Assert.assertTrue(result.isEmpty()); + } + + @Test + public void testAllocateTo_GPUNotAvailable() { + List inputHosts = Arrays.asList(host1); + when(avoid.shouldAvoid(host1)).thenReturn(false); + when(resourceManagerMock.isGPUDeviceAvailable(offering, host1, virtualMachineProfile.getId())).thenReturn(false); + + List result = firstFitAllocatorSpy.allocateTo(virtualMachineProfile, deploymentPlan, offering, null, avoid, inputHosts, 1, true, account); + + Assert.assertTrue(result.isEmpty()); + } + + @Test + public void allocateToTestHostTypeStorageShouldReturnNull() { + List suitableHosts = firstFitAllocatorSpy.allocateTo(virtualMachineProfile, deploymentPlan, Host.Type.Storage, excludeList, null, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity); + + Assert.assertNull(suitableHosts); + } + + @Test + public void allocateToTestSuitableHostsEmptyShouldReturnNull() { + Mockito.doReturn(serviceOffering).when(virtualMachineProfile).getServiceOffering(); + Mockito.doReturn(vmTemplateVO).when(virtualMachineProfile).getTemplate(); + Mockito.doReturn(account).when(virtualMachineProfile).getOwner(); + Mockito.doReturn(hostTag).when(serviceOffering).getHostTag(); + Mockito.doReturn(templateTag).when(vmTemplateVO).getTemplateTag(); + Mockito.doReturn(emptyList).when(firstFitAllocatorSpy).retrieveHosts(Mockito.any(VirtualMachineProfile.class), Mockito.any(Host.Type.class), Mockito.nullable(List.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString()); + List suitableHosts = firstFitAllocatorSpy.allocateTo(virtualMachineProfile, deploymentPlan, type, excludeList, null, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity); + + Assert.assertNull(suitableHosts); + } + + @Test + public void allocateToTestSuitableHostsNotEmptyShouldCallAllocateToMethod() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2)); + + Mockito.doReturn(serviceOffering).when(virtualMachineProfile).getServiceOffering(); + Mockito.doReturn(vmTemplateVO).when(virtualMachineProfile).getTemplate(); + Mockito.doReturn(account).when(virtualMachineProfile).getOwner(); + Mockito.doReturn(hostTag).when(serviceOffering).getHostTag(); + Mockito.doReturn(templateTag).when(vmTemplateVO).getTemplateTag(); + Mockito.doReturn(hosts).when(firstFitAllocatorSpy).retrieveHosts(Mockito.any(VirtualMachineProfile.class), Mockito.any(Host.Type.class), Mockito.nullable(List.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString()); + Mockito.doReturn(hosts).when(firstFitAllocatorSpy).allocateTo(Mockito.any(VirtualMachineProfile.class), Mockito.any(DeploymentPlan.class), Mockito.any(ServiceOffering.class), Mockito.any(VMTemplateVO.class), Mockito.any(DeploymentPlanner.ExcludeList.class), Mockito.anyList(), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.any(Account.class)); + Mockito.doNothing().when(firstFitAllocatorSpy).addHostsToAvoidSet(Mockito.any(Host.Type.class), Mockito.any(DeploymentPlanner.ExcludeList.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyList()); + List suitableHosts = firstFitAllocatorSpy.allocateTo(virtualMachineProfile, deploymentPlan, type, excludeList, null, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity); + + Mockito.verify(firstFitAllocatorSpy, Mockito.times(1)).allocateTo(virtualMachineProfile, deploymentPlan, serviceOffering, vmTemplateVO, excludeList, hosts, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity, account); + Assert.assertEquals(2, suitableHosts.size()); + } + + @Test + public void allocateToTestProvidedHostsNotNullShouldCallAddHostsToAvoidSetMethod() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2)); + + Mockito.doReturn(serviceOffering).when(virtualMachineProfile).getServiceOffering(); + Mockito.doReturn(vmTemplateVO).when(virtualMachineProfile).getTemplate(); + Mockito.doReturn(account).when(virtualMachineProfile).getOwner(); + Mockito.doReturn(hostTag).when(serviceOffering).getHostTag(); + Mockito.doReturn(templateTag).when(vmTemplateVO).getTemplateTag(); + Mockito.doReturn(hosts).when(firstFitAllocatorSpy).retrieveHosts(Mockito.any(VirtualMachineProfile.class), Mockito.any(Host.Type.class), Mockito.nullable(List.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString()); + Mockito.doReturn(hosts).when(firstFitAllocatorSpy).allocateTo(Mockito.any(VirtualMachineProfile.class), Mockito.any(DeploymentPlan.class), Mockito.any(ServiceOffering.class), Mockito.any(VMTemplateVO.class), Mockito.any(DeploymentPlanner.ExcludeList.class), Mockito.anyList(), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.any(Account.class)); + Mockito.doNothing().when(firstFitAllocatorSpy).addHostsToAvoidSet(Mockito.any(Host.Type.class), Mockito.any(DeploymentPlanner.ExcludeList.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyList()); + firstFitAllocatorSpy.allocateTo(virtualMachineProfile, deploymentPlan, type, excludeList, null, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity); + + Mockito.verify(firstFitAllocatorSpy, Mockito.times(1)).addHostsToAvoidSet(Mockito.any(Host.Type.class), Mockito.any(DeploymentPlanner.ExcludeList.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyList()); + } + + @Test + public void retrieveHostsTestHostsToFilterIsNullAndHaTagNotNullShouldReturnOnlyHostsWithHaTag() { + List allUpAndEnabledHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List hostsWithHaTag = new ArrayList<>(Arrays.asList(host1, host2)); + String hostVmTag = "haVmTag"; + + Mockito.doReturn(hostVmTag).when(virtualMachineProfile).getParameter(Mockito.any(VirtualMachineProfile.Param.class)); + Mockito.doReturn(allUpAndEnabledHosts).when(resourceManagerMock).listAllUpAndEnabledHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hostsWithHaTag).when(hostDaoMock).listByHostTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + Mockito.doNothing().when(firstFitAllocatorSpy).filterHostsWithUefiEnabled(Mockito.any(Host.Type.class), Mockito.any(VirtualMachineProfile.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyList()); + Mockito.doNothing().when(firstFitAllocatorSpy).addHostsBasedOnTagRules(Mockito.anyString(), Mockito.anyList()); + List resultHosts = firstFitAllocatorSpy.retrieveHosts(virtualMachineProfile, type, emptyList, clusterId, podId, dcId, hostTag, templateTag); + + Assert.assertEquals(2, resultHosts.size()); + Assert.assertEquals(host1, resultHosts.get(0)); + Assert.assertEquals(host2, resultHosts.get(1)); + } + + @Test + public void retrieveHostsTestHostsToFilterIsNotNullAndHaTagNotNullShouldReturnOnlyHostsToFilterWithHaTag() { + List hostsToFilter = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List hostsWithHaTag = new ArrayList<>(Arrays.asList(host1, host2)); + String hostVmTag = "haVmTag"; + + Mockito.doReturn(hostVmTag).when(virtualMachineProfile).getParameter(Mockito.any(VirtualMachineProfile.Param.class)); + Mockito.doReturn(hostsWithHaTag).when(hostDaoMock).listByHostTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + Mockito.doNothing().when(firstFitAllocatorSpy).filterHostsWithUefiEnabled(Mockito.any(Host.Type.class), Mockito.any(VirtualMachineProfile.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyList()); + Mockito.doNothing().when(firstFitAllocatorSpy).addHostsBasedOnTagRules(Mockito.anyString(), Mockito.anyList()); + List resultHosts = firstFitAllocatorSpy.retrieveHosts(virtualMachineProfile, type, hostsToFilter, clusterId, podId, dcId, hostTag, templateTag); + + Assert.assertEquals(2, resultHosts.size()); + Assert.assertEquals(host1, resultHosts.get(0)); + Assert.assertEquals(host2, resultHosts.get(1)); + } + + @Test + public void retrieveHostsTestHostsToFilterIsNullAndNoHaTagAndNoHostTagShouldReturnOnlyAllUpAndEnabledNonHaHosts() { + List allUpAndEnabledHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List upAndEnabledHostsWithNoHa = new ArrayList<>(Arrays.asList(host1, host2)); + + Mockito.doReturn(allUpAndEnabledHosts).when(resourceManagerMock).listAllUpAndEnabledHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(upAndEnabledHostsWithNoHa).when(resourceManagerMock).listAllUpAndEnabledNonHAHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doNothing().when(firstFitAllocatorSpy).filterHostsWithUefiEnabled(Mockito.any(Host.Type.class), Mockito.any(VirtualMachineProfile.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyList()); + Mockito.doNothing().when(firstFitAllocatorSpy).addHostsBasedOnTagRules(Mockito.nullable(String.class), Mockito.anyList()); + List resultHosts = firstFitAllocatorSpy.retrieveHosts(virtualMachineProfile, type, emptyList, clusterId, podId, dcId, null, null); + + Assert.assertEquals(2, resultHosts.size()); + Assert.assertEquals(host1, resultHosts.get(0)); + Assert.assertEquals(host2, resultHosts.get(1)); + } + + @Test + public void retrieveHostsTestHostsToFilterIsNullAndNoHaTagWithHostTagShouldCallRetainHostsMatchingServiceOfferingAndTemplateTags() { + List allUpAndEnabledHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + + Mockito.doReturn(allUpAndEnabledHosts).when(resourceManagerMock).listAllUpAndEnabledHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doNothing().when(firstFitAllocatorSpy).retainHostsMatchingServiceOfferingAndTemplateTags(Mockito.anyList(), Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString()); + Mockito.doNothing().when(firstFitAllocatorSpy).filterHostsWithUefiEnabled(Mockito.any(Host.Type.class), Mockito.any(VirtualMachineProfile.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyList()); + Mockito.doNothing().when(firstFitAllocatorSpy).addHostsBasedOnTagRules(Mockito.anyString(), Mockito.anyList()); + firstFitAllocatorSpy.retrieveHosts(virtualMachineProfile, type, emptyList, clusterId, podId, dcId, hostTag, templateTag); + + Mockito.verify(firstFitAllocatorSpy, Mockito.times(1)).retainHostsMatchingServiceOfferingAndTemplateTags(Mockito.anyList(), Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString()); + } + + @Test + public void addHostsToAvoidSetTestAllHostsWereConsideredForAllocationShouldNotAddAnyHostToTheAvoidSet() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + + Mockito.doReturn(suitableHosts).when(hostDaoMock).listAllUpAndEnabledNonHAHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.nullable(String.class)); + firstFitAllocatorSpy.addHostsToAvoidSet(type, excludeList, clusterId, podId, dcId, suitableHosts); + + Assert.assertTrue(excludeList.getHostsToAvoid().isEmpty()); + } + + @Test + public void addHostsToAvoidSetTestNotAllHostsWereConsideredForAllocationShouldAddHostToTheAvoidSet() { + List allUpAndEnabledNonHAHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List consideredHosts = new ArrayList<>(Arrays.asList(host2, host3)); + + Mockito.doReturn(1L).when(host1).getId(); + Mockito.doCallRealMethod().when(excludeList).addHost(Mockito.anyLong()); + Mockito.doCallRealMethod().when(excludeList).getHostsToAvoid(); + Mockito.doReturn(allUpAndEnabledNonHAHosts).when(hostDaoMock).listAllUpAndEnabledNonHAHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.nullable(String.class)); + firstFitAllocatorSpy.addHostsToAvoidSet(type, excludeList, clusterId, podId, dcId, consideredHosts); + + Assert.assertEquals(1, excludeList.getHostsToAvoid().size()); + Assert.assertTrue(excludeList.getHostsToAvoid().contains(1L)); + } + + @Test + public void filterHostsWithUefiEnabledTestNoDetailWithUefiShouldNotFilterAnyHost() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + VMInstanceDetailVO userVmDetailVO = null; + + Mockito.doReturn(userVmDetailVO).when(userVmDetailsDaoMock).findDetail(Mockito.anyLong(), Mockito.anyString()); + firstFitAllocatorSpy.filterHostsWithUefiEnabled(type, virtualMachineProfile, clusterId, podId, dcId, suitableHosts); + + Assert.assertEquals(3, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host2, suitableHosts.get(1)); + Assert.assertEquals(host3, suitableHosts.get(2)); + } + + @Test + public void filterHostsWithUefiEnabledTestDetailWithUefiWithInvalidModeShouldNotFilterAnyHost() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + VMInstanceDetailVO userVmDetailVO = mock(VMInstanceDetailVO.class); + String bootMode = "Invalid mode"; + + Mockito.doReturn(bootMode).when(userVmDetailVO).getValue(); + Mockito.doReturn(userVmDetailVO).when(userVmDetailsDaoMock).findDetail(Mockito.anyLong(), Mockito.anyString()); + firstFitAllocatorSpy.filterHostsWithUefiEnabled(type, virtualMachineProfile, clusterId, podId, dcId, suitableHosts); + + Assert.assertEquals(3, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host2, suitableHosts.get(1)); + Assert.assertEquals(host3, suitableHosts.get(2)); + } + + @Test + public void filterHostsWithUefiEnabledTestDetailWithUefiWithLegacyModeShouldFilterHost() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List uefiHosts = new ArrayList<>(Arrays.asList(host2, host3)); + VMInstanceDetailVO userVmDetailVO = mock(VMInstanceDetailVO.class); + String bootMode = ApiConstants.BootMode.LEGACY.toString(); + + Mockito.doReturn(bootMode).when(userVmDetailVO).getValue(); + Mockito.doReturn(userVmDetailVO).when(userVmDetailsDaoMock).findDetail(Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(uefiHosts).when(hostDaoMock).listByHostCapability(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + firstFitAllocatorSpy.filterHostsWithUefiEnabled(type, virtualMachineProfile, clusterId, podId, dcId, suitableHosts); + + Assert.assertEquals(2, suitableHosts.size()); + Assert.assertEquals(host2, suitableHosts.get(0)); + Assert.assertEquals(host3, suitableHosts.get(1)); + } + + @Test + public void filterHostsWithUefiEnabledTestDetailWithUefiWithSecureModeShouldFilterHost() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List uefiHosts = new ArrayList<>(Arrays.asList(host2, host3)); + VMInstanceDetailVO userVmDetailVO = mock(VMInstanceDetailVO.class); + String bootMode = ApiConstants.BootMode.SECURE.toString(); + + Mockito.doReturn(bootMode).when(userVmDetailVO).getValue(); + Mockito.doReturn(userVmDetailVO).when(userVmDetailsDaoMock).findDetail(Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(uefiHosts).when(hostDaoMock).listByHostCapability(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + firstFitAllocatorSpy.filterHostsWithUefiEnabled(type, virtualMachineProfile, clusterId, podId, dcId, suitableHosts); + + Assert.assertEquals(2, suitableHosts.size()); + Assert.assertEquals(host2, suitableHosts.get(0)); + Assert.assertEquals(host3, suitableHosts.get(1)); + } + + @Test + public void offeringRequestedVGpuAndHostDoesNotHaveItTestVGpuRequestedButHostDoesNotHaveItShouldReturnTrue() { + Mockito.doReturn(1L).when(host1).getId(); + Mockito.doCallRealMethod().when(excludeList).addHost(Mockito.anyLong()); + Mockito.doCallRealMethod().when(excludeList).getHostsToAvoid(); + Mockito.doReturn(false).when(resourceManagerMock).isGPUDeviceAvailable(Mockito.any(ServiceOffering.class), Mockito.any(Host.class), Mockito.any(Long.class)); + boolean result = firstFitAllocatorSpy.offeringRequestedVGpuAndHostDoesNotHaveIt(serviceOffering, virtualMachineProfile, excludeList, host1); + + Assert.assertTrue(result); + Assert.assertEquals(1, excludeList.getHostsToAvoid().size()); + Assert.assertTrue(excludeList.getHostsToAvoid().contains(1L)); + } + + @Test + public void offeringRequestedVGpuAndHostDoesNotHaveItTestVGpuRequestedAndHostDoesHaveItShouldReturnFalse() { + Mockito.doReturn(true).when(resourceManagerMock).isGPUDeviceAvailable(Mockito.any(ServiceOffering.class), Mockito.any(Host.class), Mockito.any(Long.class)); + boolean result = firstFitAllocatorSpy.offeringRequestedVGpuAndHostDoesNotHaveIt(serviceOffering, virtualMachineProfile, excludeList, host1); + + Assert.assertFalse(result); + Mockito.verify(excludeList, Mockito.never()).addHost(Mockito.anyLong()); + } + + @Test + public void filterHostWithNoHvmIfTemplateRequestedTestTemplateDoesNotRequireHvm() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + + Mockito.doReturn(false).when(vmTemplateVO).isRequiresHvm(); + List suitableHosts = firstFitAllocatorSpy.filterHostWithNoHvmIfTemplateRequested(vmTemplateVO, hosts); + + Assert.assertEquals(3, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host2, suitableHosts.get(1)); + Assert.assertEquals(host3, suitableHosts.get(2)); + } + + @Test + public void filterHostWithNoHvmIfTemplateRequestedTestTemplateRequiresHvmShouldReturnOnlyHvmHosts() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + + Mockito.doReturn(true).when(vmTemplateVO).isRequiresHvm(); + Mockito.doReturn(true).when(firstFitAllocatorSpy).hostSupportsHVM(host1); + Mockito.doReturn(false).when(firstFitAllocatorSpy).hostSupportsHVM(host2); + Mockito.doReturn(true).when(firstFitAllocatorSpy).hostSupportsHVM(host3); + List suitableHosts = firstFitAllocatorSpy.filterHostWithNoHvmIfTemplateRequested(vmTemplateVO, hosts); + + Assert.assertEquals(2, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host3, suitableHosts.get(1)); + } + + @Test + public void prioritizeHostsByGpuEnabledTestServiceOfferingRequestedVGpuViaDetailShouldDoNothing() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + ServiceOfferingDetailsVO requestedVGpuType = mock(ServiceOfferingDetailsVO.class); + + Mockito.doReturn(requestedVGpuType).when(serviceOfferingDetailsDao).findDetail(Mockito.anyLong(), Mockito.anyString()); + firstFitAllocatorSpy.prioritizeHostsByGpuEnabled(serviceOffering, hosts); + + Assert.assertEquals(3, hosts.size()); + Assert.assertEquals(host1, hosts.get(0)); + Assert.assertEquals(host2, hosts.get(1)); + Assert.assertEquals(host3, hosts.get(2)); + } + + @Test + public void prioritizeHostsByGpuEnabledTestServiceOfferingRequestedVGpuViaProfileIdShouldDoNothing() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + + Mockito.doReturn(1L).when(serviceOffering).getVgpuProfileId(); + firstFitAllocatorSpy.prioritizeHostsByGpuEnabled(serviceOffering, hosts); + + Assert.assertEquals(3, hosts.size()); + Assert.assertEquals(host1, hosts.get(0)); + Assert.assertEquals(host2, hosts.get(1)); + Assert.assertEquals(host3, hosts.get(2)); + } + + @Test + public void prioritizeHostsByGpuEnabledTestServiceOfferingDidNotRequestVGpuShouldReorderList() { + List allHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + + Mockito.doReturn(null).when(serviceOfferingDetailsDao).findDetail(Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(null).when(serviceOffering).getVgpuProfileId(); + Mockito.doReturn(1L).when(host1).getId(); + Mockito.doReturn(2L).when(host2).getId(); + Mockito.doReturn(3L).when(host3).getId(); + Mockito.doReturn(true).when(resourceManagerMock).isHostGpuEnabled(1L); + Mockito.doReturn(false).when(resourceManagerMock).isHostGpuEnabled(2L); + Mockito.doReturn(false).when(resourceManagerMock).isHostGpuEnabled(3L); + firstFitAllocatorSpy.prioritizeHostsByGpuEnabled(serviceOffering, allHosts); + + Assert.assertEquals(3, allHosts.size()); + Assert.assertEquals(host2, allHosts.get(0)); + Assert.assertEquals(host3, allHosts.get(1)); + Assert.assertEquals(host1, allHosts.get(2)); + } + + @Test + public void prioritizeHostsByGpuEnabledTestServiceOfferingDidNotRequestVGpuShouldNotReorderListIfThereIsNoHostWithVGpu() { + List allHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + + Mockito.doReturn(null).when(serviceOfferingDetailsDao).findDetail(Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(null).when(serviceOffering).getVgpuProfileId(); + Mockito.doReturn(1L).when(host1).getId(); + Mockito.doReturn(2L).when(host2).getId(); + Mockito.doReturn(3L).when(host3).getId(); + Mockito.doReturn(false).when(resourceManagerMock).isHostGpuEnabled(Mockito.anyLong()); + firstFitAllocatorSpy.prioritizeHostsByGpuEnabled(serviceOffering, allHosts); + + Assert.assertEquals(3, allHosts.size()); + Assert.assertEquals(host1, allHosts.get(0)); + Assert.assertEquals(host2, allHosts.get(1)); + Assert.assertEquals(host3, allHosts.get(2)); + } + + @Test + public void prioritizeHostsByHvmCapabilityTestTemplateDidNotRequestedHvmShouldPutHostThatDoesNotSupportHvmInStartOfThePriorityList() { + List hostsToCheck = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List prioritizedHosts = new ArrayList<>(); + + Mockito.doReturn(false).when(vmTemplateVO).isRequiresHvm(); + Mockito.doReturn(true).when(firstFitAllocatorSpy).hostSupportsHVM(host1); + Mockito.doReturn(false).when(firstFitAllocatorSpy).hostSupportsHVM(host2); + Mockito.doReturn(true).when(firstFitAllocatorSpy).hostSupportsHVM(host3); + firstFitAllocatorSpy.prioritizeHostsByHvmCapability(vmTemplateVO, hostsToCheck, prioritizedHosts); + + Assert.assertEquals(3, prioritizedHosts.size()); + Assert.assertEquals(host2, prioritizedHosts.get(0)); + Assert.assertEquals(host1, prioritizedHosts.get(1)); + Assert.assertEquals(host3, prioritizedHosts.get(2)); + } + + @Test + public void prioritizeHostsByHvmCapabilityTestTemplateRequiresHvmShouldNotReorderList() { + List hostsToCheck = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List prioritizedHosts = new ArrayList<>(); + + Mockito.doReturn(true).when(vmTemplateVO).isRequiresHvm(); + firstFitAllocatorSpy.prioritizeHostsByHvmCapability(vmTemplateVO, hostsToCheck, prioritizedHosts); + + Assert.assertEquals(3, prioritizedHosts.size()); + Assert.assertEquals(host1, prioritizedHosts.get(0)); + Assert.assertEquals(host2, prioritizedHosts.get(1)); + Assert.assertEquals(host3, prioritizedHosts.get(2)); + } + + @Test + public void prioritizeHostsWithMatchingGuestOsTestShouldPutMatchingHostInHighPriorityAndHostsThatDoesNotMatchInLowPriorityList() { + List hostsToCheck = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List highPriorityHosts = new ArrayList<>(); + List lowPriorityHosts = new ArrayList<>(); + String guestOsCategory1 = "guestOsCategory1"; + String guestOsCategory2 = "guestOsCategory2"; + + Mockito.doReturn(guestOsCategory1).when(firstFitAllocatorSpy).getTemplateGuestOSCategory(vmTemplateVO); + Mockito.doReturn(guestOsCategory1).when(firstFitAllocatorSpy).getHostGuestOSCategory(host1); + Mockito.doReturn(guestOsCategory2).when(firstFitAllocatorSpy).getHostGuestOSCategory(host2); + Mockito.doReturn(null).when(firstFitAllocatorSpy).getHostGuestOSCategory(host3); + firstFitAllocatorSpy.prioritizeHostsWithMatchingGuestOs(vmTemplateVO,hostsToCheck, highPriorityHosts, lowPriorityHosts); + + Assert.assertEquals(1, highPriorityHosts.size()); + Assert.assertEquals(host1, highPriorityHosts.get(0)); + Assert.assertEquals(1, lowPriorityHosts.size()); + Assert.assertEquals(host2, lowPriorityHosts.get(0)); + } + + @Test + public void testHostByCombinedCapacityOrder() { + // Test scenario 1: Default capacity usage (0.5 weight) + List mockCapacity = getHostCapacities(); + Map hostByCombinedCapacity = FirstFitAllocator.getHostByCombinedCapacities(mockCapacity, 0.5); + + // Verify host ordering and capacity values + Long firstHostId = hostByCombinedCapacity.keySet().iterator().next(); + Assert.assertEquals("Host with ID 1 should be first in ordering", Long.valueOf(1L), firstHostId); + Assert.assertEquals("Host 1 combined capacity should match expected value", + 0.9609375, hostByCombinedCapacity.get(1L), TOLERANCE); + Assert.assertEquals("Host 2 combined capacity should match expected value", + 0.9296875, hostByCombinedCapacity.get(2L), TOLERANCE); + + // Test scenario 2: Modified capacity usage (0.7 weight) + when(mockCapacity.get(0).getUsedCapacity()).thenReturn(1500L); + hostByCombinedCapacity = FirstFitAllocator.getHostByCombinedCapacities(mockCapacity, 0.7); + + // Verify new ordering after capacity change + firstHostId = hostByCombinedCapacity.keySet().iterator().next(); + Assert.assertEquals("Host with ID 2 should be first after capacity change", Long.valueOf(2L), firstHostId); + Assert.assertEquals("Host 2 combined capacity should match expected value after change", + 0.9515625, hostByCombinedCapacity.get(2L), TOLERANCE); + Assert.assertEquals("Host 1 combined capacity should match expected value after change", + 0.9484375, hostByCombinedCapacity.get(1L), TOLERANCE); + } + + List getHostCapacities() { + CapacityVO cpuCapacity1 = mock(CapacityVO.class); + when(cpuCapacity1.getHostOrPoolId()).thenReturn(1L); + when(cpuCapacity1.getTotalCapacity()).thenReturn(32000L); + when(cpuCapacity1.getReservedCapacity()).thenReturn(0L); + when(cpuCapacity1.getUsedCapacity()).thenReturn(500L); + when(cpuCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_CPU); + + CapacityVO cpuCapacity2 = mock(CapacityVO.class); + when(cpuCapacity2.getHostOrPoolId()).thenReturn(2L); + when(cpuCapacity2.getTotalCapacity()).thenReturn(32000L); + when(cpuCapacity2.getReservedCapacity()).thenReturn(0L); + when(cpuCapacity2.getUsedCapacity()).thenReturn(500L); + when(cpuCapacity2.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_CPU); + + CapacityVO memCapacity1 = mock(CapacityVO.class); + when(memCapacity1.getHostOrPoolId()).thenReturn(1L); + when(memCapacity1.getTotalCapacity()).thenReturn(8589934592L); + when(memCapacity1.getReservedCapacity()).thenReturn(0L); + when(memCapacity1.getUsedCapacity()).thenReturn(536870912L); + when(memCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_MEMORY); + + CapacityVO memCapacity2 = mock(CapacityVO.class); + when(memCapacity2.getHostOrPoolId()).thenReturn(2L); + when(memCapacity2.getTotalCapacity()).thenReturn(8589934592L); + when(memCapacity2.getReservedCapacity()).thenReturn(0L); + when(memCapacity2.getUsedCapacity()).thenReturn(1073741824L); + when(memCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_MEMORY); + return Arrays.asList(cpuCapacity1, memCapacity1, cpuCapacity2, memCapacity2); + } } diff --git a/server/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java b/server/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java new file mode 100644 index 00000000000..ab9b322eb84 --- /dev/null +++ b/server/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java @@ -0,0 +1,332 @@ +// 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 com.cloud.agent.manager.allocator.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.cloud.agent.manager.allocator.HostAllocator; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.offering.ServiceOffering; +import com.cloud.resource.ResourceManager; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.storage.VMTemplateVO; +import com.cloud.vm.VirtualMachineProfile; +import org.apache.commons.collections.CollectionUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; + +@RunWith(MockitoJUnitRunner.class) +public class RandomAllocatorTest { + + @Mock + HostDao hostDao; + + @Spy + @InjectMocks + RandomAllocator randomAllocator; + + @Mock + ResourceManager resourceManagerMock; + + private final Host.Type type = Host.Type.Routing; + + private final Long clusterId = 1L; + + private final Long podId = 2L; + + private final Long zoneId = 3L; + + private final List emptyList = new ArrayList<>(); + + private final String hostTag = "hostTag"; + + private final HostVO host1 = Mockito.mock(HostVO.class); + + private final HostVO host2 = Mockito.mock(HostVO.class); + + private final HostVO host3 = Mockito.mock(HostVO.class); + + private final VMTemplateVO vmTemplateVO = Mockito.mock(VMTemplateVO.class); + + private final ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class); + + private final DeploymentPlanner.ExcludeList excludeList = Mockito.mock(DeploymentPlanner.ExcludeList.class); + + private final VirtualMachineProfile virtualMachineProfile = Mockito.mock(VirtualMachineProfile.class); + + private final DeploymentPlan deploymentPlan = Mockito.mock(DeploymentPlan.class); + + private final boolean considerReservedCapacity = true; + + + @Test + public void testListHostsByTags() { + Host.Type type = Host.Type.Routing; + Long id = 1L; + String templateTag = "tag1"; + String offeringTag = "tag2"; + HostVO host1 = Mockito.mock(HostVO.class); + HostVO host2 = Mockito.mock(HostVO.class); + Mockito.when(hostDao.listByHostTag(type, clusterId, podId, zoneId, offeringTag)).thenReturn(List.of(host1, host2)); + + // No template tagged host + ArrayList noTemplateTaggedHosts = new ArrayList<>(Arrays.asList(host1, host2)); + Mockito.when(hostDao.listByHostTag(type, clusterId, podId, zoneId, templateTag)).thenReturn(new ArrayList<>()); + randomAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(noTemplateTaggedHosts, type, zoneId, podId, clusterId, offeringTag, templateTag); + Assert.assertTrue(CollectionUtils.isEmpty(noTemplateTaggedHosts)); + + // Different template tagged host + ArrayList differentTemplateTaggedHost = new ArrayList<>(Arrays.asList(host1, host2)); + HostVO host3 = Mockito.mock(HostVO.class); + Mockito.when(hostDao.listByHostTag(type, clusterId, podId, zoneId, templateTag)).thenReturn(List.of(host3)); + randomAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(differentTemplateTaggedHost, type, zoneId, podId, clusterId, offeringTag, templateTag); + Assert.assertTrue(CollectionUtils.isEmpty(differentTemplateTaggedHost)); + + // Matching template tagged host + ArrayList matchingTemplateTaggedHost = new ArrayList<>(Arrays.asList(host1, host2)); + Mockito.when(hostDao.listByHostTag(type, clusterId, podId, zoneId, templateTag)).thenReturn(List.of(host1)); + randomAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(matchingTemplateTaggedHost, type, zoneId, podId, clusterId, offeringTag, templateTag); + Assert.assertFalse(CollectionUtils.isEmpty(matchingTemplateTaggedHost)); + Assert.assertEquals(1, matchingTemplateTaggedHost.size()); + + // No template tag + ArrayList noTemplateTag = new ArrayList<>(Arrays.asList(host1, host2)); + randomAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(noTemplateTag, type, zoneId, podId, clusterId, offeringTag, null); + Assert.assertFalse(CollectionUtils.isEmpty(noTemplateTag)); + Assert.assertEquals(2, noTemplateTag.size()); + + // No offering tag + ArrayList noOfferingTag = new ArrayList<>(Arrays.asList(host1, host2)); + randomAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(noOfferingTag, type, zoneId, podId, clusterId, null, templateTag); + Assert.assertFalse(CollectionUtils.isEmpty(noOfferingTag)); + Assert.assertEquals(1, noOfferingTag.size()); + } + + @Test + public void findSuitableHostsTestHostTypeStorageShouldReturnNull() { + List suitableHosts = randomAllocator.findSuitableHosts(virtualMachineProfile, deploymentPlan, Host.Type.Storage, excludeList, null, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity); + + Assert.assertNull(suitableHosts); + } + + @Test + public void findSuitableHostsTestNoAvailableHostsShouldReturnNull() { + Mockito.doReturn(serviceOffering).when(virtualMachineProfile).getServiceOffering(); + Mockito.doReturn(vmTemplateVO).when(virtualMachineProfile).getTemplate(); + Mockito.doReturn(hostTag).when(serviceOffering).getHostTag(); + Mockito.doReturn(emptyList).when(randomAllocator).retrieveHosts(Mockito.any(Host.Type.class), Mockito.nullable(List.class), Mockito.any(VMTemplateVO.class), Mockito.anyString(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + List suitableHosts = randomAllocator.findSuitableHosts(virtualMachineProfile, deploymentPlan, type, excludeList, null, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity); + + Assert.assertNull(suitableHosts); + } + + @Test + public void findSuitableHostsTestAvailableHostsShouldCallFilterAvailableHostsOnce() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2)); + + Mockito.doReturn(serviceOffering).when(virtualMachineProfile).getServiceOffering(); + Mockito.doReturn(vmTemplateVO).when(virtualMachineProfile).getTemplate(); + Mockito.doReturn(hostTag).when(serviceOffering).getHostTag(); + Mockito.doReturn(hosts).when(randomAllocator).retrieveHosts(Mockito.any(Host.Type.class), Mockito.nullable(List.class), Mockito.any(VMTemplateVO.class), Mockito.anyString(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hosts).when(randomAllocator).filterAvailableHosts(Mockito.any(DeploymentPlanner.ExcludeList.class), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.anyList(), Mockito.any(ServiceOffering.class)); + List suitableHosts = randomAllocator.findSuitableHosts(virtualMachineProfile, deploymentPlan, type, excludeList, null, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity); + + Mockito.verify(randomAllocator, Mockito.times(1)).filterAvailableHosts(Mockito.any(DeploymentPlanner.ExcludeList.class), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.anyList(), Mockito.any(ServiceOffering.class)); + Assert.assertEquals(2, suitableHosts.size()); + } + + @Test + public void filterAvailableHostsTestAvailableHostsReachedReturnUpToLimitShouldReturnOnlyHostsWithinLimit() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2)); + int returnUpTo = 1; + + Mockito.doReturn(false).when(excludeList).shouldAvoid(Mockito.any(Host.class)); + Mockito.doReturn(true).when(randomAllocator).hostHasCpuCapabilityAndCapacity(Mockito.anyBoolean(), Mockito.any(ServiceOffering.class), Mockito.any(Host.class)); + List suitableHosts = randomAllocator.filterAvailableHosts(excludeList, returnUpTo, considerReservedCapacity, hosts, serviceOffering); + + Assert.assertEquals(1, suitableHosts.size()); + } + + @Test + public void filterAvailableHostsTestReturnUpToAllShouldReturnAllAvailableHosts() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2)); + int returnUpTo = HostAllocator.RETURN_UPTO_ALL; + + Mockito.doReturn(false).when(excludeList).shouldAvoid(Mockito.any(Host.class)); + Mockito.doReturn(true).when(randomAllocator).hostHasCpuCapabilityAndCapacity(Mockito.anyBoolean(), Mockito.any(ServiceOffering.class), Mockito.any(Host.class)); + List suitableHosts = randomAllocator.filterAvailableHosts(excludeList, returnUpTo, considerReservedCapacity, hosts, serviceOffering); + + Assert.assertEquals(2, suitableHosts.size()); + } + + @Test + public void filterAvailableHostsTestHost1InAvoidShouldOnlyReturnHost2() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2)); + int returnUpTo = HostAllocator.RETURN_UPTO_ALL; + + Mockito.doReturn(true).when(excludeList).shouldAvoid(host1); + Mockito.doReturn(false).when(excludeList).shouldAvoid(host2); + Mockito.doReturn(true).when(randomAllocator).hostHasCpuCapabilityAndCapacity(Mockito.anyBoolean(), Mockito.any(ServiceOffering.class), Mockito.any(Host.class)); + List suitableHosts = randomAllocator.filterAvailableHosts(excludeList, returnUpTo, considerReservedCapacity, hosts, serviceOffering); + + Assert.assertEquals(1, suitableHosts.size()); + Assert.assertEquals(host2, suitableHosts.get(0)); + } + + @Test + public void filterAvailableHostsTestOnlyHost2HasCpuCapacityAndCapabilityShouldReturnOnlyHost2() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2)); + int returnUpTo = HostAllocator.RETURN_UPTO_ALL; + + Mockito.doReturn(false).when(excludeList).shouldAvoid(Mockito.any(Host.class)); + Mockito.doReturn(false).when(randomAllocator).hostHasCpuCapabilityAndCapacity(considerReservedCapacity, serviceOffering, host1); + Mockito.doReturn(true).when(randomAllocator).hostHasCpuCapabilityAndCapacity(considerReservedCapacity, serviceOffering, host2); + List suitableHosts = randomAllocator.filterAvailableHosts(excludeList, returnUpTo, considerReservedCapacity, hosts, serviceOffering); + + Assert.assertEquals(1, suitableHosts.size()); + Assert.assertEquals(host2, suitableHosts.get(0)); + } + + @Test + public void retrieveHostsTestProvidedHostsNullAndNoHostTagAndNoTagRuleShouldOnlyReturnHostsWithNoTags() { + List upAndEnabledHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithNoRuleTagsAndHostTags = new ArrayList<>(Arrays.asList(host1)); + + Mockito.doReturn(upAndEnabledHosts).when(resourceManagerMock).listAllUpAndEnabledHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hostsWithNoRuleTagsAndHostTags).when(hostDao).listAllHostsThatHaveNoRuleTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(emptyList).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, null, vmTemplateVO, null, clusterId, podId, zoneId); + + Assert.assertEquals(1, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + } + + @Test + public void retrieveHostsTestProvidedHostsNullAndOnlyHostTagsRulesShouldReturnHostsThatMatchRuleTagsAndHostsWithNoTags() { + List upAndEnabledHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithNoRuleTagsAndHostTags = new ArrayList<>(Arrays.asList(host1)); + List hostsMatchingRuleTags = new ArrayList<>(Arrays.asList(host2)); + + Mockito.doReturn(upAndEnabledHosts).when(resourceManagerMock).listAllUpAndEnabledHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hostsWithNoRuleTagsAndHostTags).when(hostDao).listAllHostsThatHaveNoRuleTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hostsMatchingRuleTags).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, null, vmTemplateVO, null, clusterId, podId, zoneId); + + Assert.assertEquals(2, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + Assert.assertEquals(host2, availableHosts.get(1)); + } + + @Test + public void retrieveHostsTestProvidedHostsNullProvidedHostTagsNotNullAndNoHostWithMatchingRuleTagsShouldReturnHostWithMatchingTags() { + List upAndEnabledHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithMatchingTags = new ArrayList<>(Arrays.asList(host1)); + + Mockito.doReturn(upAndEnabledHosts).when(resourceManagerMock).listAllUpAndEnabledHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hostsWithMatchingTags).when(hostDao).listByHostTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(emptyList).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, null, vmTemplateVO, hostTag, clusterId, podId, zoneId); + + Assert.assertEquals(1, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + } + + @Test + public void retrieveHostsTestProvidedHostsNullProvidedHostTagsNotNullAndHostWithMatchingRuleTagsShouldReturnHostWithHostMatchingTagsAndRuleTags() { + List upAndEnabledHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithMatchingTags = new ArrayList<>(Arrays.asList(host1)); + List hostsMatchingRuleTags = new ArrayList<>(Arrays.asList(host3)); + + Mockito.doReturn(upAndEnabledHosts).when(resourceManagerMock).listAllUpAndEnabledHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hostsWithMatchingTags).when(hostDao).listByHostTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(hostsMatchingRuleTags).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, null, vmTemplateVO, hostTag, clusterId, podId, zoneId); + + Assert.assertEquals(2, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + Assert.assertEquals(host3, availableHosts.get(1)); + } + + @Test + public void retrieveHostsTestProvidedHostsNotNullAndNoHostTagAndNoTagRuleShouldOnlyReturnHostsWithNoTags() { + List providedHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithNoRuleTagsAndHostTags = new ArrayList<>(Arrays.asList(host1)); + + Mockito.doReturn(hostsWithNoRuleTagsAndHostTags).when(hostDao).listAllHostsThatHaveNoRuleTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(emptyList).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, providedHosts, vmTemplateVO, null, clusterId, podId, zoneId); + + Assert.assertEquals(1, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + } + + @Test + public void retrieveHostsTestProvidedHostsNotNullAndOnlyHostTagsRulesShouldReturnHostsThatMatchRuleTagsAndHostsWithNoTags() { + List providedHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithNoRuleTagsAndHostTags = new ArrayList<>(Arrays.asList(host1)); + List hostsMatchingRuleTags = new ArrayList<>(Arrays.asList(host2)); + + Mockito.doReturn(hostsWithNoRuleTagsAndHostTags).when(hostDao).listAllHostsThatHaveNoRuleTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hostsMatchingRuleTags).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, providedHosts, vmTemplateVO, null, clusterId, podId, zoneId); + + Assert.assertEquals(2, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + Assert.assertEquals(host2, availableHosts.get(1)); + } + + @Test + public void retrieveHostsTestProvidedHostsNotNullProvidedHostTagsNotNullAndNoHostWithMatchingRuleTagsShouldReturnHostWithMatchingTags() { + List providedHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithMatchingTags = new ArrayList<>(Arrays.asList(host1)); + + Mockito.doReturn(hostsWithMatchingTags).when(hostDao).listByHostTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(emptyList).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, providedHosts, vmTemplateVO, hostTag, clusterId, podId, zoneId); + + Assert.assertEquals(1, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + } + + @Test + public void retrieveHostsTestProvidedHostsNullNotProvidedHostTagsNotNullAndHostWithMatchingRuleTagsShouldReturnHostWithHostMatchingTagsAndRuleTags() { + List providedHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithMatchingTags = new ArrayList<>(Arrays.asList(host1)); + List hostsMatchingRuleTags = new ArrayList<>(Arrays.asList(host3)); + + Mockito.doReturn(hostsWithMatchingTags).when(hostDao).listByHostTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(hostsMatchingRuleTags).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, providedHosts, vmTemplateVO, hostTag, clusterId, podId, zoneId); + + Assert.assertEquals(2, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + Assert.assertEquals(host3, availableHosts.get(1)); + } +} diff --git a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java index 51b2dad3dec..cd7d40d6895 100644 --- a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java +++ b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java @@ -460,7 +460,7 @@ public class NetworkServiceImplTest { null, null, false, null, accountMock, null, phyNet, 1L, null, null, null, null, null, true, null, null, null, null, null, - null, null, null, null, new Pair<>(1500, privateMtu), null); + null, null, null, null, new Pair<>(1500, privateMtu), null, true); } @Test public void testValidateMtuConfigWhenMtusExceedThreshold() { @@ -1330,4 +1330,52 @@ public class NetworkServiceImplTest { Mockito.verify(accountJoin).addAnd("type", SearchCriteria.Op.EQ, Account.Type.PROJECT); Mockito.verify(sc).addAnd("id", SearchCriteria.Op.SC, accountJoin); } + + @Test(expected = InvalidParameterValueException.class) + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestThrowExceptionWhenParamIsSpecifiedOnTiersCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.isForVpc()).thenReturn(true); + + service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(true, networkOfferingVO); + } + + @Test + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturnTrueByDefaultOnTiersCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.isForVpc()).thenReturn(true); + + Assert.assertTrue(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(null, networkOfferingVO)); + } + + @Test(expected = InvalidParameterValueException.class) + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestThrowExceptionWhenParamIsSpecifiedOnNonIsolatedNetworksCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.getGuestType()).thenReturn(Network.GuestType.Shared); + + service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(true, networkOfferingVO); + } + + @Test + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturnTrueByDefaultOnNonIsolatedNetworksCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.getGuestType()).thenReturn(Network.GuestType.L2); + + Assert.assertTrue(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(null, networkOfferingVO)); + } + + @Test + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturnTrueByDefaultOnIsolatedNetworksCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.getGuestType()).thenReturn(Network.GuestType.Isolated); + + Assert.assertTrue(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(null, networkOfferingVO)); + } + + @Test + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturnSpecifiedValueOnIsolatedNetworksCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.getGuestType()).thenReturn(Network.GuestType.Isolated); + + Assert.assertFalse(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(false, networkOfferingVO)); + } } diff --git a/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java b/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java index 4237ef7f6f6..e4773d01de9 100644 --- a/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java +++ b/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java @@ -24,18 +24,31 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.network.Ipv6Service; +import com.cloud.network.Network; +import com.cloud.network.Networks; +import com.cloud.network.addr.PublicIp; +import com.cloud.network.dao.NetworkVO; +import com.cloud.offering.NetworkOffering; +import com.cloud.utils.net.Ip; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.network.router.deployment.RouterDeploymentDefinition; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import com.cloud.agent.AgentManager; @@ -57,6 +70,9 @@ import com.cloud.vm.VirtualMachineName; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; +import java.util.List; +import java.util.Map; + @RunWith(MockitoJUnitRunner.class) public class NetworkHelperImplTest { @@ -69,32 +85,59 @@ public class NetworkHelperImplTest { @Mock DomainRouterDao routerDao; + @Spy @InjectMocks - protected NetworkHelperImpl nwHelper = new NetworkHelperImpl(); + protected NetworkHelperImpl networkHelperSpy = new NetworkHelperImpl(); + @Mock NetworkOrchestrationService networkOrchestrationService; + @Mock NetworkDao networkDao; + @Mock NetworkModel networkModel; + @Mock - NicDao nicDao; + private NicDao nicDaoMock; @Mock private RouterDeploymentDefinition routerDeploymentDefinition; + @Mock private VirtualRouterProvider virtualProvider; + @Mock private Account owner; + @Mock private ServiceOfferingVO routerOffering; + @Mock private VMTemplateVO template; + @Mock + private PublicIp publicIpMock; + + @Mock + private RouterDeploymentDefinition routerDeploymentDefinitionMock; + + @Mock + private Ipv6Service ipv6ServiceMock; + + @Mock + private NicVO nicVoMock; + + @Mock + private Network networkMock; + + private NicProfile nicProfile = new NicProfile(); + @Before public void setUp() { - nwHelper._networkDao = networkDao; - nwHelper._networkModel = networkModel; + networkHelperSpy._networkDao = networkDao; + networkHelperSpy._networkModel = networkModel; + when(template.getId()).thenReturn(1L); when(template.isDynamicallyScalable()).thenReturn(true); when(virtualProvider.getId()).thenReturn(1L); @@ -107,7 +150,7 @@ public class NetworkHelperImplTest { public void testSendCommandsToRouterWrongRouterVersion() throws AgentUnavailableException, OperationTimedoutException, ResourceUnavailableException { // Prepare - NetworkHelperImpl nwHelperUT = spy(this.nwHelper); + NetworkHelperImpl nwHelperUT = networkHelperSpy; VirtualRouter vr = mock(VirtualRouter.class); doReturn(false).when(nwHelperUT).checkRouterVersion(vr); @@ -122,7 +165,7 @@ public class NetworkHelperImplTest { public void testSendCommandsToRouter() throws AgentUnavailableException, OperationTimedoutException, ResourceUnavailableException { // Prepare - NetworkHelperImpl nwHelperUT = spy(this.nwHelper); + NetworkHelperImpl nwHelperUT = networkHelperSpy; VirtualRouter vr = mock(VirtualRouter.class); when(vr.getHostId()).thenReturn(HOST_ID); doReturn(true).when(nwHelperUT).checkRouterVersion(vr); @@ -160,7 +203,7 @@ public class NetworkHelperImplTest { public void testSendCommandsToRouterWithTrueResult() throws AgentUnavailableException, OperationTimedoutException, ResourceUnavailableException { // Prepare - NetworkHelperImpl nwHelperUT = spy(this.nwHelper); + NetworkHelperImpl nwHelperUT = networkHelperSpy; VirtualRouter vr = mock(VirtualRouter.class); when(vr.getHostId()).thenReturn(HOST_ID); doReturn(true).when(nwHelperUT).checkRouterVersion(vr); @@ -198,7 +241,7 @@ public class NetworkHelperImplTest { public void testSendCommandsToRouterWithNoAnswers() throws AgentUnavailableException, OperationTimedoutException, ResourceUnavailableException { // Prepare - NetworkHelperImpl nwHelperUT = spy(this.nwHelper); + NetworkHelperImpl nwHelperUT = networkHelperSpy; VirtualRouter vr = mock(VirtualRouter.class); when(vr.getHostId()).thenReturn(HOST_ID); doReturn(true).when(nwHelperUT).checkRouterVersion(vr); @@ -227,7 +270,7 @@ public class NetworkHelperImplTest { boolean offerHA = false; Long vpcId = 900L; when(routerDao.persist(any(DomainRouterVO.class))).thenAnswer(invocation -> invocation.getArgument(0)); - DomainRouterVO result = nwHelper.createOrUpdateDomainRouter( + DomainRouterVO result = networkHelperSpy.createOrUpdateDomainRouter( null, id, routerDeploymentDefinition, owner, userId, routerOffering, offerHA, vpcId, template); assertNotNull(result); assertEquals(id, result.getId()); @@ -261,11 +304,234 @@ public class NetworkHelperImplTest { owner.getDomainId(), owner.getId(), userId, routerDeploymentDefinition.isRedundant(), VirtualRouter.RedundantState.UNKNOWN, offerHA, false, vpcId); existing.setDynamicallyScalable(false); - DomainRouterVO result = nwHelper.createOrUpdateDomainRouter( + DomainRouterVO result = networkHelperSpy.createOrUpdateDomainRouter( existing, id, routerDeploymentDefinition, owner, userId, routerOffering, offerHA, vpcId, template); verify(routerDao).update(existing.getId(), existing); assertEquals(template.getId(), result.getTemplateId()); assertEquals(Hypervisor.HypervisorType.KVM, result.getHypervisorType()); assertTrue(result.isDynamicallyScalable()); } + + + private NicProfile getExpectedNicProfile(boolean vxlan, String vlanTag) { + NicProfile nic = new NicProfile(); + nic.setDefaultNic(true); + nic.setIPv4Address("192.168.0.10"); + nic.setIPv4Gateway("192.168.0.1"); + nic.setIPv4Netmask("255.255.255.0"); + nic.setMacAddress("ff-ff-ff-ff-ff-ff"); + + if (vxlan) { + nic.setBroadcastType(Networks.BroadcastDomainType.Vxlan); + nic.setBroadcastUri(Networks.BroadcastDomainType.Vxlan.toUri(vlanTag)); + nic.setIsolationUri(Networks.BroadcastDomainType.Vxlan.toUri(vlanTag)); + } else { + nic.setBroadcastType(Networks.BroadcastDomainType.Vlan); + nic.setBroadcastUri(vlanTag != null ? Networks.BroadcastDomainType.Vlan.toUri(vlanTag) : null); + nic.setIsolationUri(vlanTag != null ? Networks.IsolationType.Vlan.toUri(vlanTag) : null); + } + + return nic; + } + + @Test + public void configurePublicVrNicBasedOnSourceNatIpTestConfigureVxLanNic() { + String vlanTag = "200"; + NicProfile expected = getExpectedNicProfile(true, vlanTag); + + Ip ipMock = Mockito.mock(Ip.class); + NetworkVO publicNetworkMock = Mockito.mock(NetworkVO.class); + long networkId = 1L; + Mockito.when(publicIpMock.getAddress()).thenReturn(ipMock); + Mockito.when(ipMock.addr()).thenReturn(expected.getIPv4Address()); + Mockito.when(publicIpMock.getGateway()).thenReturn(expected.getIPv4Gateway()); + Mockito.when(publicIpMock.getNetmask()).thenReturn(expected.getIPv4Netmask()); + Mockito.when(publicIpMock.getMacAddress()).thenReturn(expected.getMacAddress()); + Mockito.when(publicIpMock.getNetworkId()).thenReturn(networkId); + Mockito.when(networkDao.findById(networkId)).thenReturn(publicNetworkMock); + Mockito.when(publicNetworkMock.getBroadcastDomainType()).thenReturn(Networks.BroadcastDomainType.Vxlan); + Mockito.when(publicIpMock.getVlanTag()).thenReturn(vlanTag); + + networkHelperSpy.configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + + Assert.assertTrue(nicProfile.isDefaultNic()); + Assert.assertEquals(expected.getIPv4Address(), nicProfile.getIPv4Address()); + Assert.assertEquals(expected.getIPv4Gateway(), nicProfile.getIPv4Gateway()); + Assert.assertEquals(expected.getIPv4Netmask(), nicProfile.getIPv4Netmask()); + Assert.assertEquals(expected.getMacAddress(), nicProfile.getMacAddress()); + Assert.assertEquals(expected.getBroadcastType(), nicProfile.getBroadcastType()); + Assert.assertEquals(expected.getBroadCastUri(), nicProfile.getBroadCastUri()); + Assert.assertEquals(expected.getIsolationUri(), nicProfile.getIsolationUri()); + } + + @Test + public void configurePublicVrNicBasedOnSourceNatIpTestConfigureVlanNicWithVlanTag() { + String vlanTag = "200"; + NicProfile expected = getExpectedNicProfile(false, vlanTag); + + Ip ipMock = Mockito.mock(Ip.class); + NetworkVO publicNetworkMock = Mockito.mock(NetworkVO.class); + long networkId = 1L; + Mockito.when(publicIpMock.getAddress()).thenReturn(ipMock); + Mockito.when(ipMock.addr()).thenReturn(expected.getIPv4Address()); + Mockito.when(publicIpMock.getGateway()).thenReturn(expected.getIPv4Gateway()); + Mockito.when(publicIpMock.getNetmask()).thenReturn(expected.getIPv4Netmask()); + Mockito.when(publicIpMock.getMacAddress()).thenReturn(expected.getMacAddress()); + Mockito.when(publicIpMock.getNetworkId()).thenReturn(networkId); + Mockito.when(networkDao.findById(networkId)).thenReturn(publicNetworkMock); + Mockito.when(publicNetworkMock.getBroadcastDomainType()).thenReturn(Networks.BroadcastDomainType.Vlan); + Mockito.when(publicIpMock.getVlanTag()).thenReturn(vlanTag); + + networkHelperSpy.configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + + Assert.assertTrue(nicProfile.isDefaultNic()); + Assert.assertEquals(expected.getIPv4Address(), nicProfile.getIPv4Address()); + Assert.assertEquals(expected.getIPv4Gateway(), nicProfile.getIPv4Gateway()); + Assert.assertEquals(expected.getIPv4Netmask(), nicProfile.getIPv4Netmask()); + Assert.assertEquals(expected.getMacAddress(), nicProfile.getMacAddress()); + Assert.assertEquals(expected.getBroadcastType(), nicProfile.getBroadcastType()); + Assert.assertEquals(expected.getBroadCastUri(), nicProfile.getBroadCastUri()); + Assert.assertEquals(expected.getIsolationUri(), nicProfile.getIsolationUri()); + } + + @Test + public void configurePublicVrNicBasedOnSourceNatIpTestConfigureVlanNicWithNoVlanTag() { + String vlanTag = null; + NicProfile expected = getExpectedNicProfile(false, vlanTag); + + Ip ipMock = Mockito.mock(Ip.class); + NetworkVO publicNetworkMock = Mockito.mock(NetworkVO.class); + long networkId = 1L; + Mockito.when(publicIpMock.getAddress()).thenReturn(ipMock); + Mockito.when(ipMock.addr()).thenReturn(expected.getIPv4Address()); + Mockito.when(publicIpMock.getGateway()).thenReturn(expected.getIPv4Gateway()); + Mockito.when(publicIpMock.getNetmask()).thenReturn(expected.getIPv4Netmask()); + Mockito.when(publicIpMock.getMacAddress()).thenReturn(expected.getMacAddress()); + Mockito.when(publicIpMock.getNetworkId()).thenReturn(networkId); + Mockito.when(networkDao.findById(networkId)).thenReturn(publicNetworkMock); + Mockito.when(publicNetworkMock.getBroadcastDomainType()).thenReturn(Networks.BroadcastDomainType.Vlan); + Mockito.when(publicIpMock.getVlanTag()).thenReturn(vlanTag); + + networkHelperSpy.configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + + Assert.assertTrue(nicProfile.isDefaultNic()); + Assert.assertEquals(expected.getIPv4Address(), nicProfile.getIPv4Address()); + Assert.assertEquals(expected.getIPv4Gateway(), nicProfile.getIPv4Gateway()); + Assert.assertEquals(expected.getIPv4Netmask(), nicProfile.getIPv4Netmask()); + Assert.assertEquals(expected.getMacAddress(), nicProfile.getMacAddress()); + Assert.assertEquals(expected.getBroadcastType(), nicProfile.getBroadcastType()); + Assert.assertEquals(expected.getBroadCastUri(), nicProfile.getBroadCastUri()); + Assert.assertEquals(expected.getIsolationUri(), nicProfile.getIsolationUri()); + } + + @Test + public void setPublicNicMacAddressSameAsPeerNicTestDoNothingWhenThereIsNoPeer() throws InsufficientAddressCapacityException { + String newMacAddress = "ff-ff-ff-ff-ff-ff"; + nicProfile.setIPv4Address("10.0.0.1"); + nicProfile.setMacAddress(newMacAddress); + Mockito.when(nicDaoMock.findByIp4AddressAndNetworkId(Mockito.anyString(), Mockito.anyLong())).thenReturn(null); + + networkHelperSpy.setPublicNicMacAddressSameAsPeerNic(nicProfile, networkMock, routerDeploymentDefinitionMock); + + Assert.assertEquals(newMacAddress, nicProfile.getMacAddress()); + } + + @Test + public void setPublicNicMacAddressSameAsPeerNicTestKeepMacAddress() throws InsufficientAddressCapacityException { + String peerMacAddress = "ff-ff-ff-ff-ff-ff"; + nicProfile.setIPv4Address("10.0.0.1"); + nicProfile.setMacAddress("ff-ff-ff-ff-ff-f1"); + + Mockito.when(nicDaoMock.findByIp4AddressAndNetworkId(Mockito.anyString(), Mockito.anyLong())).thenReturn(nicVoMock); + Mockito.when(nicVoMock.getMacAddress()).thenReturn(peerMacAddress); + Mockito.when(routerDeploymentDefinitionMock.getKeepMacAddressOnPublicNic()).thenReturn(true); + + networkHelperSpy.setPublicNicMacAddressSameAsPeerNic(nicProfile, networkMock, routerDeploymentDefinitionMock); + + Assert.assertEquals(peerMacAddress, nicProfile.getMacAddress()); + } + + @Test + public void setPublicNicMacAddressSameAsPeerNicTestDifferentMacAddressFetchingNewSourceNatIp() throws InsufficientAddressCapacityException { + String macAddress = "ff-ff-ff-ff-ff-f1"; + nicProfile.setIPv4Address("10.0.0.1"); + nicProfile.setMacAddress(macAddress); + PublicIp publicIpMock = Mockito.mock(PublicIp.class); + + Mockito.when(nicDaoMock.findByIp4AddressAndNetworkId(Mockito.anyString(), Mockito.anyLong())).thenReturn(nicVoMock); + Mockito.when(nicVoMock.getMacAddress()).thenReturn(macAddress); + Mockito.when(routerDeploymentDefinitionMock.getKeepMacAddressOnPublicNic()).thenReturn(false); + Mockito.when(routerDeploymentDefinitionMock.getSourceNatIP()).thenReturn(publicIpMock); + Mockito.doNothing().when(networkHelperSpy).configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + + networkHelperSpy.setPublicNicMacAddressSameAsPeerNic(nicProfile, networkMock, routerDeploymentDefinitionMock); + + Mockito.verify(routerDeploymentDefinitionMock).findSourceNatIP(); + Mockito.verify(networkHelperSpy).configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + } + + @Test + public void setPublicNicMacAddressSameAsPeerNicTestDifferentMacAddressNotFetchingNewSourceNatIp() throws InsufficientAddressCapacityException { + String newMacAddress = "ff-ff-ff-ff-ff-ff"; + nicProfile.setIPv4Address("10.0.0.1"); + nicProfile.setMacAddress(newMacAddress); + + Mockito.when(nicDaoMock.findByIp4AddressAndNetworkId(Mockito.anyString(), Mockito.anyLong())).thenReturn(nicVoMock); + Mockito.when(nicVoMock.getMacAddress()).thenReturn("ff-ff-ff-ff-ff-f1"); + Mockito.when(routerDeploymentDefinitionMock.getKeepMacAddressOnPublicNic()).thenReturn(false); + + networkHelperSpy.setPublicNicMacAddressSameAsPeerNic(nicProfile, networkMock, routerDeploymentDefinitionMock); + + Mockito.verify(routerDeploymentDefinitionMock, Mockito.never()).findSourceNatIP(); + Mockito.verify(networkHelperSpy, Mockito.never()).configurePublicVrNicBasedOnSourceNatIp(Mockito.any(), Mockito.any()); + Assert.assertEquals(newMacAddress, nicProfile.getMacAddress()); + } + + @Test + public void configurePublicNicTestReturnEmptyMapWhenNetworkIsNotPublic() throws InsufficientAddressCapacityException { + Mockito.when(routerDeploymentDefinitionMock.isPublicNetwork()).thenReturn(false); + + Map> nic = networkHelperSpy.configurePublicNic(routerDeploymentDefinitionMock, false); + Assert.assertTrue(nic.isEmpty()); + } + + @Test + public void configurePublicNicTestConfigureDeviceId() throws InsufficientAddressCapacityException { + PublicIp publicIpMock = Mockito.mock(PublicIp.class); + NetworkOffering networkOfferingMock = Mockito.mock(NetworkOffering.class); + + Mockito.when(routerDeploymentDefinitionMock.isPublicNetwork()).thenReturn(true); + Mockito.when(routerDeploymentDefinitionMock.getSourceNatIP()).thenReturn(publicIpMock); + Mockito.doNothing().when(networkHelperSpy).configurePublicVrNicBasedOnSourceNatIp(Mockito.any(), Mockito.any()); + Mockito.doReturn(List.of(networkOfferingMock)).when(networkModel).getSystemAccountNetworkOfferings(Mockito.any()); + Mockito.doReturn(List.of(networkMock)).when(networkOrchestrationService).setupNetwork( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean() + ); + Mockito.doNothing().when(networkHelperSpy).setPublicNicMacAddressSameAsPeerNic(Mockito.any(), Mockito.any(), Mockito.any()); + + Map> nic = networkHelperSpy.configurePublicNic(routerDeploymentDefinitionMock, true); + Integer nicDeviceId = nic.get(networkMock).get(0).getDeviceId(); + Assert.assertEquals(2, nicDeviceId.intValue()); + } + + @Test + public void configurePublicNicTestUpdateGuestNetworksIpv6Nic() throws InsufficientAddressCapacityException { + PublicIp publicIpMock = Mockito.mock(PublicIp.class); + NetworkOffering networkOfferingMock = Mockito.mock(NetworkOffering.class); + DeployDestination deployDestinationMock = Mockito.mock(DeployDestination.class); + + Mockito.when(routerDeploymentDefinitionMock.isPublicNetwork()).thenReturn(true); + Mockito.when(routerDeploymentDefinitionMock.getSourceNatIP()).thenReturn(publicIpMock); + Mockito.doNothing().when(networkHelperSpy).configurePublicVrNicBasedOnSourceNatIp(Mockito.any(), Mockito.any()); + Mockito.doReturn(List.of(networkOfferingMock)).when(networkModel).getSystemAccountNetworkOfferings(Mockito.any()); + Mockito.doReturn(List.of(networkMock)).when(networkOrchestrationService).setupNetwork( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean() + ); + Mockito.doNothing().when(networkHelperSpy).setPublicNicMacAddressSameAsPeerNic(Mockito.any(), Mockito.any(), Mockito.any()); + Mockito.when(routerDeploymentDefinitionMock.getGuestNetwork()).thenReturn(networkMock); + Mockito.when(routerDeploymentDefinitionMock.getDest()).thenReturn(deployDestinationMock); + + Map> nic = networkHelperSpy.configurePublicNic(routerDeploymentDefinitionMock, false); + Mockito.verify(ipv6ServiceMock).updateNicIpv6(Mockito.eq(nic.get(networkMock).get(0)), Mockito.any(), Mockito.eq(networkMock)); + } } diff --git a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java index 9569769b016..8b8259421cd 100644 --- a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java +++ b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java @@ -492,7 +492,7 @@ public class VpcManagerImplTest { mockVpcDnsResources(false, false); try { manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, - ip4Dns[0], null, null, null, true, 1500, null, null, null, false); + ip4Dns[0], null, null, null, true, 1500, null, null, null, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } @@ -503,7 +503,7 @@ public class VpcManagerImplTest { mockVpcDnsResources(true, false); try { manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, - ip4Dns[0], ip4Dns[1], ip6Dns[0], null, true, 1500, null, null, null, false); + ip4Dns[0], ip4Dns[1], ip6Dns[0], null, true, 1500, null, null, null, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } @@ -517,7 +517,7 @@ public class VpcManagerImplTest { Mockito.when(vpc.getUuid()).thenReturn("uuid"); try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, - ip4Dns[0], ip4Dns[1], null, null, true, 1500, null, null, null, false); + ip4Dns[0], ip4Dns[1], null, null, true, 1500, null, null, null, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } @@ -533,7 +533,7 @@ public class VpcManagerImplTest { doNothing().when(routedIpv4Manager).getOrCreateIpv4SubnetForVpc(any(), anyString()); try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, - ip4Dns[0], ip4Dns[1], null, null, true, 1500, null, null, null, false); + ip4Dns[0], ip4Dns[1], null, null, true, 1500, null, null, null, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } @@ -556,7 +556,7 @@ public class VpcManagerImplTest { try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, null, vpcDomain, - ip4Dns[0], ip4Dns[1], null, null, true, 1500, 24, null, bgpPeerIds, false); + ip4Dns[0], ip4Dns[1], null, null, true, 1500, 24, null, bgpPeerIds, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index 3b120b88db4..e4bc2096b9d 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -704,11 +704,19 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, boolean bypassVlanOverlapCheck, String networkDomain, Account owner, Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String gatewayv6, String cidrv6, Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, - String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize) throws ConcurrentOperationException, ResourceAllocationException { + String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize) throws ConcurrentOperationException { // TODO Auto-generated method stub return null; } + @Override + public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, boolean bypassVlanOverlapCheck, String networkDomain, + Account owner, Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String gatewayv6, + String cidrv6, Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, + String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize, boolean keepMacAddressOnPublicNic) throws ConcurrentOperationException { + return null; + } + /* (non-Javadoc) * @see com.cloud.network.NetworkManager#getPasswordResetProvider(com.cloud.network.Network) */ diff --git a/test/integration/smoke/test_vm_strict_host_tags.py b/test/integration/smoke/test_vm_strict_host_tags.py index 2377e9a7618..aac3e1ea65f 100644 --- a/test/integration/smoke/test_vm_strict_host_tags.py +++ b/test/integration/smoke/test_vm_strict_host_tags.py @@ -423,7 +423,7 @@ class TestRestoreVMStrictTags(cloudstackTestCase): vm.restore(self.apiclient, templateid=self.template_t2.id, expunge=True) self.fail("VM should not be restored") except Exception as e: - self.assertTrue("Unable to start VM with specified id" in str(e)) + self.assertTrue("Unable to create a deployment for " in str(e)) class TestMigrateVMStrictTags(cloudstackTestCase): diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 4a23b454252..f0a6ad3c79b 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1433,6 +1433,7 @@ "label.javadistribution": "Java Runtime Distribution", "label.javaversion": "Java Runtime Version", "label.keep": "Keep", +"label.keep.mac.address.on.public.nic": "Use same MAC address for public NIC of VRs", "label.kernelversion": "Kernel Version", "label.key": "Key", "label.keyboard": "Keyboard language", @@ -4167,6 +4168,7 @@ "message.warn.select.template": "Please select a Template for Registration.", "message.warn.zone.mtu.update": "Please note that this limit won't affect pre-existing Network's MTU settings", "message.webhook.deliveries.time.filter": "Webhook deliveries list can be filtered based on date-time. Select 'Custom' for specifying start and end date range.", +"message.webhook.filter.add": "Webhook deliveries can be controlled using filters (currently by Event type). Please select the parameters to add to the applied filters list.", "message.zone.creation.complete": "Zone creation complete.", "message.zone.detail.description": "Populate Zone details.", "message.zone.detail.hint": "A Zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more Pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone.", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index c7ca36c1278..d2910a3a2b9 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -904,6 +904,7 @@ "label.items.selected": "item(ns) selecionados", "label.japanese.keyboard": "Teclado japon\u00eas", "label.keep": "Manter", +"label.keep.mac.address.on.public.nic": "Utilizar o mesmo endere\u00e7o MAC para a NIC p\u00fablica dos VRs", "label.key": "Chave", "label.keyboard": "Linguagem do teclado", "label.keyboardtype": "Tipo de teclado", diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index 66dd6b3db9e..b03293efaca 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -949,7 +949,7 @@ style="margin-left: 5px" :actions="actions" :resource="record" - :enabled="quickViewEnabled() && actions.length > 0" + :enabled="quickViewEnabled(actions, columns, column.key)" @exec-action="$parent.execAction" /> diff --git a/ui/src/components/view/WebhookFiltersTab.vue b/ui/src/components/view/WebhookFiltersTab.vue index 2d276e2a3cf..513720a8625 100644 --- a/ui/src/components/view/WebhookFiltersTab.vue +++ b/ui/src/components/view/WebhookFiltersTab.vue @@ -18,6 +18,7 @@