Merge remote-tracking branch 'apache/main' into integration-veeam-kvm

This commit is contained in:
Abhishek Kumar 2026-04-30 11:32:56 +05:30
commit eaab07d99d
142 changed files with 5658 additions and 1708 deletions

View File

@ -468,3 +468,15 @@ iscsi.session.cleanup.enabled=false
# Time, in seconds, to wait before retrying to rebase during the incremental snapshot process.
# incremental.snapshot.retry.rebase.wait=60
# Path to the VDDK library directory for VMware to KVM conversion via VDDK,
# passed to virt-v2v as -io vddk-libdir=<path>
#vddk.lib.dir=
# Ordered VDDK transport preference for VMware to KVM conversion via VDDK, passed as
# -io vddk-transports=<value> to virt-v2v. Example: nbd:nbdssl
#vddk.transports=
# Optional vCenter SHA1 thumbprint for VMware to KVM conversion via VDDK, passed as
# -io vddk-thumbprint=<value>. If unset, CloudStack computes it on the KVM host via openssl.
#vddk.thumbprint=

View File

@ -822,6 +822,30 @@ public class AgentProperties{
*/
public static final Property<String> CONVERT_ENV_VIRTV2V_TMPDIR = new Property<>("convert.instance.env.virtv2v.tmpdir", null, String.class);
/**
* Path to the VDDK library directory on the KVM conversion host, used when converting VMs from VMware to KVM via VDDK.
* This directory is passed to virt-v2v as <code>-io vddk-libdir=&lt;path&gt;</code>.
* Data type: String.<br>
* Default value: <code>null</code>
*/
public static final Property<String> VDDK_LIB_DIR = new Property<>("vddk.lib.dir", null, String.class);
/**
* Ordered list of VDDK transports for virt-v2v, passed as <code>-io vddk-transports=&lt;value&gt;</code>.
* Example: <code>nbd:nbdssl</code>.
* Data type: String.<br>
* Default value: <code>null</code>
*/
public static final Property<String> VDDK_TRANSPORTS = new Property<>("vddk.transports", null, String.class);
/**
* vCenter TLS certificate thumbprint used by virt-v2v VDDK mode, passed as <code>-io vddk-thumbprint=&lt;value&gt;</code>.
* If unset, the KVM host computes it at runtime from the vCenter endpoint.
* Data type: String.<br>
* Default value: <code>null</code>
*/
public static final Property<String> VDDK_THUMBPRINT = new Property<>("vddk.thumbprint", null, String.class);
/**
* BGP controll CIDR
* Data type: String.<br>

View File

@ -36,13 +36,17 @@ public class RemoteInstanceTO implements Serializable {
private String vcenterPassword;
private String vcenterHost;
private String datacenterName;
private String clusterName;
private String hostName;
public RemoteInstanceTO() {
}
public RemoteInstanceTO(String instanceName) {
public RemoteInstanceTO(String instanceName, String clusterName, String hostName) {
this.hypervisorType = Hypervisor.HypervisorType.VMware;
this.instanceName = instanceName;
this.clusterName = clusterName;
this.hostName = hostName;
}
public RemoteInstanceTO(String instanceName, String instancePath, String vcenterHost, String vcenterUsername, String vcenterPassword, String datacenterName) {
@ -55,6 +59,12 @@ public class RemoteInstanceTO implements Serializable {
this.datacenterName = datacenterName;
}
public RemoteInstanceTO(String instanceName, String instancePath, String vcenterHost, String vcenterUsername, String vcenterPassword, String datacenterName, String clusterName, String hostName) {
this(instanceName, instancePath, vcenterHost, vcenterUsername, vcenterPassword, datacenterName);
this.clusterName = clusterName;
this.hostName = hostName;
}
public Hypervisor.HypervisorType getHypervisorType() {
return this.hypervisorType;
}
@ -82,4 +92,12 @@ public class RemoteInstanceTO implements Serializable {
public String getDatacenterName() {
return datacenterName;
}
public String getClusterName() {
return clusterName;
}
public String getHostName() {
return hostName;
}
}

View File

@ -57,6 +57,9 @@ public interface Host extends StateObject<Status>, Identity, Partition, HAResour
String HOST_UEFI_ENABLE = "host.uefi.enable";
String HOST_VOLUME_ENCRYPTION = "host.volume.encryption";
String HOST_INSTANCE_CONVERSION = "host.instance.conversion";
String HOST_VDDK_SUPPORT = "host.vddk.support";
String HOST_VDDK_LIB_DIR = "vddk.lib.dir";
String HOST_VDDK_VERSION = "host.vddk.version";
String HOST_OVFTOOL_VERSION = "host.ovftool.version";
String HOST_VIRTV2V_VERSION = "host.virtv2v.version";
String HOST_SSH_PORT = "host.ssh.port";

View File

@ -158,6 +158,7 @@ public class ApiConstants {
public static final String CUSTOM_ID = "customid";
public static final String CUSTOM_ACTION_ID = "customactionid";
public static final String CUSTOM_JOB_ID = "customjobid";
public static final String CURRENCY = "currency";
public static final String CURRENT_START_IP = "currentstartip";
public static final String CURRENT_END_IP = "currentendip";
public static final String ENCRYPT = "encrypt";
@ -530,7 +531,6 @@ public class ApiConstants {
public static final String SCHEDULE = "schedule";
public static final String SCHEDULE_ID = "scheduleid";
public static final String SCOPE = "scope";
public static final String USER_SECRET_KEY = "usersecretkey";
public static final String SEARCH_BASE = "searchbase";
public static final String SECONDARY_IP = "secondaryip";
public static final String SECURITY_GROUP_IDS = "securitygroupids";
@ -546,6 +546,7 @@ public class ApiConstants {
public static final String SESSIONKEY = "sessionkey";
public static final String SHOW_CAPACITIES = "showcapacities";
public static final String SHOW_REMOVED = "showremoved";
public static final String SHOW_RESOURCES = "showresources";
public static final String SHOW_RESOURCE_ICON = "showicon";
public static final String SHOW_INACTIVE = "showinactive";
public static final String SHOW_UNIQUE = "showunique";
@ -612,9 +613,11 @@ public class ApiConstants {
public static final String TOTAL = "total";
public static final String TOTAL_SUBNETS = "totalsubnets";
public static final String TO_CHECKPOINT_ID = "tocheckpointid";
public static final String TOTAL_QUOTA = "totalquota";
public static final String TYPE = "type";
public static final String TRUST_STORE = "truststore";
public static final String TRUST_STORE_PASSWORD = "truststorepass";
public static final String UNIT = "unit";
public static final String URL = "url";
public static final String USAGE_INTERFACE = "usageinterface";
public static final String USED = "used";
@ -635,6 +638,8 @@ public class ApiConstants {
public static final String USERNAME = "username";
public static final String USER_CONFIGURABLE = "userconfigurable";
public static final String USER_SECURITY_GROUP_LIST = "usersecuritygrouplist";
public static final String USER_SECRET_KEY = "usersecretkey";
public static final String USE_VDDK = "usevddk";
public static final String USE_VIRTUAL_NETWORK = "usevirtualnetwork";
public static final String USE_VIRTUAL_ROUTER_IP_RESOLVER = "userouteripresolver";
public static final String UPDATE_IN_SEQUENCE = "updateinsequence";
@ -1304,6 +1309,8 @@ public class ApiConstants {
public static final String OBJECT_LOCKING = "objectlocking";
public static final String ENCRYPTION = "encryption";
public static final String QUOTA = "quota";
public static final String QUOTA_CONSUMED = "quotaconsumed";
public static final String QUOTA_USAGE = "quotausage";
public static final String ACCESS_KEY = "accesskey";
public static final String SOURCE_NAT_IP = "sourcenatipaddress";

View File

@ -27,7 +27,6 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;
import javax.inject.Inject;
@ -504,12 +503,6 @@ public abstract class BaseCmd {
}
public String getResourceUuid(String parameterName) {
UUID resourceUuid = CallContext.current().getApiResourceUuid(parameterName);
if (resourceUuid != null) {
return resourceUuid.toString();
}
return null;
return CallContext.current().getApiResourceUuid(parameterName);
}
}

View File

@ -179,6 +179,14 @@ public class ImportVmCmd extends ImportUnmanagedInstanceCmd {
description = "(only for importing VMs from VMware to KVM) optional - the ID of the guest OS for the imported VM.")
private Long guestOsId;
@Parameter(name = ApiConstants.USE_VDDK,
type = CommandType.BOOLEAN,
since = "4.22.1",
description = "(only for importing VMs from VMware to KVM) optional - if true, uses VDDK on the KVM conversion host for converting the VM. " +
"This parameter is mutually exclusive with " + ApiConstants.FORCE_MS_TO_IMPORT_VM_FILES + ".")
private Boolean useVddk;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -255,6 +263,10 @@ public class ImportVmCmd extends ImportUnmanagedInstanceCmd {
return storagePoolId;
}
public boolean getUseVddk() {
return BooleanUtils.toBooleanDefaultIfNull(useVddk, true);
}
public String getTmpPath() {
return tmpPath;
}

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.api.command.user.bucket;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.ResourceAllocationException;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.storage.object.Bucket;
import com.cloud.user.Account;
@ -82,7 +83,7 @@ public class DeleteBucketCmd extends BaseCmd {
}
@Override
public void execute() throws ConcurrentOperationException {
public void execute() throws ConcurrentOperationException, ResourceAllocationException {
CallContext.current().setEventDetails("Bucket ID: " + getResourceUuid(ApiConstants.ID));
boolean result = _bucketService.deleteBucket(id, CallContext.current().getCallingAccount());
SuccessResponse response = new SuccessResponse(getCommandName());

View File

@ -235,7 +235,7 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer
* @param forced Indicates if backup will be force removed or not
* @return returns operation success
*/
boolean deleteBackup(final Long backupId, final Boolean forced);
boolean deleteBackup(final Long backupId, final Boolean forced) throws ResourceAllocationException;
void validateBackupForZone(Long zoneId);

View File

@ -63,7 +63,7 @@ public class CallContext {
private User user;
private long userId;
private final Map<Object, Object> context = new HashMap<Object, Object>();
private final Map<String, UUID> apiResourcesUuids = new HashMap<>();
private final Map<String, String> apiResourcesUuids = new HashMap<>();
private Project project;
private String apiName;
@ -389,11 +389,11 @@ public class CallContext {
isEventDisplayEnabled = eventDisplayEnabled;
}
public UUID getApiResourceUuid(String paramName) {
public String getApiResourceUuid(String paramName) {
return apiResourcesUuids.get(paramName);
}
public void putApiResourceUuid(String paramName, UUID uuid) {
public void putApiResourceUuid(String paramName, String uuid) {
apiResourcesUuids.put(paramName, uuid);
}

View File

@ -95,7 +95,7 @@ public interface BucketApiService {
*/
Bucket createBucket(CreateBucketCmd cmd);
boolean deleteBucket(long bucketId, Account caller);
boolean deleteBucket(long bucketId, Account caller) throws ResourceAllocationException;
boolean updateBucket(UpdateBucketCmd cmd, Account caller) throws ResourceAllocationException;

View File

@ -40,7 +40,7 @@ public class CreateIpv4SubnetForGuestNetworkCmdTest {
@Test
public void testCreateIpv4SubnetForGuestNetworkCmd() {
Long parentId = 1L;
UUID parentUuid = UUID.randomUUID();
String parentUuid = UUID.randomUUID().toString();
String subnet = "192.168.1.0/24";
Integer cidrSize = 26;

View File

@ -40,7 +40,7 @@ public class CreateIpv4SubnetForZoneCmdTest {
@Test
public void testCreateIpv4SubnetForZoneCmd() {
Long zoneId = 1L;
UUID zoneUuid = UUID.randomUUID();
String zoneUuid = UUID.randomUUID().toString();
String subnet = "192.168.1.0/24";
String accountName = "user";
Long projectId = 10L;

View File

@ -39,7 +39,7 @@ public class DedicateIpv4SubnetForZoneCmdTest {
@Test
public void testDedicateIpv4SubnetForZoneCmd() {
Long id = 1L;
UUID uuid = UUID.randomUUID();
String uuid = UUID.randomUUID().toString();
String accountName = "user";
Long projectId = 10L;
Long domainId = 11L;

View File

@ -39,7 +39,7 @@ public class DeleteIpv4SubnetForGuestNetworkCmdTest {
@Test
public void testDeleteIpv4SubnetForGuestNetworkCmd() {
Long id = 1L;
UUID uuid = UUID.randomUUID();
String uuid = UUID.randomUUID().toString();
DeleteIpv4SubnetForGuestNetworkCmd cmd = new DeleteIpv4SubnetForGuestNetworkCmd();
ReflectionTestUtils.setField(cmd, "id", id);

View File

@ -39,7 +39,7 @@ public class DeleteIpv4SubnetForZoneCmdTest {
@Test
public void testDeleteIpv4SubnetForZoneCmd() {
Long id = 1L;
UUID uuid = UUID.randomUUID();
String uuid = UUID.randomUUID().toString();
DeleteIpv4SubnetForZoneCmd cmd = new DeleteIpv4SubnetForZoneCmd();
ReflectionTestUtils.setField(cmd, "id", id);

View File

@ -39,7 +39,7 @@ public class ReleaseDedicatedIpv4SubnetForZoneCmdTest {
@Test
public void testReleaseDedicatedIpv4SubnetForZoneCmd() {
Long id = 1L;
UUID uuid = UUID.randomUUID();
String uuid = UUID.randomUUID().toString();
ReleaseDedicatedIpv4SubnetForZoneCmd cmd = new ReleaseDedicatedIpv4SubnetForZoneCmd();
ReflectionTestUtils.setField(cmd, "id", id);

View File

@ -40,7 +40,7 @@ public class UpdateIpv4SubnetForZoneCmdTest {
@Test
public void testUpdateIpv4SubnetForZoneCmd() {
Long id = 1L;
UUID uuid = UUID.randomUUID();
String uuid = UUID.randomUUID().toString();
String subnet = "192.168.1.0/24";
UpdateIpv4SubnetForZoneCmd cmd = new UpdateIpv4SubnetForZoneCmd();

View File

@ -46,7 +46,7 @@ public class ChangeBgpPeersForNetworkCmdTest {
@Test
public void testChangeBgpPeersForNetworkCmd() {
Long networkId = 10L;
UUID networkUuid = UUID.randomUUID();
String networkUuid = UUID.randomUUID().toString();
List<Long> bgpPeerIds = Arrays.asList(20L, 21L);
ChangeBgpPeersForNetworkCmd cmd = new ChangeBgpPeersForNetworkCmd();

View File

@ -46,7 +46,7 @@ public class ChangeBgpPeersForVpcCmdTest {
@Test
public void testChangeBgpPeersForVpcCmd() {
Long VpcId = 10L;
UUID vpcUuid = UUID.randomUUID();
String vpcUuid = UUID.randomUUID().toString();
List<Long> bgpPeerIds = Arrays.asList(20L, 21L);
ChangeBgpPeersForVpcCmd cmd = new ChangeBgpPeersForVpcCmd();

View File

@ -40,7 +40,7 @@ public class CreateBgpPeerCmdTest {
@Test
public void testCreateBgpPeerCmd() {
Long zoneId = 1L;
UUID zoneUuid = UUID.randomUUID();
String zoneUuid = UUID.randomUUID().toString();
String accountName = "user";
Long projectId = 10L;
Long domainId = 11L;

View File

@ -39,7 +39,7 @@ public class DedicateBgpPeerCmdTest {
@Test
public void testDedicateBgpPeerCmd() {
Long id = 1L;
UUID uuid = UUID.randomUUID();
String uuid = UUID.randomUUID().toString();
String accountName = "user";
Long projectId = 10L;
Long domainId = 11L;

View File

@ -39,7 +39,7 @@ public class DeleteBgpPeerCmdTest {
@Test
public void testDeleteBgpPeerCmd() {
Long id = 1L;
UUID uuid = UUID.randomUUID();
String uuid = UUID.randomUUID().toString();
DeleteBgpPeerCmd cmd = new DeleteBgpPeerCmd();
ReflectionTestUtils.setField(cmd, "id", id);

View File

@ -39,7 +39,7 @@ public class ReleaseDedicatedBgpPeerCmdTest {
@Test
public void testReleaseDedicatedBgpPeerCmd() {
Long id = 1L;
UUID uuid = UUID.randomUUID();
String uuid = UUID.randomUUID().toString();
ReleaseDedicatedBgpPeerCmd cmd = new ReleaseDedicatedBgpPeerCmd();
ReflectionTestUtils.setField(cmd, "id", id);

View File

@ -40,7 +40,7 @@ public class UpdateBgpPeerCmdTest {
@Test
public void testUpdateBgpPeerCmd() {
Long id = 1L;
UUID uuid = UUID.randomUUID();
String uuid = UUID.randomUUID().toString();
String ip4Address = "ip4-address";
String ip6Address = "ip6-address";
Long peerAsNumber = 15000L;

View File

@ -97,7 +97,7 @@ public class DownloadImageStoreObjectCmdTest {
@Test
public void testGetEventDescription() {
UUID uuid = UUID.randomUUID();
String uuid = UUID.randomUUID().toString();
ReflectionTestUtils.setField(cmd, "storeId", 1L);
ReflectionTestUtils.setField(cmd, "path", "path/to/object");

View File

@ -44,7 +44,7 @@ public class UnmanageVolumeCmdTest {
public void testUnmanageVolumeCmd() {
long accountId = 2L;
Long volumeId = 3L;
UUID volumeUuid = UUID.randomUUID();
String volumeUuid = UUID.randomUUID().toString();
Volume volume = Mockito.mock(Volume.class);
Mockito.when(responseGenerator.findVolumeById(volumeId)).thenReturn(volume);

View File

@ -118,7 +118,7 @@ public class CreateSnapshotCmdTest extends TestCase {
AccountService accountService = Mockito.mock(AccountService.class);
Account account = Mockito.mock(Account.class);
Mockito.when(accountService.getAccount(anyLong())).thenReturn(account);
UUID volumeUuid = UUID.randomUUID();
String volumeUuid = UUID.randomUUID().toString();
CallContext.current().putApiResourceUuid("volumeid", volumeUuid);

View File

@ -56,7 +56,7 @@ public class UpdateConditionCmdTest {
private static final Long threshold = 100L;
private static final long accountId = 5L;
private static final UUID conditionUuid = UUID.randomUUID();
private static final String conditionUuid = UUID.randomUUID().toString();
@Before
public void setUp() {

View File

@ -49,7 +49,7 @@ public class DeleteRoutingFirewallRuleCmdTest {
ReflectionTestUtils.setField(cmd, "_firewallService", _firewallService);
long id = 1L;
UUID uuid = UUID.randomUUID();
String uuid = UUID.randomUUID().toString();
long accountId = 2L;
long networkId = 3L;

View File

@ -18,6 +18,8 @@ package com.cloud.agent.api;
public class CheckConvertInstanceCommand extends Command {
boolean checkWindowsGuestConversionSupport = false;
boolean useVddk = false;
String vddkLibDir;
public CheckConvertInstanceCommand() {
}
@ -26,6 +28,11 @@ public class CheckConvertInstanceCommand extends Command {
this.checkWindowsGuestConversionSupport = checkWindowsGuestConversionSupport;
}
public CheckConvertInstanceCommand(boolean checkWindowsGuestConversionSupport, boolean useVddk) {
this.checkWindowsGuestConversionSupport = checkWindowsGuestConversionSupport;
this.useVddk = useVddk;
}
@Override
public boolean executeInSequence() {
return false;
@ -34,4 +41,20 @@ public class CheckConvertInstanceCommand extends Command {
public boolean getCheckWindowsGuestConversionSupport() {
return checkWindowsGuestConversionSupport;
}
public boolean isUseVddk() {
return useVddk;
}
public void setUseVddk(boolean useVddk) {
this.useVddk = useVddk;
}
public String getVddkLibDir() {
return vddkLibDir;
}
public void setVddkLibDir(String vddkLibDir) {
this.vddkLibDir = vddkLibDir;
}
}

View File

@ -31,6 +31,10 @@ public class ConvertInstanceCommand extends Command {
private boolean exportOvfToConversionLocation;
private int threadsCountToExportOvf = 0;
private String extraParams;
private boolean useVddk;
private String vddkLibDir;
private String vddkTransports;
private String vddkThumbprint;
public ConvertInstanceCommand() {
}
@ -90,6 +94,38 @@ public class ConvertInstanceCommand extends Command {
this.extraParams = extraParams;
}
public boolean isUseVddk() {
return useVddk;
}
public void setUseVddk(boolean useVddk) {
this.useVddk = useVddk;
}
public String getVddkLibDir() {
return vddkLibDir;
}
public void setVddkLibDir(String vddkLibDir) {
this.vddkLibDir = vddkLibDir;
}
public String getVddkTransports() {
return vddkTransports;
}
public void setVddkTransports(String vddkTransports) {
this.vddkTransports = vddkTransports;
}
public String getVddkThumbprint() {
return vddkThumbprint;
}
public void setVddkThumbprint(String vddkThumbprint) {
this.vddkThumbprint = vddkThumbprint;
}
@Override
public boolean executeInSequence() {
return false;

View File

@ -24,6 +24,8 @@ import com.cloud.resource.ResourceState;
public class PropagateResourceEventCommand extends Command {
long hostId;
ResourceState.Event event;
boolean forced;
boolean forceDeleteStorage;
protected PropagateResourceEventCommand() {
@ -34,6 +36,13 @@ public class PropagateResourceEventCommand extends Command {
this.event = event;
}
public PropagateResourceEventCommand(long hostId, ResourceState.Event event, boolean forced, boolean forceDeleteStorage) {
this.hostId = hostId;
this.event = event;
this.forced = forced;
this.forceDeleteStorage = forceDeleteStorage;
}
public long getHostId() {
return hostId;
}
@ -42,6 +51,14 @@ public class PropagateResourceEventCommand extends Command {
return event;
}
public boolean isForced() {
return forced;
}
public boolean isForceDeleteStorage() {
return forceDeleteStorage;
}
@Override
public boolean executeInSequence() {
// TODO Auto-generated method stub

View File

@ -36,6 +36,7 @@ public class LoadBalancerConfigCommand extends NetworkElementCommand {
public String lbStatsAuth = "admin1:AdMiN123";
public String lbStatsUri = "/admin?stats";
public String maxconn = "";
public Long idleTimeout = 50000L; /* 0=infinite, >0 = timeout in milliseconds */
public String lbProtocol;
public boolean keepAliveEnabled = false;
NicTO nic;
@ -50,7 +51,7 @@ public class LoadBalancerConfigCommand extends NetworkElementCommand {
}
public LoadBalancerConfigCommand(LoadBalancerTO[] loadBalancers, String publicIp, String guestIp, String privateIp, NicTO nic, Long vpcId, String maxconn,
boolean keepAliveEnabled) {
boolean keepAliveEnabled, Long idleTimeout) {
this.loadBalancers = loadBalancers;
this.lbStatsPublicIP = publicIp;
this.lbStatsPrivateIP = privateIp;
@ -59,6 +60,7 @@ public class LoadBalancerConfigCommand extends NetworkElementCommand {
this.vpcId = vpcId;
this.maxconn = maxconn;
this.keepAliveEnabled = keepAliveEnabled;
this.idleTimeout = idleTimeout;
}
public NicTO getNic() {

View File

@ -635,6 +635,19 @@ public class HAProxyConfigurator implements LoadBalancerConfigurator {
if (lbCmd.keepAliveEnabled) {
dSection.set(7, "\tno option httpclose");
}
if (lbCmd.idleTimeout > 0) {
dSection.set(9, "\ttimeout client " + Long.toString(lbCmd.idleTimeout));
dSection.set(10, "\ttimeout server " + Long.toString(lbCmd.idleTimeout));
} else if (lbCmd.idleTimeout == 0) {
// .remove() is not allowed, only .set() operations are allowed as the list
// is a fixed size. So lets just mark the entry as blank.
dSection.set(9, "");
dSection.set(10, "");
} else {
// Negative idleTimeout values are considered invalid; retain the
// default HAProxy timeout values from defaultsSection for predictability.
logger.warn("Negative idleTimeout ({}) configured; retaining default HAProxy timeouts.", lbCmd.idleTimeout);
}
if (logger.isDebugEnabled()) {
for (final String s : dSection) {

View File

@ -235,7 +235,7 @@ public class ConfigHelperTest {
lbs.toArray(arrayLbs);
final NicTO nic = new NicTO();
final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, null, "1000", false);
final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, null, "1000", false, 0L);
cmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, "10.1.10.2");
cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME);

View File

@ -779,7 +779,7 @@ public class VirtualRoutingResourceTest implements VirtualRouterDeployer {
final LoadBalancerTO[] arrayLbs = new LoadBalancerTO[lbs.size()];
lbs.toArray(arrayLbs);
final NicTO nic = new NicTO();
final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, null, "1000", false);
final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, null, "1000", false, 50000L);
cmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, "10.1.10.2");
cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME);
return cmd;
@ -795,7 +795,7 @@ public class VirtualRoutingResourceTest implements VirtualRouterDeployer {
lbs.toArray(arrayLbs);
final NicTO nic = new NicTO();
nic.setIp("10.1.10.2");
final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, Long.valueOf(1), "1000", false);
final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, Long.valueOf(1), "1000", false, 50000L);
cmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, "10.1.10.2");
cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME);
return cmd;

View File

@ -79,13 +79,14 @@ public class HAProxyConfiguratorTest {
LoadBalancerTO[] lba = new LoadBalancerTO[1];
lba[0] = lb;
HAProxyConfigurator hpg = new HAProxyConfigurator();
LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false);
LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false, 0L);
String result = genConfig(hpg, cmd);
assertTrue("keepalive disabled should result in 'option httpclose' in the resulting haproxy config", result.contains("\toption httpclose"));
cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "4", true);
cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "4", true, 0L);
result = genConfig(hpg, cmd);
assertTrue("keepalive enabled should result in 'no option httpclose' in the resulting haproxy config", result.contains("\tno option httpclose"));
// TODO
// create lb command
// setup tests for
@ -93,6 +94,27 @@ public class HAProxyConfiguratorTest {
// httpmode
}
/**
* Test method for {@link com.cloud.network.HAProxyConfigurator#generateConfiguration(com.cloud.agent.api.routing.LoadBalancerConfigCommand)}.
*/
@Test
public void testGenerateConfigurationLoadBalancerIdleTimeoutConfigCommand() {
LoadBalancerTO lb = new LoadBalancerTO("1", "10.2.0.1", 80, "http", "bla", false, false, false, null);
LoadBalancerTO[] lba = new LoadBalancerTO[1];
lba[0] = lb;
HAProxyConfigurator hpg = new HAProxyConfigurator();
LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "4", true, 0L);
String result = genConfig(hpg, cmd);
assertTrue("idleTimeout of 0 should not generate 'timeout server' in the resulting haproxy config", !result.contains("\ttimeout server"));
assertTrue("idleTimeout of 0 should not generate 'timeout client' in the resulting haproxy config", !result.contains("\ttimeout client"));
cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "4", true, 1234L);
result = genConfig(hpg, cmd);
assertTrue("idleTimeout of 1234 should result in 'timeout server 1234' in the resulting haproxy config", result.contains("\ttimeout server 1234"));
assertTrue("idleTimeout of 1234 should result in 'timeout client 1234' in the resulting haproxy config", result.contains("\ttimeout client 1234"));
}
/**
* Test method for {@link com.cloud.network.HAProxyConfigurator#generateConfiguration(com.cloud.agent.api.routing.LoadBalancerConfigCommand)}.
*/
@ -106,7 +128,7 @@ public class HAProxyConfiguratorTest {
LoadBalancerTO[] lba = new LoadBalancerTO[1];
lba[0] = lb;
HAProxyConfigurator hpg = new HAProxyConfigurator();
LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false);
LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false, 0L);
String result = genConfig(hpg, cmd);
assertTrue("'send-proxy' should result if protocol is 'tcp-proxy'", result.contains("send-proxy"));
}
@ -118,7 +140,7 @@ public class HAProxyConfiguratorTest {
LoadBalancerTO[] lba = new LoadBalancerTO[1];
lba[0] = lb;
HAProxyConfigurator hpg = new HAProxyConfigurator();
LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false);
LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false, 0L);
String result = genConfig(hpg, cmd);
Assert.assertTrue(result.contains("acl network_allowed src 1.1.1.1 2.2.2.2/24 \n\ttcp-request connection reject if !network_allowed"));
}
@ -131,7 +153,7 @@ public class HAProxyConfiguratorTest {
LoadBalancerTO[] lba = new LoadBalancerTO[1];
lba[0] = lb;
HAProxyConfigurator hpg = new HAProxyConfigurator();
LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false);
LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false, 0L);
String result = genConfig(hpg, cmd);
Assert.assertTrue(result.contains("bind 10.2.0.1:443 ssl crt /etc/cloudstack/ssl/10_2_0_1-443.pem"));
}

View File

@ -122,6 +122,14 @@ public interface NetworkOrchestrationService {
"Load Balancer(haproxy) maximum number of concurrent connections(global max)",
true,
Scope.Global);
ConfigKey<Long> NETWORK_LB_HAPROXY_IDLE_TIMEOUT = new ConfigKey<>(
"Network",
Long.class,
"network.loadbalancer.haproxy.idle.timeout",
"50000",
"Load Balancer(haproxy) idle timeout in milliseconds. Use 0 for infinite.",
true,
Scope.Global);
List<? extends Network> setupNetwork(Account owner, NetworkOffering offering, DeploymentPlan plan, String name, String displayText, boolean isDefault)
throws ConcurrentOperationException;

View File

@ -122,6 +122,8 @@ public interface ResourceManager extends ResourceService, Configurable {
public boolean executeUserRequest(long hostId, ResourceState.Event event) throws AgentUnavailableException;
boolean executeUserRequest(long hostId, ResourceState.Event event, boolean isForced, boolean isForceDeleteStorage) throws AgentUnavailableException;
boolean resourceStateTransitTo(Host host, Event event, long msId) throws NoTransitionException;
boolean umanageHost(long hostId);

View File

@ -805,8 +805,11 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
String uefiEnabled = detailsMap.get(Host.HOST_UEFI_ENABLE);
String virtv2vVersion = detailsMap.get(Host.HOST_VIRTV2V_VERSION);
String ovftoolVersion = detailsMap.get(Host.HOST_OVFTOOL_VERSION);
String vddkSupport = detailsMap.get(Host.HOST_VDDK_SUPPORT);
String vddkLibDir = detailsMap.get(Host.HOST_VDDK_LIB_DIR);
String vddkVersion = detailsMap.get(Host.HOST_VDDK_VERSION);
logger.debug("Got HOST_UEFI_ENABLE [{}] for host [{}]:", uefiEnabled, host);
if (ObjectUtils.anyNotNull(uefiEnabled, virtv2vVersion, ovftoolVersion)) {
if (ObjectUtils.anyNotNull(uefiEnabled, virtv2vVersion, ovftoolVersion, vddkSupport, vddkLibDir, vddkVersion)) {
_hostDao.loadDetails(host);
boolean updateNeeded = false;
if (StringUtils.isNotBlank(uefiEnabled) && !uefiEnabled.equals(host.getDetails().get(Host.HOST_UEFI_ENABLE))) {
@ -821,6 +824,26 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
host.getDetails().put(Host.HOST_OVFTOOL_VERSION, ovftoolVersion);
updateNeeded = true;
}
if (StringUtils.isNotBlank(vddkSupport) && !vddkSupport.equals(host.getDetails().get(Host.HOST_VDDK_SUPPORT))) {
host.getDetails().put(Host.HOST_VDDK_SUPPORT, vddkSupport);
updateNeeded = true;
}
if (!StringUtils.defaultString(vddkLibDir).equals(StringUtils.defaultString(host.getDetails().get(Host.HOST_VDDK_LIB_DIR)))) {
if (StringUtils.isBlank(vddkLibDir)) {
host.getDetails().remove(Host.HOST_VDDK_LIB_DIR);
} else {
host.getDetails().put(Host.HOST_VDDK_LIB_DIR, vddkLibDir);
}
updateNeeded = true;
}
if (!StringUtils.defaultString(vddkVersion).equals(StringUtils.defaultString(host.getDetails().get(Host.HOST_VDDK_VERSION)))) {
if (StringUtils.isBlank(vddkVersion)) {
host.getDetails().remove(Host.HOST_VDDK_VERSION);
} else {
host.getDetails().put(Host.HOST_VDDK_VERSION, vddkVersion);
}
updateNeeded = true;
}
if (updateNeeded) {
_hostDao.saveDetails(host);
}

View File

@ -1306,11 +1306,20 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust
boolean result;
try {
result = _resourceMgr.executeUserRequest(cmd.getHostId(), cmd.getEvent());
result = _resourceMgr.executeUserRequest(cmd.getHostId(), cmd.getEvent(), cmd.isForced(), cmd.isForceDeleteStorage());
logger.debug("Result is {}", result);
} catch (final AgentUnavailableException ex) {
logger.warn("Agent is unavailable", ex);
return null;
} catch (final RuntimeException ex) {
logger.error(String.format("Failed to execute propagated event %s for host %d", cmd.getEvent().name(), cmd.getHostId()), ex);
final Answer[] answers = new Answer[1];
String details = ex.getMessage();
if (details == null || details.isEmpty()) {
details = ex.toString();
}
answers[0] = new Answer(cmd, false, details);
return _gson.toJson(answers);
}
final Answer[] answers = new Answer[1];

View File

@ -2776,219 +2776,218 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
boolean ipv6 = false;
try (CheckedReservation networkReservation = new CheckedReservation(owner, domainId, Resource.ResourceType.network, null, null, 1L, reservationDao, _resourceLimitMgr)) {
if (StringUtils.isNoneBlank(ip6Gateway, ip6Cidr)) {
ipv6 = true;
}
// Validate zone
if (zone.getNetworkType() == NetworkType.Basic) {
// In Basic zone the network should have aclType=Domain, domainId=1, subdomainAccess=true
if (aclType == null || aclType != ACLType.Domain) {
throw new InvalidParameterValueException("Only AclType=Domain can be specified for network creation in Basic zone");
if (StringUtils.isNoneBlank(ip6Gateway, ip6Cidr)) {
ipv6 = true;
}
// Validate zone
if (zone.getNetworkType() == NetworkType.Basic) {
// In Basic zone the network should have aclType=Domain, domainId=1, subdomainAccess=true
if (aclType == null || aclType != ACLType.Domain) {
throw new InvalidParameterValueException("Only AclType=Domain can be specified for network creation in Basic zone");
}
// Only one guest network is supported in Basic zone
final List<NetworkVO> guestNetworks = _networksDao.listByZoneAndTrafficType(zone.getId(), TrafficType.Guest);
if (!guestNetworks.isEmpty()) {
throw new InvalidParameterValueException("Can't have more than one Guest network in zone with network type " + NetworkType.Basic);
}
// Only one guest network is supported in Basic zone
final List<NetworkVO> guestNetworks = _networksDao.listByZoneAndTrafficType(zone.getId(), TrafficType.Guest);
if (!guestNetworks.isEmpty()) {
throw new InvalidParameterValueException("Can't have more than one Guest network in zone with network type " + NetworkType.Basic);
}
// if zone is basic, only Shared network offerings w/o source nat service are allowed
if (!(ntwkOff.getGuestType() == GuestType.Shared && !_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.SourceNat))) {
throw new InvalidParameterValueException("For zone of type " + NetworkType.Basic + " only offerings of " + "guestType " + GuestType.Shared + " with disabled "
+ Service.SourceNat.getName() + " service are allowed");
}
// if zone is basic, only Shared network offerings w/o source nat service are allowed
if (!(ntwkOff.getGuestType() == GuestType.Shared && !_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.SourceNat))) {
throw new InvalidParameterValueException("For zone of type " + NetworkType.Basic + " only offerings of " + "guestType " + GuestType.Shared + " with disabled "
+ Service.SourceNat.getName() + " service are allowed");
}
if (domainId == null || domainId != Domain.ROOT_DOMAIN) {
throw new InvalidParameterValueException("Guest network in Basic zone should be dedicated to ROOT domain");
}
if (domainId == null || domainId != Domain.ROOT_DOMAIN) {
throw new InvalidParameterValueException("Guest network in Basic zone should be dedicated to ROOT domain");
}
if (subdomainAccess == null) {
subdomainAccess = true;
} else if (!subdomainAccess) {
throw new InvalidParameterValueException("Subdomain access should be set to true for the" + " guest network in the Basic zone");
}
if (subdomainAccess == null) {
subdomainAccess = true;
} else if (!subdomainAccess) {
throw new InvalidParameterValueException("Subdomain access should be set to true for the" + " guest network in the Basic zone");
}
if (vlanId == null) {
vlanId = Vlan.UNTAGGED;
} else {
if (!vlanId.equalsIgnoreCase(Vlan.UNTAGGED)) {
throw new InvalidParameterValueException("Only vlan " + Vlan.UNTAGGED + " can be created in " + "the zone of type " + NetworkType.Basic);
if (vlanId == null) {
vlanId = Vlan.UNTAGGED;
} else {
if (!vlanId.equalsIgnoreCase(Vlan.UNTAGGED)) {
throw new InvalidParameterValueException("Only vlan " + Vlan.UNTAGGED + " can be created in " + "the zone of type " + NetworkType.Basic);
}
}
} else if (zone.getNetworkType() == NetworkType.Advanced) {
if (zone.isSecurityGroupEnabled()) {
if (isolatedPvlan != null) {
throw new InvalidParameterValueException("Isolated Private VLAN is not supported with security group!");
}
// Only Account specific Isolated network with sourceNat service disabled are allowed in security group
// enabled zone
if ((ntwkOff.getGuestType() != GuestType.Shared) && (ntwkOff.getGuestType() != GuestType.L2)) {
throw new InvalidParameterValueException("Only shared or L2 guest network can be created in security group enabled zone");
}
if (_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.SourceNat)) {
throw new InvalidParameterValueException("Service SourceNat is not allowed in security group enabled zone");
}
}
//don't allow eip/elb networks in Advance zone
if (ntwkOff.isElasticIp() || ntwkOff.isElasticLb()) {
throw new InvalidParameterValueException("Elastic IP and Elastic LB services are supported in zone of type " + NetworkType.Basic);
}
}
} else if (zone.getNetworkType() == NetworkType.Advanced) {
if (zone.isSecurityGroupEnabled()) {
if (isolatedPvlan != null) {
throw new InvalidParameterValueException("Isolated Private VLAN is not supported with security group!");
}
// Only Account specific Isolated network with sourceNat service disabled are allowed in security group
// enabled zone
if ((ntwkOff.getGuestType() != GuestType.Shared) && (ntwkOff.getGuestType() != GuestType.L2)) {
throw new InvalidParameterValueException("Only shared or L2 guest network can be created in security group enabled zone");
}
if (_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.SourceNat)) {
throw new InvalidParameterValueException("Service SourceNat is not allowed in security group enabled zone");
if (ipv6 && !GuestType.Shared.equals(ntwkOff.getGuestType())) {
_networkModel.checkIp6CidrSizeEqualTo64(ip6Cidr);
}
//TODO(VXLAN): Support VNI specified
// VlanId can be specified only when network offering supports it
final boolean vlanSpecified = vlanId != null;
if (vlanSpecified != ntwkOff.isSpecifyVlan()) {
if (vlanSpecified) {
if (!isSharedNetworkWithoutSpecifyVlan(ntwkOff) && !isPrivateGatewayWithoutSpecifyVlan(ntwkOff)) {
throw new InvalidParameterValueException("Can't specify vlan; corresponding offering says specifyVlan=false");
}
} else {
throw new InvalidParameterValueException("Vlan has to be specified; corresponding offering says specifyVlan=true");
}
}
//don't allow eip/elb networks in Advance zone
if (ntwkOff.isElasticIp() || ntwkOff.isElasticLb()) {
throw new InvalidParameterValueException("Elastic IP and Elastic LB services are supported in zone of type " + NetworkType.Basic);
}
}
if (ipv6 && !GuestType.Shared.equals(ntwkOff.getGuestType())) {
_networkModel.checkIp6CidrSizeEqualTo64(ip6Cidr);
}
//TODO(VXLAN): Support VNI specified
// VlanId can be specified only when network offering supports it
final boolean vlanSpecified = vlanId != null;
if (vlanSpecified != ntwkOff.isSpecifyVlan()) {
if (vlanSpecified) {
if (!isSharedNetworkWithoutSpecifyVlan(ntwkOff) && !isPrivateGatewayWithoutSpecifyVlan(ntwkOff)) {
throw new InvalidParameterValueException("Can't specify vlan; corresponding offering says specifyVlan=false");
URI uri = encodeVlanIdIntoBroadcastUri(vlanId, pNtwk);
// Aux: generate secondary URI for secondary VLAN ID (if provided) for performing checks
URI secondaryUri = StringUtils.isNotBlank(isolatedPvlan) ? BroadcastDomainType.fromString(isolatedPvlan) : null;
if (isSharedNetworkWithoutSpecifyVlan(ntwkOff) || isPrivateGatewayWithoutSpecifyVlan(ntwkOff)) {
bypassVlanOverlapCheck = true;
}
} else {
throw new InvalidParameterValueException("Vlan has to be specified; corresponding offering says specifyVlan=true");
}
}
if (vlanSpecified) {
URI uri = encodeVlanIdIntoBroadcastUri(vlanId, pNtwk);
// Aux: generate secondary URI for secondary VLAN ID (if provided) for performing checks
URI secondaryUri = StringUtils.isNotBlank(isolatedPvlan) ? BroadcastDomainType.fromString(isolatedPvlan) : null;
if (isSharedNetworkWithoutSpecifyVlan(ntwkOff) || isPrivateGatewayWithoutSpecifyVlan(ntwkOff)) {
bypassVlanOverlapCheck = true;
}
//don't allow to specify vlan tag used by physical network for dynamic vlan allocation
if (!(bypassVlanOverlapCheck && (ntwkOff.getGuestType() == GuestType.Shared || isPrivateNetwork))
&& _dcDao.findVnet(zoneId, pNtwk.getId(), BroadcastDomainType.getValue(uri)).size() > 0) {
throw new InvalidParameterValueException("The VLAN tag to use for new guest network, " + vlanId + " is already being used for dynamic vlan allocation for the guest network in zone "
+ zone.getName());
}
if (secondaryUri != null && !(bypassVlanOverlapCheck && ntwkOff.getGuestType() == GuestType.Shared) &&
_dcDao.findVnet(zoneId, pNtwk.getId(), BroadcastDomainType.getValue(secondaryUri)).size() > 0) {
throw new InvalidParameterValueException(String.format(
"The VLAN tag for isolated PVLAN %s is already being used for dynamic vlan allocation for the guest network in zone %s",
isolatedPvlan, zone));
}
if (!UuidUtils.isUuid(vlanId)) {
// For Isolated and L2 networks, don't allow to create network with vlan that already exists in the zone
if (!hasGuestBypassVlanOverlapCheck(bypassVlanOverlapCheck, ntwkOff, isPrivateNetwork)) {
if (_networksDao.listByZoneAndUriAndGuestType(zoneId, uri.toString(), null).size() > 0) {
throw new InvalidParameterValueException(String.format(
"Network with vlan %s already exists or overlaps with other network vlans in zone %s",
vlanId, zone));
} else if (secondaryUri != null && _networksDao.listByZoneAndUriAndGuestType(zoneId, secondaryUri.toString(), null).size() > 0) {
throw new InvalidParameterValueException(String.format(
"Network with vlan %s already exists or overlaps with other network vlans in zone %s",
isolatedPvlan, zone));
} else {
final List<DataCenterVnetVO> dcVnets = _datacenterVnetDao.findVnet(zoneId, BroadcastDomainType.getValue(uri));
//for the network that is created as part of private gateway,
//the vnet is not coming from the data center vnet table, so the list can be empty
if (!dcVnets.isEmpty()) {
final DataCenterVnetVO dcVnet = dcVnets.get(0);
// Fail network creation if specified vlan is dedicated to a different account
if (dcVnet.getAccountGuestVlanMapId() != null) {
final Long accountGuestVlanMapId = dcVnet.getAccountGuestVlanMapId();
final AccountGuestVlanMapVO map = _accountGuestVlanMapDao.findById(accountGuestVlanMapId);
if (map.getAccountId() != owner.getAccountId()) {
throw new InvalidParameterValueException("Vlan " + vlanId + " is dedicated to a different account");
}
// Fail network creation if owner has a dedicated range of vlans but the specified vlan belongs to the system pool
} else {
final List<AccountGuestVlanMapVO> maps = _accountGuestVlanMapDao.listAccountGuestVlanMapsByAccount(owner.getAccountId());
if (maps != null && !maps.isEmpty()) {
final int vnetsAllocatedToAccount = _datacenterVnetDao.countVnetsAllocatedToAccount(zoneId, owner.getAccountId());
final int vnetsDedicatedToAccount = _datacenterVnetDao.countVnetsDedicatedToAccount(zoneId, owner.getAccountId());
if (vnetsAllocatedToAccount < vnetsDedicatedToAccount) {
throw new InvalidParameterValueException("Specified vlan " + vlanId + " doesn't belong" + " to the vlan range dedicated to the owner "
+ owner.getAccountName());
//don't allow to specify vlan tag used by physical network for dynamic vlan allocation
if (!(bypassVlanOverlapCheck && (ntwkOff.getGuestType() == GuestType.Shared || isPrivateNetwork))
&& _dcDao.findVnet(zoneId, pNtwk.getId(), BroadcastDomainType.getValue(uri)).size() > 0) {
throw new InvalidParameterValueException("The VLAN tag to use for new guest network, " + vlanId + " is already being used for dynamic vlan allocation for the guest network in zone "
+ zone.getName());
}
if (secondaryUri != null && !(bypassVlanOverlapCheck && ntwkOff.getGuestType() == GuestType.Shared) &&
_dcDao.findVnet(zoneId, pNtwk.getId(), BroadcastDomainType.getValue(secondaryUri)).size() > 0) {
throw new InvalidParameterValueException(String.format(
"The VLAN tag for isolated PVLAN %s is already being used for dynamic vlan allocation for the guest network in zone %s",
isolatedPvlan, zone));
}
if (!UuidUtils.isUuid(vlanId)) {
// For Isolated and L2 networks, don't allow to create network with vlan that already exists in the zone
if (!hasGuestBypassVlanOverlapCheck(bypassVlanOverlapCheck, ntwkOff, isPrivateNetwork)) {
if (_networksDao.listByZoneAndUriAndGuestType(zoneId, uri.toString(), null).size() > 0) {
throw new InvalidParameterValueException(String.format(
"Network with vlan %s already exists or overlaps with other network vlans in zone %s",
vlanId, zone));
} else if (secondaryUri != null && _networksDao.listByZoneAndUriAndGuestType(zoneId, secondaryUri.toString(), null).size() > 0) {
throw new InvalidParameterValueException(String.format(
"Network with vlan %s already exists or overlaps with other network vlans in zone %s",
isolatedPvlan, zone));
} else {
final List<DataCenterVnetVO> dcVnets = _datacenterVnetDao.findVnet(zoneId, BroadcastDomainType.getValue(uri));
//for the network that is created as part of private gateway,
//the vnet is not coming from the data center vnet table, so the list can be empty
if (!dcVnets.isEmpty()) {
final DataCenterVnetVO dcVnet = dcVnets.get(0);
// Fail network creation if specified vlan is dedicated to a different account
if (dcVnet.getAccountGuestVlanMapId() != null) {
final Long accountGuestVlanMapId = dcVnet.getAccountGuestVlanMapId();
final AccountGuestVlanMapVO map = _accountGuestVlanMapDao.findById(accountGuestVlanMapId);
if (map.getAccountId() != owner.getAccountId()) {
throw new InvalidParameterValueException("Vlan " + vlanId + " is dedicated to a different account");
}
// Fail network creation if owner has a dedicated range of vlans but the specified vlan belongs to the system pool
} else {
final List<AccountGuestVlanMapVO> maps = _accountGuestVlanMapDao.listAccountGuestVlanMapsByAccount(owner.getAccountId());
if (maps != null && !maps.isEmpty()) {
final int vnetsAllocatedToAccount = _datacenterVnetDao.countVnetsAllocatedToAccount(zoneId, owner.getAccountId());
final int vnetsDedicatedToAccount = _datacenterVnetDao.countVnetsDedicatedToAccount(zoneId, owner.getAccountId());
if (vnetsAllocatedToAccount < vnetsDedicatedToAccount) {
throw new InvalidParameterValueException("Specified vlan " + vlanId + " doesn't belong" + " to the vlan range dedicated to the owner "
+ owner.getAccountName());
}
}
}
}
}
} else {
// don't allow to creating shared network with given Vlan ID, if there already exists a isolated network or
// shared network with same Vlan ID in the zone
if (!bypassVlanOverlapCheck && _networksDao.listByZoneAndUriAndGuestType(zoneId, uri.toString(), GuestType.Isolated).size() > 0) {
throw new InvalidParameterValueException(String.format(
"There is an existing isolated/shared network that overlaps with vlan id:%s in zone %s", vlanId, zone));
}
}
} else {
// don't allow to creating shared network with given Vlan ID, if there already exists a isolated network or
// shared network with same Vlan ID in the zone
if (!bypassVlanOverlapCheck && _networksDao.listByZoneAndUriAndGuestType(zoneId, uri.toString(), GuestType.Isolated).size() > 0) {
}
}
// If networkDomain is not specified, take it from the global configuration
if (_networkModel.areServicesSupportedByNetworkOffering(networkOfferingId, Service.Dns)) {
final Map<Network.Capability, String> dnsCapabilities = _networkModel.getNetworkOfferingServiceCapabilities(_entityMgr.findById(NetworkOffering.class, networkOfferingId),
Service.Dns);
final String isUpdateDnsSupported = dnsCapabilities.get(Capability.AllowDnsSuffixModification);
if (isUpdateDnsSupported == null || !Boolean.valueOf(isUpdateDnsSupported)) {
if (networkDomain != null) {
// TBD: NetworkOfferingId and zoneId. Send uuids instead.
throw new InvalidParameterValueException(String.format(
"There is an existing isolated/shared network that overlaps with vlan id:%s in zone %s", vlanId, zone));
"Domain name change is not supported by network offering id=%d in zone %s",
networkOfferingId, zone));
}
}
}
}
// If networkDomain is not specified, take it from the global configuration
if (_networkModel.areServicesSupportedByNetworkOffering(networkOfferingId, Service.Dns)) {
final Map<Network.Capability, String> dnsCapabilities = _networkModel.getNetworkOfferingServiceCapabilities(_entityMgr.findById(NetworkOffering.class, networkOfferingId),
Service.Dns);
final String isUpdateDnsSupported = dnsCapabilities.get(Capability.AllowDnsSuffixModification);
if (isUpdateDnsSupported == null || !Boolean.valueOf(isUpdateDnsSupported)) {
if (networkDomain != null) {
// TBD: NetworkOfferingId and zoneId. Send uuids instead.
throw new InvalidParameterValueException(String.format(
"Domain name change is not supported by network offering id=%d in zone %s",
networkOfferingId, zone));
}
} else {
if (networkDomain == null) {
// 1) Get networkDomain from the corresponding account/domain/zone
if (aclType == ACLType.Domain) {
networkDomain = _networkModel.getDomainNetworkDomain(domainId, zoneId);
} else if (aclType == ACLType.Account) {
networkDomain = _networkModel.getAccountNetworkDomain(owner.getId(), zoneId);
}
// 2) If null, generate networkDomain using domain suffix from the global config variables
if (networkDomain == null) {
networkDomain = "cs" + Long.toHexString(owner.getId()) + GuestDomainSuffix.valueIn(zoneId);
}
} else {
// validate network domain
if (!NetUtils.verifyDomainName(networkDomain)) {
throw new InvalidParameterValueException("Invalid network domain. Total length shouldn't exceed 190 chars. Each domain "
+ "label must be between 1 and 63 characters long, can contain ASCII letters 'a' through 'z', the digits '0' through '9', "
+ "and the hyphen ('-'); can't start or end with \"-\"");
if (networkDomain == null) {
// 1) Get networkDomain from the corresponding account/domain/zone
if (aclType == ACLType.Domain) {
networkDomain = _networkModel.getDomainNetworkDomain(domainId, zoneId);
} else if (aclType == ACLType.Account) {
networkDomain = _networkModel.getAccountNetworkDomain(owner.getId(), zoneId);
}
// 2) If null, generate networkDomain using domain suffix from the global config variables
if (networkDomain == null) {
networkDomain = "cs" + Long.toHexString(owner.getId()) + GuestDomainSuffix.valueIn(zoneId);
}
} else {
// validate network domain
if (!NetUtils.verifyDomainName(networkDomain)) {
throw new InvalidParameterValueException("Invalid network domain. Total length shouldn't exceed 190 chars. Each domain "
+ "label must be between 1 and 63 characters long, can contain ASCII letters 'a' through 'z', the digits '0' through '9', "
+ "and the hyphen ('-'); can't start or end with \"-\"");
}
}
}
}
}
// In Advance zone Cidr for Shared networks and Isolated networks w/o source nat service can't be NULL - 2.2.x
// limitation, remove after we introduce support for multiple ip ranges
// with different Cidrs for the same Shared network
final boolean cidrRequired = zone.getNetworkType() == NetworkType.Advanced
&& ntwkOff.getTrafficType() == TrafficType.Guest
&& (ntwkOff.getGuestType() == GuestType.Shared || (ntwkOff.getGuestType() == GuestType.Isolated
&& !_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.SourceNat)
&& !_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.Gateway)));
if (cidr == null && ip6Cidr == null && cidrRequired) {
if (ntwkOff.getGuestType() == GuestType.Shared) {
throw new InvalidParameterValueException(String.format("Gateway/netmask are required when creating %s networks.", Network.GuestType.Shared));
} else {
throw new InvalidParameterValueException("gateway/netmask are required when create network of" + " type " + GuestType.Isolated + " with service " + Service.SourceNat.getName() + " disabled");
// In Advance zone Cidr for Shared networks and Isolated networks w/o source nat service can't be NULL - 2.2.x
// limitation, remove after we introduce support for multiple ip ranges
// with different Cidrs for the same Shared network
final boolean cidrRequired = zone.getNetworkType() == NetworkType.Advanced
&& ntwkOff.getTrafficType() == TrafficType.Guest
&& (ntwkOff.getGuestType() == GuestType.Shared || (ntwkOff.getGuestType() == GuestType.Isolated
&& !_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.SourceNat)
&& !_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.Gateway)));
if (cidr == null && ip6Cidr == null && cidrRequired) {
if (ntwkOff.getGuestType() == GuestType.Shared) {
throw new InvalidParameterValueException(String.format("Gateway/netmask are required when creating %s networks.", Network.GuestType.Shared));
} else {
throw new InvalidParameterValueException("gateway/netmask are required when create network of" + " type " + GuestType.Isolated + " with service " + Service.SourceNat.getName() + " disabled");
}
}
}
checkL2OfferingServices(ntwkOff);
checkL2OfferingServices(ntwkOff);
// No cidr can be specified in Basic zone
if (zone.getNetworkType() == NetworkType.Basic && cidr != null) {
throw new InvalidParameterValueException("StartIp/endIp/gateway/netmask can't be specified for zone of type " + NetworkType.Basic);
}
// No cidr can be specified in Basic zone
if (zone.getNetworkType() == NetworkType.Basic && cidr != null) {
throw new InvalidParameterValueException("StartIp/endIp/gateway/netmask can't be specified for zone of type " + NetworkType.Basic);
}
// Check if cidr is RFC1918 compliant if the network is Guest Isolated for IPv4
if (cidr != null && (ntwkOff.getGuestType() == Network.GuestType.Isolated && ntwkOff.getTrafficType() == TrafficType.Guest) &&
!NetUtils.validateGuestCidr(cidr, !ConfigurationManager.AllowNonRFC1918CompliantIPs.value())) {
// Check if cidr is RFC1918 compliant if the network is Guest Isolated for IPv4
if (cidr != null && (ntwkOff.getGuestType() == Network.GuestType.Isolated && ntwkOff.getTrafficType() == TrafficType.Guest) &&
!NetUtils.validateGuestCidr(cidr, !ConfigurationManager.AllowNonRFC1918CompliantIPs.value())) {
throw new InvalidParameterValueException("Virtual Guest Cidr " + cidr + " is not RFC 1918 or 6598 compliant");
}
}
final String networkDomainFinal = networkDomain;
final String vlanIdFinal = vlanId;
@ -3004,75 +3003,75 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
final NetworkVO userNetwork = new NetworkVO();
userNetwork.setNetworkDomain(networkDomainFinal);
if (cidr != null && gateway != null) {
userNetwork.setCidr(cidr);
userNetwork.setGateway(gateway);
}
if (cidr != null && gateway != null) {
userNetwork.setCidr(cidr);
userNetwork.setGateway(gateway);
}
if (StringUtils.isNoneBlank(ip6Gateway, ip6Cidr)) {
userNetwork.setIp6Cidr(ip6Cidr);
userNetwork.setIp6Gateway(ip6Gateway);
}
if (StringUtils.isNoneBlank(ip6Gateway, ip6Cidr)) {
userNetwork.setIp6Cidr(ip6Cidr);
userNetwork.setIp6Gateway(ip6Gateway);
}
if (externalId != null) {
userNetwork.setExternalId(externalId);
}
if (externalId != null) {
userNetwork.setExternalId(externalId);
}
if (StringUtils.isNotBlank(routerIp)) {
userNetwork.setRouterIp(routerIp);
}
if (StringUtils.isNotBlank(routerIp)) {
userNetwork.setRouterIp(routerIp);
}
if (StringUtils.isNotBlank(routerIpv6)) {
userNetwork.setRouterIpv6(routerIpv6);
}
if (StringUtils.isNotBlank(routerIpv6)) {
userNetwork.setRouterIpv6(routerIpv6);
}
if (vrIfaceMTUs != null) {
if (vrIfaceMTUs.first() != null && vrIfaceMTUs.first() > 0) {
userNetwork.setPublicMtu(vrIfaceMTUs.first());
if (vrIfaceMTUs != null) {
if (vrIfaceMTUs.first() != null && vrIfaceMTUs.first() > 0) {
userNetwork.setPublicMtu(vrIfaceMTUs.first());
} else {
userNetwork.setPublicMtu(Integer.valueOf(NetworkService.VRPublicInterfaceMtu.defaultValue()));
}
if (vrIfaceMTUs.second() != null && vrIfaceMTUs.second() > 0) {
userNetwork.setPrivateMtu(vrIfaceMTUs.second());
} else {
userNetwork.setPrivateMtu(Integer.valueOf(NetworkService.VRPrivateInterfaceMtu.defaultValue()));
}
} else {
userNetwork.setPublicMtu(Integer.valueOf(NetworkService.VRPublicInterfaceMtu.defaultValue()));
}
if (vrIfaceMTUs.second() != null && vrIfaceMTUs.second() > 0) {
userNetwork.setPrivateMtu(vrIfaceMTUs.second());
} else {
userNetwork.setPrivateMtu(Integer.valueOf(NetworkService.VRPrivateInterfaceMtu.defaultValue()));
}
} else {
userNetwork.setPublicMtu(Integer.valueOf(NetworkService.VRPublicInterfaceMtu.defaultValue()));
userNetwork.setPrivateMtu(Integer.valueOf(NetworkService.VRPrivateInterfaceMtu.defaultValue()));
}
if (!GuestType.L2.equals(userNetwork.getGuestType())) {
if (StringUtils.isNotBlank(ip4Dns1)) {
userNetwork.setDns1(ip4Dns1);
}
if (StringUtils.isNotBlank(ip4Dns2)) {
userNetwork.setDns2(ip4Dns2);
}
if (StringUtils.isNotBlank(ip6Dns1)) {
userNetwork.setIp6Dns1(ip6Dns1);
}
if (StringUtils.isNotBlank(ip6Dns2)) {
userNetwork.setIp6Dns2(ip6Dns2);
}
}
if (vlanIdFinal != null) {
if (isolatedPvlan == null) {
URI uri = null;
if (UuidUtils.isUuid(vlanIdFinal)) {
//Logical router's UUID provided as VLAN_ID
userNetwork.setVlanIdAsUUID(vlanIdFinal); //Set transient field
} else {
uri = encodeVlanIdIntoBroadcastUri(vlanIdFinal, pNtwk);
if (!GuestType.L2.equals(userNetwork.getGuestType())) {
if (StringUtils.isNotBlank(ip4Dns1)) {
userNetwork.setDns1(ip4Dns1);
}
if (_networksDao.listByPhysicalNetworkPvlan(physicalNetworkId, uri.toString()).size() > 0) {
throw new InvalidParameterValueException(String.format(
"Network with vlan %s already exists or overlaps with other network pvlans in zone %s",
vlanIdFinal, zone));
if (StringUtils.isNotBlank(ip4Dns2)) {
userNetwork.setDns2(ip4Dns2);
}
if (StringUtils.isNotBlank(ip6Dns1)) {
userNetwork.setIp6Dns1(ip6Dns1);
}
if (StringUtils.isNotBlank(ip6Dns2)) {
userNetwork.setIp6Dns2(ip6Dns2);
}
}
if (vlanIdFinal != null) {
if (isolatedPvlan == null) {
URI uri = null;
if (UuidUtils.isUuid(vlanIdFinal)) {
//Logical router's UUID provided as VLAN_ID
userNetwork.setVlanIdAsUUID(vlanIdFinal); //Set transient field
} else {
uri = encodeVlanIdIntoBroadcastUri(vlanIdFinal, pNtwk);
}
if (_networksDao.listByPhysicalNetworkPvlan(physicalNetworkId, uri.toString()).size() > 0) {
throw new InvalidParameterValueException(String.format(
"Network with vlan %s already exists or overlaps with other network pvlans in zone %s",
vlanIdFinal, zone));
}
userNetwork.setBroadcastUri(uri);
if (!vlanIdFinal.equalsIgnoreCase(Vlan.UNTAGGED)) {
@ -4940,6 +4939,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
return new ConfigKey<?>[]{NetworkGcWait, NetworkGcInterval, NetworkLockTimeout, DeniedRoutes,
GuestDomainSuffix, NetworkThrottlingRate, MinVRVersion,
PromiscuousMode, MacAddressChanges, ForgedTransmits, MacLearning, RollingRestartEnabled,
TUNGSTEN_ENABLED, NSX_ENABLED, NETRIS_ENABLED, NETWORK_LB_HAPROXY_MAX_CONN};
TUNGSTEN_ENABLED, NSX_ENABLED, NETRIS_ENABLED, NETWORK_LB_HAPROXY_MAX_CONN,
NETWORK_LB_HAPROXY_IDLE_TIMEOUT};
}
}

View File

@ -16,6 +16,7 @@
// under the License.
package com.cloud.upgrade;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@ -96,7 +97,9 @@ public final class DatabaseVersionHierarchy {
// we cannot find the version specified, so get the
// most recent one immediately before this version
if (!contains(fromVersion)) {
return getPath(getRecentVersion(fromVersion), toVersion);
DbUpgrade[] dbUpgrades = getPath(getRecentVersion(fromVersion), toVersion);
return Arrays.stream(dbUpgrades).filter(up -> CloudStackVersion.compare(up.getUpgradedVersion(), fromVersion.toString()) > 0)
.toArray(DbUpgrade[]::new);
}
final Predicate<? super VersionNode> predicate;

View File

@ -57,8 +57,4 @@ public class Upgrade42020to42030 extends DbUpgradeAbstractImpl implements DbUpgr
public InputStream[] getCleanupScripts() {
return null;
}
@Override
public void updateSystemVmTemplates(Connection conn) {
}
}

View File

@ -73,5 +73,7 @@
<bean id="volumeDaoImpl" class="com.cloud.storage.dao.VolumeDaoImpl" />
<bean id="reservationDao" class="org.apache.cloudstack.reservation.dao.ReservationDaoImpl" />
<bean id="backupOfferingDaoImpl" class="org.apache.cloudstack.backup.dao.BackupOfferingDaoImpl" />
<bean id="vpcOfferingDaoImpl" class="com.cloud.network.vpc.dao.VpcOfferingDaoImpl" />
<bean id="vpcOfferingDetailsDaoImpl" class="com.cloud.network.vpc.dao.VpcOfferingDetailsDaoImpl"/>
<bean id="backupOfferingDetailsDaoImpl" class="org.apache.cloudstack.backup.dao.BackupOfferingDetailsDaoImpl" />
</beans>

View File

@ -237,13 +237,11 @@
<bean id="volumeStatsDaoImpl" class="com.cloud.storage.dao.VolumeStatsDaoImpl" />
<bean id="vpcDaoImpl" class="com.cloud.network.vpc.dao.VpcDaoImpl" />
<bean id="vpcGatewayDaoImpl" class="com.cloud.network.vpc.dao.VpcGatewayDaoImpl" />
<bean id="vpcOfferingDaoImpl" class="com.cloud.network.vpc.dao.VpcOfferingDaoImpl" />
<bean id="vpcOfferingJoinDaoImpl" class="com.cloud.api.query.dao.VpcOfferingJoinDaoImpl" />
<bean id="vpcOfferingServiceMapDaoImpl" class="com.cloud.network.vpc.dao.VpcOfferingServiceMapDaoImpl" />
<bean id="vpcServiceMapDaoImpl" class="com.cloud.network.vpc.dao.VpcServiceMapDaoImpl" />
<bean id="vpnUserDaoImpl" class="com.cloud.network.dao.VpnUserDaoImpl" />
<bean id="applicationLbRuleDaoImpl" class="org.apache.cloudstack.lb.dao.ApplicationLoadBalancerRuleDaoImpl" />
<bean id="vpcOfferingDetailsDaoImpl" class="com.cloud.network.vpc.dao.VpcOfferingDetailsDaoImpl"/>
<bean id="networkDetailsDaoImpl" class="com.cloud.network.dao.NetworkDetailsDaoImpl" />
<bean id="tungstenGuestNetworkIpAddressDaoImpl" class="com.cloud.network.dao.TungstenGuestNetworkIpAddressDaoImpl"/>
<bean id="tungstenSecurityGroupRuleDaoImpl" class="com.cloud.network.security.dao.TungstenSecurityGroupRuleDaoImpl"/>

View File

@ -31,7 +31,7 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.account', 'api_key_access', 'boolean
CALL `cloud_usage`.`IDEMPOTENT_ADD_COLUMN`('cloud_usage.account', 'api_key_access', 'boolean DEFAULT NULL COMMENT "is api key access allowed for the account" ');
-- Create a new group for Usage Server related configurations
INSERT INTO `cloud`.`configuration_group` (`name`, `description`, `precedence`) VALUES ('Usage Server', 'Usage Server related configuration', 9);
INSERT IGNORE INTO `cloud`.`configuration_group` (`name`, `description`, `precedence`) VALUES ('Usage Server', 'Usage Server related configuration', 9);
UPDATE `cloud`.`configuration_subgroup` set `group_id` = (SELECT `id` FROM `cloud`.`configuration_group` WHERE `name` = 'Usage Server'), `precedence` = 1 WHERE `name`='Usage';
UPDATE `cloud`.`configuration` SET `group_id` = (SELECT `id` FROM `cloud`.`configuration_group` WHERE `name` = 'Usage Server') where `subgroup_id` = (SELECT `id` FROM `cloud`.`configuration_subgroup` WHERE `name` = 'Usage');

View File

@ -53,3 +53,9 @@ DELETE FROM `cloud`.`configuration` WHERE name = 'consoleproxy.cmd.port';
-- Drops the unused "backup_interval_type" column of the "cloud.backups" table
ALTER TABLE `cloud`.`backups` DROP COLUMN `backup_interval_type`;
-- Update `user.password.reset.mail.template` configuration value to match new logic
UPDATE `cloud`.`configuration`
SET value = CONCAT_WS('\n', 'Hello {{username}}!', 'You have requested to reset your password. Please click the following link to reset your password:', '{{{resetLink}}}', 'If you did not request a password reset, please ignore this email.', '', 'Regards,', 'The CloudStack Team')
WHERE name = 'user.password.reset.mail.template'
AND value IN (CONCAT_WS('\n', 'Hello {{username}}!', 'You have requested to reset your password. Please click the following link to reset your password:', 'http://{{{resetLink}}}', 'If you did not request a password reset, please ignore this email.', '', 'Regards,', 'The CloudStack Team'), CONCAT_WS('\n', 'Hello {{username}}!', 'You have requested to reset your password. Please click the following link to reset your password:', '{{{domainUrl}}}{{{resetLink}}}', 'If you did not request a password reset, please ignore this email.', '', 'Regards,', 'The CloudStack Team'));

View File

@ -118,6 +118,16 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','conserve_mode', 'tin
--- Disable/enable NICs
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nics','enabled', 'TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''Indicates whether the NIC is enabled or not'' ');
--- Quota tariff/usage mapping
CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`tariff_id` bigint(20) unsigned NOT NULL COMMENT 'ID of the tariff of the Quota usage detail calculated, foreign key to quota_tariff table',
`quota_usage_id` bigint(20) unsigned NOT NULL COMMENT 'ID of the aggregation of Quota usage details, foreign key to quota_usage table',
`quota_used` decimal(20,8) NOT NULL COMMENT 'Amount of quota used',
PRIMARY KEY (`id`),
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 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',

View File

@ -0,0 +1,35 @@
-- 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.
-- VIEW `cloud_usage`.`quota_usage_view`;
DROP VIEW IF EXISTS `cloud_usage`.`quota_usage_view`;
CREATE VIEW `cloud_usage`.`quota_usage_view` AS
SELECT qu.id,
qu.usage_item_id,
qu.zone_id,
qu.account_id,
qu.domain_id,
qu.usage_type,
qu.quota_used,
qu.start_date,
qu.end_date,
cu.usage_id AS resource_id,
cu.network_id as network_id,
cu.offering_id as offering_id
FROM `cloud_usage`.`quota_usage` qu
INNER JOIN `cloud_usage`.`cloud_usage` cu ON (cu.id = qu.usage_item_id);

View File

@ -44,6 +44,7 @@ import com.cloud.upgrade.dao.Upgrade41120to41130;
import com.cloud.upgrade.dao.Upgrade41120to41200;
import com.cloud.upgrade.dao.Upgrade41510to41520;
import com.cloud.upgrade.dao.Upgrade41610to41700;
import com.cloud.upgrade.dao.Upgrade42010to42100;
import com.cloud.upgrade.dao.Upgrade452to453;
import com.cloud.upgrade.dao.Upgrade453to460;
import com.cloud.upgrade.dao.Upgrade460to461;
@ -380,4 +381,23 @@ public class DatabaseUpgradeCheckerTest {
assertFalse("DatabaseUpgradeChecker should not be a standalone component", checker.isStandalone());
}
@Test
public void testCalculateUpgradePath42010to42100() {
final CloudStackVersion dbVersion = CloudStackVersion.parse("4.20.1.0");
assertNotNull(dbVersion);
final CloudStackVersion currentVersion = CloudStackVersion.parse("4.21.0.0");
assertNotNull(currentVersion);
final DatabaseUpgradeChecker checker = new DatabaseUpgradeChecker();
final DbUpgrade[] upgrades = checker.calculateUpgradePath(dbVersion, currentVersion);
assertNotNull(upgrades);
assertEquals(1, upgrades.length);
assertTrue(upgrades[0] instanceof Upgrade42010to42100);
assertArrayEquals(new String[]{"4.20.1.0", "4.21.0.0"}, upgrades[0].getUpgradableVersionRange());
assertEquals(currentVersion.toString(), upgrades[0].getUpgradedVersion());
}
}

View File

@ -205,6 +205,12 @@ public class SearchCriteria<K> {
}
public void setJoinParametersIfNotNull(String joinName, String conditionName, Object... params) {
if (ArrayUtils.isNotEmpty(params) && (params.length > 1 || params[0] != null)) {
setJoinParameters(joinName, conditionName, params);
}
}
public SearchCriteria<?> getJoin(String joinName) {
return _joins.get(joinName).getT();
}

View File

@ -28,11 +28,12 @@ import java.util.stream.Collectors;
import com.cloud.dc.ClusterDetailsDao;
import com.cloud.dc.ClusterDetailsVO;
import com.cloud.host.HostTagVO;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.vpc.VpcOfferingVO;
import com.cloud.network.vpc.VpcVO;
import javax.inject.Inject;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.storage.StoragePoolTagVO;
import org.apache.cloudstack.acl.RoleVO;
import org.apache.cloudstack.acl.dao.RoleDao;
@ -66,6 +67,7 @@ import com.cloud.domain.dao.DomainDao;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.host.dao.HostTagsDao;
import com.cloud.network.vpc.dao.VpcOfferingDao;
import com.cloud.offerings.NetworkOfferingVO;
import com.cloud.offerings.dao.NetworkOfferingDao;
import com.cloud.server.ResourceTag;
@ -191,6 +193,9 @@ public class PresetVariableHelper {
@Inject
ClusterDetailsDao clusterDetailsDao;
@Inject
VpcOfferingDao vpcOfferingDao;
protected boolean backupSnapshotAfterTakingSnapshot = SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value();
private List<Integer> runningAndAllocatedVmUsageTypes = Arrays.asList(UsageTypes.RUNNING_VM, UsageTypes.ALLOCATED_VM);
@ -778,6 +783,19 @@ public class PresetVariableHelper {
value.setId(network.getUuid());
value.setName(network.getName());
value.setState(usageRecord.getState());
value.setNetworkOffering(getPresetVariableValueNetworkOffering(network.getNetworkOfferingId()));
}
protected GenericPresetVariable getPresetVariableValueNetworkOffering(Long networkOfferingId) {
NetworkOfferingVO networkOfferingVo = networkOfferingDao.findByIdIncludingRemoved(networkOfferingId);
validateIfObjectIsNull(networkOfferingVo, networkOfferingId, "network offering");
GenericPresetVariable networkOffering = new GenericPresetVariable();
networkOffering.setId(networkOfferingVo.getUuid());
networkOffering.setName(networkOfferingVo.getName());
return networkOffering;
}
protected void loadPresetVariableValueForVpc(UsageVO usageRecord, Value value) {
@ -793,6 +811,18 @@ public class PresetVariableHelper {
value.setId(vpc.getUuid());
value.setName(vpc.getName());
value.setVpcOffering(getPresetVariableValueVpcOffering(vpc.getVpcOfferingId()));
}
protected GenericPresetVariable getPresetVariableValueVpcOffering(Long vpcOfferingId) {
VpcOfferingVO vpcOfferingVo = vpcOfferingDao.findByIdIncludingRemoved(vpcOfferingId);
validateIfObjectIsNull(vpcOfferingVo, vpcOfferingId, "vpc offering");
GenericPresetVariable vpcOffering = new GenericPresetVariable();
vpcOffering.setId(vpcOfferingVo.getUuid());
vpcOffering.setName(vpcOfferingVo.getName());
return vpcOffering;
}
/**

View File

@ -96,6 +96,12 @@ public class Value extends GenericPresetVariable {
private String state;
@PresetVariableDefinition(description = "Network offering of the network.", supportedTypes = {QuotaTypes.NETWORK})
private GenericPresetVariable networkOffering;
@PresetVariableDefinition(description = "VPC offering of the VPC.", supportedTypes = {QuotaTypes.VPC})
private GenericPresetVariable vpcOffering;
public Host getHost() {
return host;
}
@ -255,4 +261,20 @@ public class Value extends GenericPresetVariable {
public void setState(String state) {
this.state = state;
}
public GenericPresetVariable getNetworkOffering() {
return networkOffering;
}
public void setNetworkOffering(GenericPresetVariable networkOffering) {
this.networkOffering = networkOffering;
}
public GenericPresetVariable getVpcOffering() {
return vpcOffering;
}
public void setVpcOffering(GenericPresetVariable vpcOffering) {
this.vpcOffering = vpcOffering;
}
}

View File

@ -0,0 +1,29 @@
//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.dao;
import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.quota.vo.QuotaTariffUsageVO;
import java.util.List;
public interface QuotaTariffUsageDao extends GenericDao<QuotaTariffUsageVO, Long> {
void persistQuotaTariffUsage(QuotaTariffUsageVO quotaTariffUsage);
List<QuotaTariffUsageVO> listQuotaTariffUsages(Long quotaUsageId);
}

View File

@ -0,0 +1,56 @@
//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.dao;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import org.apache.cloudstack.quota.vo.QuotaTariffUsageVO;
import org.springframework.stereotype.Component;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.db.TransactionLegacy;
import javax.annotation.PostConstruct;
import java.util.List;
@Component
public class QuotaTariffUsageDaoImpl extends GenericDaoBase<QuotaTariffUsageVO, Long> implements QuotaTariffUsageDao {
private SearchBuilder<QuotaTariffUsageVO> searchQuotaTariffUsages;
@PostConstruct
public void init() {
searchQuotaTariffUsages = createSearchBuilder();
searchQuotaTariffUsages.and("quotaUsageId", searchQuotaTariffUsages.entity().getQuotaUsageId(), SearchCriteria.Op.EQ);
searchQuotaTariffUsages.done();
}
@Override
public void persistQuotaTariffUsage(final QuotaTariffUsageVO quotaTariffUsage) {
logger.trace("Persisting quota tariff usage [{}].", quotaTariffUsage);
Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback<QuotaTariffUsageVO>) status -> persist(quotaTariffUsage));
}
@Override
public List<QuotaTariffUsageVO> listQuotaTariffUsages(Long quotaUsageId) {
SearchCriteria<QuotaTariffUsageVO> sc = searchQuotaTariffUsages.create();
sc.setParameters("quotaUsageId", quotaUsageId);
return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback<List<QuotaTariffUsageVO>>) status -> listBy(sc));
}
}

View File

@ -0,0 +1,31 @@
// 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.dao;
import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO;
import java.util.Date;
import java.util.List;
public interface QuotaUsageJoinDao extends GenericDao<QuotaUsageJoinVO, Long> {
List<QuotaUsageJoinVO> findQuotaUsage(Long accountId, Long domainId, Integer usageType, Long resourceId, Long networkId, Long offeringId, Date startDate, Date endDate, Long tariffId);
}

View File

@ -0,0 +1,94 @@
// 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.dao;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.JoinBuilder;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.db.TransactionLegacy;
import org.apache.cloudstack.quota.vo.QuotaTariffUsageVO;
import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.Date;
import java.util.List;
@Component
public class QuotaUsageJoinDaoImpl extends GenericDaoBase<QuotaUsageJoinVO, Long> implements QuotaUsageJoinDao {
private SearchBuilder<QuotaUsageJoinVO> searchQuotaUsages;
private SearchBuilder<QuotaUsageJoinVO> searchQuotaUsagesJoinTariffUsages;
@Inject
private QuotaTariffUsageDao quotaTariffUsageDao;
@PostConstruct
public void init() {
searchQuotaUsages = createSearchBuilder();
prepareQuotaUsageSearchBuilder(searchQuotaUsages);
searchQuotaUsages.done();
SearchBuilder<QuotaTariffUsageVO> searchQuotaTariffUsages = quotaTariffUsageDao.createSearchBuilder();
searchQuotaTariffUsages.and("tariffId", searchQuotaTariffUsages.entity().getTariffId(), SearchCriteria.Op.EQ);
searchQuotaUsagesJoinTariffUsages = createSearchBuilder();
prepareQuotaUsageSearchBuilder(searchQuotaUsagesJoinTariffUsages);
searchQuotaUsagesJoinTariffUsages.join("searchQuotaTariffUsages", searchQuotaTariffUsages, searchQuotaUsagesJoinTariffUsages.entity().getId(),
searchQuotaTariffUsages.entity().getQuotaUsageId(), JoinBuilder.JoinType.INNER);
searchQuotaUsagesJoinTariffUsages.done();
}
private void prepareQuotaUsageSearchBuilder(SearchBuilder<QuotaUsageJoinVO> searchBuilder) {
searchBuilder.and("accountId", searchBuilder.entity().getAccountId(), SearchCriteria.Op.EQ);
searchBuilder.and("domainId", searchBuilder.entity().getDomainId(), SearchCriteria.Op.EQ);
searchBuilder.and("usageType", searchBuilder.entity().getUsageType(), SearchCriteria.Op.EQ);
searchBuilder.and("resourceId", searchBuilder.entity().getResourceId(), SearchCriteria.Op.EQ);
searchBuilder.and("networkId", searchBuilder.entity().getNetworkId(), SearchCriteria.Op.EQ);
searchBuilder.and("offeringId", searchBuilder.entity().getOfferingId(), SearchCriteria.Op.EQ);
searchBuilder.and("startDate", searchBuilder.entity().getStartDate(), SearchCriteria.Op.BETWEEN);
searchBuilder.and("endDate", searchBuilder.entity().getEndDate(), SearchCriteria.Op.BETWEEN);
}
@Override
public List<QuotaUsageJoinVO> findQuotaUsage(Long accountId, Long domainId, Integer usageType, Long resourceId, Long networkId, Long offeringId, Date startDate, Date endDate, Long tariffId) {
SearchCriteria<QuotaUsageJoinVO> sc = tariffId == null ? searchQuotaUsages.create() : searchQuotaUsagesJoinTariffUsages.create();
sc.setParametersIfNotNull("accountId", accountId);
sc.setParametersIfNotNull("domainId", domainId);
sc.setParametersIfNotNull("usageType", usageType);
sc.setParametersIfNotNull("resourceId", resourceId);
sc.setParametersIfNotNull("networkId", networkId);
sc.setParametersIfNotNull("offeringId", offeringId);
if (ObjectUtils.allNotNull(startDate, endDate)) {
sc.setParameters("startDate", startDate, endDate);
sc.setParameters("endDate", startDate, endDate);
}
sc.setJoinParametersIfNotNull("searchQuotaTariffUsages", "tariffId", tariffId);
return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback<List<QuotaUsageJoinVO>>) status -> listBy(sc));
}
}

View File

@ -0,0 +1,86 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//with the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.quota.vo;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.cloudstack.api.InternalIdentity;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@Entity
@Table(name = "quota_tariff_usage")
public class QuotaTariffUsageVO implements InternalIdentity {
@Id
@Column(name = "id")
private Long id;
@Column(name = "tariff_id")
private Long tariffId;
@Column(name = "quota_usage_id")
private Long quotaUsageId;
@Column(name = "quota_used")
private BigDecimal quotaUsed;
public QuotaTariffUsageVO() {
quotaUsed = new BigDecimal(0);
}
@Override
public long getId() {
return id;
}
public Long getTariffId() {
return tariffId;
}
public Long getQuotaUsageId() {
return quotaUsageId;
}
public BigDecimal getQuotaUsed() {
return quotaUsed;
}
public void setId(Long id) {
this.id = id;
}
public void setTariffId(Long tariffId) {
this.tariffId = tariffId;
}
public void setQuotaUsageId(Long quotaUsageId) {
this.quotaUsageId = quotaUsageId;
}
public void setQuotaUsed(BigDecimal quotaUsed) {
this.quotaUsed = quotaUsed;
}
@Override
public String toString() {
return new ReflectionToStringBuilder(this, ToStringStyle.JSON_STYLE).toString();
}
}

View File

@ -0,0 +1,179 @@
//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.vo;
import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import java.math.BigDecimal;
import java.util.Date;
@Entity
@Table(name = "quota_usage_view")
public class QuotaUsageJoinVO implements InternalIdentity {
@Id
@Column(name = "id", updatable = false, nullable = false)
private Long id;
@Column(name = "zone_id")
private Long zoneId = null;
@Column(name = "account_id")
private Long accountId = null;
@Column(name = "domain_id")
private Long domainId = null;
@Column(name = "usage_item_id")
private Long usageItemId;
@Column(name = "usage_type")
private int usageType;
@Column(name = "quota_used")
private BigDecimal quotaUsed;
@Column(name = "start_date")
@Temporal(value = TemporalType.TIMESTAMP)
private Date startDate = null;
@Column(name = "end_date")
@Temporal(value = TemporalType.TIMESTAMP)
private Date endDate = null;
@Column(name = "resource_id")
private Long resourceId = null;
@Column(name = "network_id")
private Long networkId = null;
@Column(name = "offering_id")
private Long offeringId = null;
@Override
public long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getZoneId() {
return zoneId;
}
public void setZoneId(Long zoneId) {
this.zoneId = zoneId;
}
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public Long getDomainId() {
return domainId;
}
public void setDomainId(Long domainId) {
this.domainId = domainId;
}
public Long getUsageItemId() {
return usageItemId;
}
public void setUsageItemId(Long usageItemId) {
this.usageItemId = usageItemId;
}
public int getUsageType() {
return usageType;
}
public void setUsageType(int usageType) {
this.usageType = usageType;
}
public BigDecimal getQuotaUsed() {
return quotaUsed;
}
public void setQuotaUsed(BigDecimal quotaUsed) {
this.quotaUsed = quotaUsed;
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public Long getResourceId() {
return resourceId;
}
public void setResourceId(Long resourceId) {
this.resourceId = resourceId;
}
public Long getNetworkId() {
return networkId;
}
public void setNetworkId(Long networkId) {
this.networkId = networkId;
}
public Long getOfferingId() {
return offeringId;
}
public void setOfferingId(Long offeringId) {
this.offeringId = offeringId;
}
public QuotaUsageJoinVO () {
}
@Override
public String toString() {
return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "zoneId", "accountId", "domainId", "usageItemId", "usageType", "quotaUsed", "startDate",
"endDate", "resourceId");
}
}

View File

@ -0,0 +1,62 @@
//
// 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.vo;
import java.util.Date;
public class QuotaUsageResourceVO {
private String uuid;
private String name;
private Date removed;
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getRemoved() {
return removed;
}
public void setRemoved(Date removed) {
this.removed = removed;
}
public boolean isRemoved() {
return this.removed != null;
}
public QuotaUsageResourceVO(String uuid, String name, Date removed) {
this.uuid = uuid;
this.name = name;
this.removed = removed;
}
}

View File

@ -26,7 +26,9 @@
<bean id="QuotaEmailTemplatesDao"
class="org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDaoImpl" />
<bean id="QuotaUsageDao" class="org.apache.cloudstack.quota.dao.QuotaUsageDaoImpl" />
<bean id="UserVmDetailsDao" class="org.apache.cloudstack.quota.dao.VMInstanceDetailsDaoImpl" />
<bean id="QuotaUsageJoinDao" class="org.apache.cloudstack.quota.dao.QuotaUsageJoinDaoImpl"/>
<bean id="QuotaTariffUsageDao" class="org.apache.cloudstack.quota.dao.QuotaTariffUsageDaoImpl" />
<bean id="UserVmDetailsDao" class="org.apache.cloudstack.quota.dao.VMInstanceDetailsDaoImpl" />
<bean id="QuotaManager" class="org.apache.cloudstack.quota.QuotaManagerImpl" />
<bean id="QuotaAlertManager" class="org.apache.cloudstack.quota.QuotaAlertManagerImpl" />

View File

@ -31,6 +31,10 @@ import com.cloud.dc.ClusterDetailsDao;
import com.cloud.dc.ClusterDetailsVO;
import com.cloud.host.HostTagVO;
import com.cloud.hypervisor.Hypervisor;
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 org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.RoleVO;
@ -38,7 +42,9 @@ import org.apache.cloudstack.acl.dao.RoleDao;
import org.apache.cloudstack.backup.BackupOfferingVO;
import org.apache.cloudstack.backup.dao.BackupOfferingDao;
import org.apache.cloudstack.quota.constant.QuotaTypes;
import org.apache.cloudstack.quota.dao.NetworkDao;
import org.apache.cloudstack.quota.dao.VmTemplateDao;
import org.apache.cloudstack.quota.dao.VpcDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
@ -188,6 +194,15 @@ public class PresetVariableHelperTest {
@Mock
BackupOfferingDao backupOfferingDaoMock;
@Mock
NetworkDao networkDaoMock;
@Mock
VpcDao vpcDaoMock;
@Mock
VpcOfferingDao vpcOfferingDaoMock;
List<Integer> runningAndAllocatedVmUsageTypes = Arrays.asList(UsageTypes.RUNNING_VM, UsageTypes.ALLOCATED_VM);
List<Integer> templateAndIsoUsageTypes = Arrays.asList(UsageTypes.TEMPLATE, UsageTypes.ISO);
@ -223,6 +238,8 @@ public class PresetVariableHelperTest {
value.setVmSnapshotType(VMSnapshot.Type.Disk.toString());
value.setComputingResources(getComputingResourcesForTests());
value.setVolumeType(Volume.Type.DATADISK.toString());
value.setNetworkOffering(getNetworkOfferingForTests());
value.setVpcOffering(getVpcOfferingForTests());
return value;
}
@ -324,6 +341,20 @@ public class PresetVariableHelperTest {
return diskOffering;
}
private GenericPresetVariable getNetworkOfferingForTests() {
GenericPresetVariable networkOffering = new GenericPresetVariable();
networkOffering.setId("network_offering_id");
networkOffering.setName("network_offering_name");
return networkOffering;
}
private GenericPresetVariable getVpcOfferingForTests() {
GenericPresetVariable vpcOffering = new GenericPresetVariable();
vpcOffering.setId("vpc_offering_id");
vpcOffering.setName("vpc_offering_name");
return vpcOffering;
}
private void mockMethodValidateIfObjectIsNull() {
Mockito.doNothing().when(presetVariableHelperSpy).validateIfObjectIsNull(Mockito.any(), Mockito.anyLong(), Mockito.anyString());
}
@ -1289,4 +1320,100 @@ public class PresetVariableHelperTest {
Mockito.when(imageStoreDaoMock.findById(1L)).thenReturn(store);
Assert.assertNotNull(presetVariableHelperSpy.getSnapshotImageStoreRef(1L, 1L));
}
@Test
public void loadPresetVariableValueForNetworkTestRecordIsNotANetworkDoNothing() {
getQuotaTypesForTests(UsageTypes.NETWORK).forEach(type -> {
Mockito.doReturn(type.getKey()).when(usageVoMock).getUsageType();
presetVariableHelperSpy.loadPresetVariableValueForNetwork(usageVoMock, null);
});
Mockito.verifyNoInteractions(networkDaoMock);
}
@Test
public void loadPresetVariableValueForNetworkTestRecordIsNetworkSetFields() {
Value expected = getValueForTests();
NetworkVO networkVoMock = Mockito.mock(NetworkVO.class);
Mockito.doReturn(networkVoMock).when(networkDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
mockMethodValidateIfObjectIsNull();
Mockito.doReturn(expected.getId()).when(networkVoMock).getUuid();
Mockito.doReturn(expected.getName()).when(networkVoMock).getName();
Mockito.doReturn(expected.getState()).when(usageVoMock).getState();
Mockito.doReturn(expected.getNetworkOffering()).when(presetVariableHelperSpy).getPresetVariableValueNetworkOffering(Mockito.anyLong());
Mockito.doReturn(UsageTypes.NETWORK).when(usageVoMock).getUsageType();
Value result = new Value();
presetVariableHelperSpy.loadPresetVariableValueForNetwork(usageVoMock, result);
assertPresetVariableIdAndName(expected, result);
Assert.assertEquals(expected.getState(), result.getState());
Assert.assertEquals(expected.getNetworkOffering(), result.getNetworkOffering());
}
@Test
public void loadPresetVariableValueForVpcTestRecordIsNotAVpcDoNothing() {
getQuotaTypesForTests(UsageTypes.VPC).forEach(type -> {
Mockito.doReturn(type.getKey()).when(usageVoMock).getUsageType();
presetVariableHelperSpy.loadPresetVariableValueForVpc(usageVoMock, null);
});
Mockito.verifyNoInteractions(networkDaoMock);
}
@Test
public void loadPresetVariableValueForVpcTestRecordIsVpcSetFields() {
Value expected = getValueForTests();
VpcVO networkVoMock = Mockito.mock(VpcVO.class);
Mockito.doReturn(networkVoMock).when(vpcDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
mockMethodValidateIfObjectIsNull();
Mockito.doReturn(expected.getId()).when(networkVoMock).getUuid();
Mockito.doReturn(expected.getName()).when(networkVoMock).getName();
Mockito.doReturn(expected.getVpcOffering()).when(presetVariableHelperSpy).getPresetVariableValueVpcOffering(Mockito.anyLong());
Mockito.doReturn(UsageTypes.VPC).when(usageVoMock).getUsageType();
Value result = new Value();
presetVariableHelperSpy.loadPresetVariableValueForVpc(usageVoMock, result);
assertPresetVariableIdAndName(expected, result);
Assert.assertEquals(expected.getVpcOffering(), result.getVpcOffering());
}
@Test
public void getPresetVariableValueNetworkOfferingTestSetValuesAndReturnObject() {
NetworkOfferingVO networkOfferingVoMock = Mockito.mock(NetworkOfferingVO.class);
Mockito.doReturn(networkOfferingVoMock).when(networkOfferingDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
mockMethodValidateIfObjectIsNull();
GenericPresetVariable expected = getGenericPresetVariableForTests();
Mockito.doReturn(expected.getId()).when(networkOfferingVoMock).getUuid();
Mockito.doReturn(expected.getName()).when(networkOfferingVoMock).getName();
GenericPresetVariable result = presetVariableHelperSpy.getPresetVariableValueNetworkOffering(1L);
assertPresetVariableIdAndName(expected, result);
}
@Test
public void getPresetVariableValueVpcOfferingTestSetValuesAndReturnObject() {
VpcOfferingVO vpcOfferingVoMock = Mockito.mock(VpcOfferingVO.class);
Mockito.doReturn(vpcOfferingVoMock).when(vpcOfferingDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
mockMethodValidateIfObjectIsNull();
GenericPresetVariable expected = getGenericPresetVariableForTests();
Mockito.doReturn(expected.getId()).when(vpcOfferingVoMock).getUuid();
Mockito.doReturn(expected.getName()).when(vpcOfferingVoMock).getName();
GenericPresetVariable result = presetVariableHelperSpy.getPresetVariableValueVpcOffering(1L);
assertPresetVariableIdAndName(expected, result);
}
}

View File

@ -17,7 +17,6 @@
package org.apache.cloudstack.api.command;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
@ -28,24 +27,25 @@ import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.ProjectResponse;
import org.apache.cloudstack.api.response.QuotaResponseBuilder;
import org.apache.cloudstack.api.response.QuotaStatementItemResponse;
import org.apache.cloudstack.api.response.QuotaStatementResponse;
import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import com.cloud.user.Account;
import org.apache.commons.lang3.ObjectUtils;
@APICommand(name = "quotaStatement", responseObject = QuotaStatementItemResponse.class, description = "Create a quota statement", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
httpMethod = "GET")
@APICommand(name = "quotaStatement", responseObject = QuotaStatementItemResponse.class, description = "Create a Quota statement for the provided Account, Project, or Domain.",
since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, httpMethod = "GET")
public class QuotaStatementCmd extends BaseCmd {
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = true, description = "Optional, Account Id for which statement needs to be generated")
@ACL
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING,
description = "Name of the Account for which the Quota statement will be generated. Deprecated, please use accountid instead.")
private String accountName;
@ACL
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class, description = "Optional, If domain Id is given and the caller is domain admin then the statement is generated for domain.")
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class,
description = "ID of the Domain for which the Quota statement will be generated. May be used individually or with account.")
private Long domainId;
@Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, required = true, description = "End of the period of the Quota statement. " +
@ -56,15 +56,25 @@ public class QuotaStatementCmd extends BaseCmd {
ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS)
private Date startDate;
@Parameter(name = ApiConstants.TYPE, type = CommandType.INTEGER, description = "List quota usage records for the specified usage type")
@Parameter(name = ApiConstants.TYPE, type = CommandType.INTEGER,
description = "Consider only Quota usage records for the specified usage type in the statement.")
private Integer usageType;
@ACL
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "List usage records for the specified Account")
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class,
description = "ID of the Account for which the Quota statement will be generated. Can not be specified with projectid.")
private Long accountId;
@ACL
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class,
description = "ID of the Project for which the Quota statement will be generated. Can not be specified with accountid.", since = "4.23.0")
private Long projectId;
@Parameter(name = ApiConstants.SHOW_RESOURCES, type = CommandType.BOOLEAN, description = "List the resources of each Quota type in the period.", since = "4.23.0")
private boolean showResources;
@Inject
private QuotaResponseBuilder _responseBuilder;
QuotaResponseBuilder responseBuilder;
public Long getAccountId() {
return accountId;
@ -99,43 +109,47 @@ public class QuotaStatementCmd extends BaseCmd {
}
public Date getEndDate() {
return _responseBuilder.startOfNextDay(endDate == null ? new Date() : new Date(endDate.getTime()));
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate == null ? null : new Date(endDate.getTime());
this.endDate = endDate;
}
public Date getStartDate() {
return startDate == null ? null : new Date(startDate.getTime());
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate == null ? null : new Date(startDate.getTime());
this.startDate = startDate;
}
public boolean isShowResources() {
return showResources;
}
public void setShowResources(boolean showResources) {
this.showResources = showResources;
}
public Long getProjectId() {
return projectId;
}
@Override
public long getEntityOwnerId() {
if (accountId != null) {
return accountId;
if (ObjectUtils.allNull(accountId, accountName, projectId)) {
return -1;
}
Account activeAccountByName = _accountService.getActiveAccountByName(accountName, domainId);
if (activeAccountByName != null) {
return activeAccountByName.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
return _accountService.finalizeAccountId(accountId, accountName, domainId, projectId);
}
@Override
public void execute() {
List<QuotaUsageVO> quotaUsage = _responseBuilder.getQuotaUsage(this);
QuotaStatementResponse response = _responseBuilder.createQuotaStatementResponse(quotaUsage);
response.setStartDate(startDate == null ? null : new Date(startDate.getTime()));
response.setEndDate(endDate == null ? null : new Date(endDate.getTime()));
QuotaStatementResponse response = responseBuilder.createQuotaStatementResponse(this);
response.setStartDate(startDate);
response.setEndDate(endDate);
response.setResponseName(getCommandName());
setResponseObject(response);
}
}

View File

@ -32,7 +32,6 @@ import org.apache.cloudstack.api.command.QuotaValidateActivationRuleCmd;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO;
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import java.util.Date;
import java.util.List;
@ -49,7 +48,7 @@ public interface QuotaResponseBuilder {
boolean isUserAllowedToSeeActivationRules(User user);
QuotaStatementResponse createQuotaStatementResponse(List<QuotaUsageVO> quotaUsage);
QuotaStatementResponse createQuotaStatementResponse(QuotaStatementCmd cmd);
QuotaBalanceResponse createQuotaBalanceResponse(List<QuotaBalanceVO> quotaUsage, Date startDate, Date endDate);
@ -57,8 +56,6 @@ public interface QuotaResponseBuilder {
QuotaBalanceResponse createQuotaLastBalanceResponse(List<QuotaBalanceVO> quotaBalance, Date startDate);
List<QuotaUsageVO> getQuotaUsage(QuotaStatementCmd cmd);
List<QuotaBalanceVO> getQuotaBalance(QuotaBalanceCmd cmd);
QuotaCreditsResponse addQuotaCredits(Long accountId, Long domainId, Double amount, Long updatedBy, Boolean enforce);

View File

@ -21,13 +21,11 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
@ -36,6 +34,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@ -43,12 +42,36 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.domain.Domain;
import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.projects.dao.ProjectDao;
import com.cloud.network.dao.IPAddressDao;
import com.cloud.network.dao.IPAddressVO;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.offerings.dao.NetworkOfferingDao;
import com.cloud.offerings.NetworkOfferingVO;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.SnapshotVO;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.UserDao;
import com.cloud.user.User;
import com.cloud.user.UserVO;
import com.cloud.utils.DateUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException;
@ -94,7 +117,8 @@ import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO;
import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO;
import org.apache.cloudstack.quota.vo.QuotaSummaryVO;
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO;
import org.apache.cloudstack.quota.vo.QuotaUsageResourceVO;
import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.collections4.CollectionUtils;
@ -106,18 +130,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;
import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.UserDao;
import com.cloud.utils.Pair;
@Component
public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
protected Logger logger = LogManager.getLogger(getClass());
@ -140,8 +152,6 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
@Inject
private AccountDao _accountDao;
@Inject
private ProjectDao projectDao;
@Inject
private QuotaAccountDao quotaAccountDao;
@Inject
private DomainDao domainDao;
@ -159,6 +169,21 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
private JsInterpreterHelper jsInterpreterHelper;
@Inject
private ApiDiscoveryService apiDiscoveryService;
@Inject
private IPAddressDao ipAddressDao;
@Inject
private NetworkDao networkDao;
@Inject
private NetworkOfferingDao networkOfferingDao;
@Inject
private SnapshotDao snapshotDao;
@Inject
private VMInstanceDao vmInstanceDao;
@Inject
private VMTemplateDao vmTemplateDao;
@Inject
private VolumeDao volumeDao;
private final Class<?>[] assignableClasses = {GenericPresetVariable.class, ComputingResources.class};
@ -393,78 +418,212 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
}
@Override
public QuotaStatementResponse createQuotaStatementResponse(final List<QuotaUsageVO> quotaUsage) {
if (quotaUsage == null || quotaUsage.isEmpty()) {
throw new InvalidParameterValueException("There is no usage data found for period mentioned.");
}
public QuotaStatementResponse createQuotaStatementResponse(QuotaStatementCmd cmd) {
Long accountId = getAccountIdForQuotaStatement(cmd);
Long domainId = getDomainIdForQuotaStatement(cmd, accountId);
List<QuotaUsageJoinVO> quotaUsages = _quotaService.getQuotaUsage(accountId, null, domainId, cmd.getUsageType(), cmd.getStartDate(), cmd.getEndDate());
logger.debug("Creating quota statement from [{}] usage records for parameters [{}].", quotaUsages.size(),
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(cmd, "accountName", "accountId", "projectId", "domainId", "startDate", "endDate", "usageType", "showResources"));
createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformed(quotaUsages, cmd.getUsageType());
Map<Integer, List<QuotaUsageJoinVO>> recordsPerUsageTypes = quotaUsages.stream()
.sorted(Comparator.comparingInt(QuotaUsageJoinVO::getUsageType))
.collect(Collectors.groupingBy(QuotaUsageJoinVO::getUsageType));
List<QuotaStatementItemResponse> items = new ArrayList<>();
recordsPerUsageTypes.forEach((key, value) -> items.add(createStatementItem(key, value, cmd.isShowResources())));
QuotaStatementResponse statement = new QuotaStatementResponse();
HashMap<Integer, QuotaTypes> quotaTariffMap = new HashMap<Integer, QuotaTypes>();
Collection<QuotaTypes> result = QuotaTypes.listQuotaTypes().values();
for (QuotaTypes quotaTariff : result) {
quotaTariffMap.put(quotaTariff.getQuotaType(), quotaTariff);
// add dummy record for each usage type
QuotaUsageVO dummy = new QuotaUsageVO(quotaUsage.get(0));
dummy.setUsageType(quotaTariff.getQuotaType());
dummy.setQuotaUsed(new BigDecimal(0));
quotaUsage.add(dummy);
}
if (logger.isDebugEnabled()) {
logger.debug(
"createQuotaStatementResponse Type=" + quotaUsage.get(0).getUsageType() + " usage=" + quotaUsage.get(0).getQuotaUsed().setScale(2, RoundingMode.HALF_EVEN)
+ " rec.id=" + quotaUsage.get(0).getUsageItemId() + " SD=" + quotaUsage.get(0).getStartDate() + " ED=" + quotaUsage.get(0).getEndDate());
}
Collections.sort(quotaUsage, new Comparator<QuotaUsageVO>() {
@Override
public int compare(QuotaUsageVO o1, QuotaUsageVO o2) {
if (o1.getUsageType() == o2.getUsageType()) {
return 0;
}
return o1.getUsageType() < o2.getUsageType() ? -1 : 1;
}
});
List<QuotaStatementItemResponse> items = new ArrayList<QuotaStatementItemResponse>();
QuotaStatementItemResponse lineitem;
int type = -1;
BigDecimal usage = new BigDecimal(0);
BigDecimal totalUsage = new BigDecimal(0);
quotaUsage.add(new QuotaUsageVO());// boundary
QuotaUsageVO prev = quotaUsage.get(0);
if (logger.isDebugEnabled()) {
logger.debug("createQuotaStatementResponse record count=" + quotaUsage.size());
}
for (final QuotaUsageVO quotaRecord : quotaUsage) {
if (type != quotaRecord.getUsageType()) {
if (type != -1) {
lineitem = new QuotaStatementItemResponse(type);
lineitem.setQuotaUsed(usage);
lineitem.setAccountId(prev.getAccountId());
lineitem.setDomainId(prev.getDomainId());
lineitem.setUsageUnit(quotaTariffMap.get(type).getQuotaUnit());
lineitem.setUsageName(quotaTariffMap.get(type).getQuotaName());
lineitem.setObjectName("quotausage");
items.add(lineitem);
totalUsage = totalUsage.add(usage);
usage = new BigDecimal(0);
}
type = quotaRecord.getUsageType();
}
prev = quotaRecord;
usage = usage.add(quotaRecord.getQuotaUsed());
}
statement.setLineItem(items);
statement.setTotalQuota(totalUsage);
statement.setTotalQuota(items.stream().map(QuotaStatementItemResponse::getQuotaUsed).reduce(BigDecimal.ZERO, BigDecimal::add));
statement.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
statement.setObjectName("statement");
if (accountId != null) {
Account account = _accountDao.findByIdIncludingRemoved(accountId);
statement.setAccountId(account.getUuid());
statement.setAccountName(account.getAccountName());
domainId = account.getDomainId();
}
if (domainId != null) {
DomainVO domain = domainDao.findByIdIncludingRemoved(domainId);
statement.setDomainId(domain.getUuid());
}
return statement;
}
protected Long getAccountIdForQuotaStatement(QuotaStatementCmd cmd) {
if (Account.Type.NORMAL.equals(CallContext.current().getCallingAccount().getType())) {
logger.debug("Limiting the Quota statement for the calling Account, as they are a User Account.");
return CallContext.current().getCallingAccountId();
}
long accountId = cmd.getEntityOwnerId();
if (accountId != -1) {
return accountId;
}
if (cmd.getDomainId() == null) {
logger.debug("Limiting the Quota statement for the calling Account, as 'domainid' was not informed.");
return CallContext.current().getCallingAccountId();
}
logger.debug("Allowing admin/domain admin to generate the Quota statement for the provided Domain.");
return null;
}
protected Long getDomainIdForQuotaStatement(QuotaStatementCmd cmd, Long accountId) {
if (accountId != null) {
logger.debug("Quota statement is already limited to Account [{}].", accountId);
Account account = _accountDao.findByIdIncludingRemoved(accountId);
return account.getDomainId();
}
Long domainId = cmd.getDomainId();
if (domainId != null) {
return domainId;
}
logger.debug("Limiting the Quota statement for the calling Account's Domain.");
return CallContext.current().getCallingAccount().getDomainId();
}
protected void createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformed(List<QuotaUsageJoinVO> quotaUsages, Integer usageType) {
if (usageType != null) {
logger.debug("As the usage type [{}] was informed as parameter of the API quotaStatement, we will not create dummy records.", usageType);
return;
}
for (Integer quotaType : QuotaTypes.listQuotaTypes().keySet()) {
QuotaUsageJoinVO dummy = new QuotaUsageJoinVO();
dummy.setUsageType(quotaType);
dummy.setQuotaUsed(BigDecimal.ZERO);
quotaUsages.add(dummy);
}
}
protected QuotaStatementItemResponse createStatementItem(int usageType, List<QuotaUsageJoinVO> usageRecords, boolean showResources) {
QuotaUsageJoinVO firstRecord = usageRecords.get(0);
int type = firstRecord.getUsageType();
QuotaTypes quotaType = QuotaTypes.listQuotaTypes().get(type);
QuotaStatementItemResponse item = new QuotaStatementItemResponse(type);
item.setQuotaUsed(usageRecords.stream().map(QuotaUsageJoinVO::getQuotaUsed).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
item.setUsageUnit(quotaType.getQuotaUnit());
item.setUsageName(quotaType.getQuotaName());
setStatementItemResources(item, usageType, usageRecords, showResources);
return item;
}
protected void setStatementItemResources(QuotaStatementItemResponse statementItem, int usageType, List<QuotaUsageJoinVO> quotaUsageRecords, boolean showResources) {
if (!showResources) {
return;
}
List<QuotaStatementItemResourceResponse> itemDetails = new ArrayList<>();
Map<Long, BigDecimal> quotaUsagesValuesAggregatedById = quotaUsageRecords
.stream()
.filter(quotaUsageJoinVo -> getResourceIdByUsageType(quotaUsageJoinVo, usageType) != null)
.collect(Collectors.groupingBy(
quotaUsageJoinVo -> getResourceIdByUsageType(quotaUsageJoinVo, usageType),
Collectors.reducing(new BigDecimal(0), QuotaUsageJoinVO::getQuotaUsed, BigDecimal::add)
));
for (Map.Entry<Long, BigDecimal> entry : quotaUsagesValuesAggregatedById.entrySet()) {
QuotaStatementItemResourceResponse detail = new QuotaStatementItemResourceResponse();
detail.setQuotaUsed(entry.getValue());
QuotaUsageResourceVO resource = getResourceFromIdAndType(entry.getKey(), usageType);
if (resource != null) {
detail.setResourceId(resource.getUuid());
detail.setDisplayName(resource.getName());
detail.setRemoved(resource.isRemoved());
} else {
detail.setDisplayName("<untraceable>");
}
itemDetails.add(detail);
}
statementItem.setResources(itemDetails);
}
protected Long getResourceIdByUsageType(QuotaUsageJoinVO quotaUsageJoinVo, int usageType) {
switch (usageType) {
case QuotaTypes.NETWORK_BYTES_SENT:
case QuotaTypes.NETWORK_BYTES_RECEIVED:
return quotaUsageJoinVo.getNetworkId();
case QuotaTypes.NETWORK_OFFERING:
return quotaUsageJoinVo.getOfferingId();
default:
return quotaUsageJoinVo.getResourceId();
}
}
protected QuotaUsageResourceVO getResourceFromIdAndType(long resourceId, int usageType) {
switch (usageType) {
case QuotaTypes.ALLOCATED_VM:
case QuotaTypes.RUNNING_VM:
VMInstanceVO vmInstance = vmInstanceDao.findByIdIncludingRemoved(resourceId);
if (vmInstance != null) {
return new QuotaUsageResourceVO(vmInstance.getUuid(), vmInstance.getHostName(), vmInstance.getRemoved());
}
break;
case QuotaTypes.VOLUME:
case QuotaTypes.VOLUME_SECONDARY:
case QuotaTypes.VM_DISK_BYTES_READ:
case QuotaTypes.VM_DISK_BYTES_WRITE:
case QuotaTypes.VM_DISK_IO_READ:
case QuotaTypes.VM_DISK_IO_WRITE:
VolumeVO volume = volumeDao.findByIdIncludingRemoved(resourceId);
if (volume != null) {
return new QuotaUsageResourceVO(volume.getUuid(), volume.getName(), volume.getRemoved());
}
break;
case QuotaTypes.VM_SNAPSHOT_ON_PRIMARY:
case QuotaTypes.VM_SNAPSHOT:
case QuotaTypes.SNAPSHOT:
SnapshotVO snapshot = snapshotDao.findByIdIncludingRemoved(resourceId);
if (snapshot != null) {
return new QuotaUsageResourceVO(snapshot.getUuid(), snapshot.getName(), snapshot.getRemoved());
}
break;
case QuotaTypes.NETWORK_BYTES_SENT:
case QuotaTypes.NETWORK_BYTES_RECEIVED:
NetworkVO network = networkDao.findByIdIncludingRemoved(resourceId);
if (network != null) {
return new QuotaUsageResourceVO(network.getUuid(), network.getName(), network.getRemoved());
}
break;
case QuotaTypes.TEMPLATE:
case QuotaTypes.ISO:
VMTemplateVO vmTemplate = vmTemplateDao.findByIdIncludingRemoved(resourceId);
if (vmTemplate != null) {
return new QuotaUsageResourceVO(vmTemplate.getUuid(), vmTemplate.getName(), vmTemplate.getRemoved());
}
break;
case QuotaTypes.NETWORK_OFFERING:
NetworkOfferingVO networkOffering = networkOfferingDao.findByIdIncludingRemoved(resourceId);
if (networkOffering != null) {
return new QuotaUsageResourceVO(networkOffering.getUuid(), networkOffering.getName(), networkOffering.getRemoved());
}
break;
case QuotaTypes.IP_ADDRESS:
IPAddressVO ipAddress = ipAddressDao.findByIdIncludingRemoved(resourceId);
if (ipAddress != null) {
return new QuotaUsageResourceVO(ipAddress.getUuid(), ipAddress.getName(), ipAddress.getRemoved());
}
break;
}
return null;
}
@Override
public Pair<List<QuotaTariffVO>, Integer> listQuotaTariffPlans(final QuotaTariffListCmd cmd) {
Date startDate = cmd.getEffectiveDate();
@ -518,14 +677,14 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
}
protected void warnQuotaTariffUpdateDeprecatedFields(QuotaTariffUpdateCmd cmd) {
String warnMessage = "The parameter 's%s' for API 'quotaTariffUpdate' is no longer needed and it will be removed in future releases.";
String warnMessage = "The parameter '{}' for API 'quotaTariffUpdate' is no longer needed and it will be removed in future releases.";
if (cmd.getStartDate() != null) {
logger.warn(String.format(warnMessage,"startdate"));
logger.warn(warnMessage, "startdate");
}
if (cmd.getUsageType() != null) {
logger.warn(String.format(warnMessage,"usagetype"));
logger.warn(warnMessage, "usagetype");
}
}
@ -712,11 +871,6 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
return resp;
}
@Override
public List<QuotaUsageVO> getQuotaUsage(QuotaStatementCmd cmd) {
return _quotaService.getQuotaUsage(cmd.getAccountId(), cmd.getAccountName(), cmd.getDomainId(), cmd.getUsageType(), cmd.getStartDate(), cmd.getEndDate());
}
@Override
public List<QuotaBalanceVO> getQuotaBalance(QuotaBalanceCmd cmd) {
return _quotaService.findQuotaBalanceVO(cmd.getAccountId(), cmd.getAccountName(), cmd.getDomainId(), cmd.getStartDate(), cmd.getEndDate());

View File

@ -0,0 +1,61 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//with the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.response;
import java.math.BigDecimal;
import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import com.cloud.serializer.Param;
public class QuotaStatementItemResourceResponse extends BaseResponse {
@SerializedName(ApiConstants.QUOTA_CONSUMED)
@Param(description = "Quota consumed.")
private BigDecimal quotaUsed;
@SerializedName(ApiConstants.RESOURCE_ID)
@Param(description = "Resources's ID.")
private String resourceId;
@SerializedName(ApiConstants.DISPLAY_NAME)
@Param(description = "Resource's display name.")
private String displayName;
@SerializedName(ApiConstants.REMOVED)
@Param(description = "Indicates whether the resource is removed or active.")
private boolean removed;
public void setQuotaUsed(BigDecimal quotaUsed) {
this.quotaUsed = quotaUsed;
}
public void setResourceId(String resourceId) {
this.resourceId = resourceId;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public void setRemoved(boolean removed) {
this.removed = removed;
}
}

View File

@ -17,72 +17,41 @@
package org.apache.cloudstack.api.response;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import com.cloud.serializer.Param;
public class QuotaStatementItemResponse extends BaseResponse {
@SerializedName("type")
@Param(description = "Usage type")
@SerializedName(ApiConstants.TYPE)
@Param(description = "Usage type.")
private int usageType;
@SerializedName("accountid")
@Param(description = "Account id")
private Long accountId;
@SerializedName("account")
@Param(description = "Account name")
private String accountName;
@SerializedName("domain")
@Param(description = "Domain id")
private Long domainId;
@SerializedName("name")
@Param(description = "Usage type name")
@SerializedName(ApiConstants.NAME)
@Param(description = "Name of the Usage type.")
private String usageName;
@SerializedName("unit")
@Param(description = "Usage unit")
@SerializedName(ApiConstants.UNIT)
@Param(description = "Unit of the Usage type.")
private String usageUnit;
@SerializedName("quota")
@Param(description = "Quota consumed")
@SerializedName(ApiConstants.QUOTA)
@Param(description = "Quota consumed.")
private BigDecimal quotaUsed;
@SerializedName(ApiConstants.RESOURCES)
@Param(description = "Item's resources.")
private List<QuotaStatementItemResourceResponse> resources;
public QuotaStatementItemResponse(final int usageType) {
this.usageType = usageType;
}
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public Long getDomainId() {
return domainId;
}
public void setDomainId(Long domainId) {
this.domainId = domainId;
}
public String getUsageName() {
return usageName;
}
@ -112,7 +81,15 @@ public class QuotaStatementItemResponse extends BaseResponse {
}
public void setQuotaUsed(BigDecimal quotaUsed) {
this.quotaUsed = quotaUsed.setScale(2, RoundingMode.HALF_EVEN);
this.quotaUsed = quotaUsed;
}
public List<QuotaStatementItemResourceResponse> getResources() {
return resources;
}
public void setResources(List<QuotaStatementItemResourceResponse> resources) {
this.resources = resources;
}
}

View File

@ -18,56 +18,56 @@ package org.apache.cloudstack.api.response;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
public class QuotaStatementResponse extends BaseResponse {
@SerializedName("accountid")
@Param(description = "Account ID")
private Long accountId;
@SerializedName(ApiConstants.ACCOUNT_ID)
@Param(description = "ID of the Account.")
private String accountId;
@SerializedName("account")
@Param(description = "Account name")
@SerializedName(ApiConstants.ACCOUNT)
@Param(description = "Name of the Account.")
private String accountName;
@SerializedName("domain")
@Param(description = "Domain ID")
private Long domainId;
@SerializedName(ApiConstants.DOMAIN)
@Param(description = "ID of the Domain.")
private String domainId;
@SerializedName("quotausage")
@Param(description = "List of quota usage under various types", responseObject = QuotaStatementItemResponse.class)
@SerializedName(ApiConstants.QUOTA_USAGE)
@Param(description = "List of Quota usage under various types.", responseObject = QuotaStatementItemResponse.class)
private List<QuotaStatementItemResponse> lineItem;
@SerializedName("totalquota")
@Param(description = "Total quota used during this period")
@SerializedName(ApiConstants.TOTAL_QUOTA)
@Param(description = "Total Quota consumed during this period.")
private BigDecimal totalQuota;
@SerializedName("startdate")
@Param(description = "Start date")
@SerializedName(ApiConstants.START_DATE)
@Param(description = "Start date of the Quota statement.")
private Date startDate = null;
@SerializedName("enddate")
@Param(description = "End date")
@SerializedName(ApiConstants.END_DATE)
@Param(description = "End date of the Quota statement.")
private Date endDate = null;
@SerializedName("currency")
@Param(description = "Currency")
@SerializedName(ApiConstants.CURRENCY)
@Param(description = "Currency of the Quota statement.")
private String currency;
public QuotaStatementResponse() {
super();
}
public Long getAccountId() {
public String getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
public void setAccountId(String accountId) {
this.accountId = accountId;
}
@ -79,45 +79,36 @@ public class QuotaStatementResponse extends BaseResponse {
this.accountName = accountName;
}
public Long getDomainId() {
public String getDomainId() {
return domainId;
}
public void setDomainId(Long domainId) {
public void setDomainId(String domainId) {
this.domainId = domainId;
}
public List<QuotaStatementItemResponse> getLineItem() {
return lineItem;
}
public void setLineItem(List<QuotaStatementItemResponse> lineItem) {
this.lineItem = lineItem;
}
public Date getStartDate() {
return startDate == null ? null : new Date(startDate.getTime());
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate == null ? null : new Date(startDate.getTime());
this.startDate = startDate;
}
public Date getEndDate() {
return endDate == null ? null : new Date(endDate.getTime());
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate == null ? null : new Date(endDate.getTime());
}
public BigDecimal getTotalQuota() {
return totalQuota;
this.endDate = endDate;
}
public void setTotalQuota(BigDecimal totalQuota) {
this.totalQuota = totalQuota.setScale(2, RoundingMode.HALF_EVEN);
this.totalQuota = totalQuota;
}
public String getCurrency() {

View File

@ -21,14 +21,14 @@ import java.util.Date;
import java.util.List;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO;
import com.cloud.user.AccountVO;
import com.cloud.utils.component.PluggableService;
public interface QuotaService extends PluggableService {
List<QuotaUsageVO> getQuotaUsage(Long accountId, String accountName, Long domainId, Integer usageType, Date startDate, Date endDate);
List<QuotaUsageJoinVO> getQuotaUsage(Long accountId, String accountName, Long domainId, Integer usageType, Date startDate, Date endDate);
List<QuotaBalanceVO> findQuotaBalanceVO(Long accountId, String accountName, Long domainId, Date startDate, Date endDate);

View File

@ -53,10 +53,10 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.quota.constant.QuotaConfig;
import org.apache.cloudstack.quota.dao.QuotaAccountDao;
import org.apache.cloudstack.quota.dao.QuotaBalanceDao;
import org.apache.cloudstack.quota.dao.QuotaUsageDao;
import org.apache.cloudstack.quota.dao.QuotaUsageJoinDao;
import org.apache.cloudstack.quota.vo.QuotaAccountVO;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Component;
@ -80,7 +80,7 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
@Inject
private QuotaAccountDao _quotaAcc;
@Inject
private QuotaUsageDao _quotaUsageDao;
private QuotaUsageJoinDao quotaUsageJoinDao;
@Inject
private DomainDao _domainDao;
@Inject
@ -213,27 +213,7 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
}
@Override
public List<QuotaUsageVO> getQuotaUsage(Long accountId, String accountName, Long domainId, Integer usageType, Date startDate, Date endDate) {
// if accountId is not specified, use accountName and domainId
if ((accountId == null) && (accountName != null) && (domainId != null)) {
Account userAccount = null;
Account caller = CallContext.current().getCallingAccount();
if (_domainDao.isChildDomain(caller.getDomainId(), domainId)) {
Filter filter = new Filter(AccountVO.class, "id", Boolean.FALSE, null, null);
List<AccountVO> accounts = _accountDao.listAccounts(accountName, domainId, filter);
if (!accounts.isEmpty()) {
userAccount = accounts.get(0);
}
if (userAccount != null) {
accountId = userAccount.getId();
} else {
throw new InvalidParameterValueException("Unable to find account " + accountName + " in domain " + domainId);
}
} else {
throw new PermissionDeniedException("Invalid Domain Id or Account");
}
}
public List<QuotaUsageJoinVO> getQuotaUsage(Long accountId, String accountName, Long domainId, Integer usageType, Date startDate, Date endDate) {
if (startDate.after(endDate)) {
throw new InvalidParameterValueException("Incorrect Date Range. Start date: " + startDate + " is after end date:" + endDate);
}
@ -241,7 +221,7 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
logger.debug("Getting quota records of type [{}] for account [{}] in domain [{}], between [{}] and [{}].",
usageType, accountId, domainId, startDate, endDate);
return _quotaUsageDao.findQuotaUsage(accountId, domainId, usageType, startDate, endDate);
return quotaUsageJoinDao.findQuotaUsage(accountId, domainId, usageType, null, null, null, startDate, endDate, null);
}
@Override

View File

@ -16,38 +16,29 @@
// under the License.
package org.apache.cloudstack.api.command;
import junit.framework.TestCase;
import org.apache.cloudstack.api.response.QuotaResponseBuilder;
import org.apache.cloudstack.api.response.QuotaStatementResponse;
import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
@RunWith(MockitoJUnitRunner.class)
public class QuotaStatementCmdTest extends TestCase {
public class QuotaStatementCmdTest {
@Mock
QuotaResponseBuilder responseBuilder;
QuotaResponseBuilder responseBuilderMock;
@Test
public void testQuotaStatementCmd() throws NoSuchFieldException, IllegalAccessException {
public void executeTestVerifyCalls() {
QuotaStatementCmd cmd = new QuotaStatementCmd();
cmd.setAccountName("admin");
cmd.responseBuilder = responseBuilderMock;
Field rbField = QuotaStatementCmd.class.getDeclaredField("_responseBuilder");
rbField.setAccessible(true);
rbField.set(cmd, responseBuilder);
Mockito.doReturn(new QuotaStatementResponse()).when(responseBuilderMock).createQuotaStatementResponse(Mockito.any());
List<QuotaUsageVO> quotaUsageVOList = new ArrayList<QuotaUsageVO>();
Mockito.when(responseBuilder.getQuotaUsage(Mockito.eq(cmd))).thenReturn(quotaUsageVOList);
Mockito.when(responseBuilder.createQuotaStatementResponse(Mockito.eq(quotaUsageVOList))).thenReturn(new QuotaStatementResponse());
cmd.execute();
Mockito.verify(responseBuilder, Mockito.times(1)).getQuotaUsage(Mockito.eq(cmd));
Mockito.verify(responseBuilderMock).createQuotaStatementResponse(cmd);
}
}

View File

@ -17,6 +17,8 @@
package org.apache.cloudstack.api.response;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
@ -42,6 +44,7 @@ import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd;
import org.apache.cloudstack.api.command.QuotaCreditsListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd;
import org.apache.cloudstack.api.command.QuotaStatementCmd;
import org.apache.cloudstack.api.command.QuotaSummaryCmd;
import org.apache.cloudstack.api.command.QuotaValidateActivationRuleCmd;
import org.apache.cloudstack.context.CallContext;
@ -67,6 +70,7 @@ import org.apache.cloudstack.quota.vo.QuotaCreditsVO;
import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO;
import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO;
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO;
import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter;
import org.apache.commons.lang3.time.DateUtils;
@ -914,4 +918,199 @@ public class QuotaResponseBuilderImplTest extends TestCase {
Assert.assertTrue(formattedVariables.containsValue("accountname"));
Assert.assertTrue(formattedVariables.containsValue("zonename"));
}
@Test
public void createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformedTestUsageTypeDifferentFromNullDoNothing() {
List<QuotaUsageJoinVO> listUsage = new ArrayList<>();
quotaResponseBuilderSpy.createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformed(listUsage, 1);
Assert.assertTrue(listUsage.isEmpty());
}
@Test
public void createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformedTestUsageTypeIsNullAddDummyForAllQuotaTypes() {
List<QuotaUsageJoinVO> listUsage = new ArrayList<>();
listUsage.add(new QuotaUsageJoinVO());
quotaResponseBuilderSpy.createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformed(listUsage, null);
Assert.assertEquals(QuotaTypes.listQuotaTypes().size() + 1, listUsage.size());
QuotaTypes.listQuotaTypes().entrySet().forEach(entry -> {
Assert.assertTrue(listUsage.stream().anyMatch(usage -> usage.getUsageType() == entry.getKey() && usage.getQuotaUsed().equals(BigDecimal.ZERO)));
});
}
private List<QuotaUsageJoinVO> getQuotaUsagesForTest() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
List<QuotaUsageJoinVO> quotaUsages = new ArrayList<>();
QuotaUsageJoinVO quotaUsage = new QuotaUsageJoinVO();
quotaUsage.setAccountId(1l);
quotaUsage.setDomainId(2l);
quotaUsage.setUsageType(3);
quotaUsage.setQuotaUsed(BigDecimal.valueOf(10));
try {
quotaUsage.setStartDate(sdf.parse("2022-01-01"));
quotaUsage.setEndDate(sdf.parse("2022-01-02"));
} catch (ParseException ignored) {
}
quotaUsages.add(quotaUsage);
quotaUsage = new QuotaUsageJoinVO();
quotaUsage.setAccountId(4l);
quotaUsage.setDomainId(5l);
quotaUsage.setUsageType(3);
quotaUsage.setQuotaUsed(null);
try {
quotaUsage.setStartDate(sdf.parse("2022-01-03"));
quotaUsage.setEndDate(sdf.parse("2022-01-04"));
} catch (ParseException ignored) {
}
quotaUsages.add(quotaUsage);
quotaUsage = new QuotaUsageJoinVO();
quotaUsage.setAccountId(6l);
quotaUsage.setDomainId(7l);
quotaUsage.setUsageType(3);
quotaUsage.setQuotaUsed(BigDecimal.valueOf(5));
try {
quotaUsage.setStartDate(sdf.parse("2022-01-05"));
quotaUsage.setEndDate(sdf.parse("2022-01-06"));
} catch (ParseException ignored) {
}
quotaUsages.add(quotaUsage);
return quotaUsages;
}
@Test
public void createStatementItemTestReturnItem() {
List<QuotaUsageJoinVO> quotaUsages = getQuotaUsagesForTest();
Mockito.doNothing().when(quotaResponseBuilderSpy).setStatementItemResources(Mockito.any(), Mockito.anyInt(), Mockito.any(), Mockito.anyBoolean());
QuotaStatementItemResponse result = quotaResponseBuilderSpy.createStatementItem(0, quotaUsages, false);
QuotaUsageJoinVO expected = quotaUsages.get(0);
QuotaTypes quotaTypeExpected = QuotaTypes.listQuotaTypes().get(expected.getUsageType());
Assert.assertEquals(BigDecimal.valueOf(15), result.getQuotaUsed());
Assert.assertEquals(quotaTypeExpected.getQuotaUnit(), result.getUsageUnit());
Assert.assertEquals(quotaTypeExpected.getQuotaName(), result.getUsageName());
}
@Test
public void setStatementItemResourcesTestDoNotShowResourcesDoNothing() {
QuotaStatementItemResponse item = new QuotaStatementItemResponse(1);
quotaResponseBuilderSpy.setStatementItemResources(item, 0, getQuotaUsagesForTest(), false);
Assert.assertNull(item.getResources());
}
@Test
public void getAccountIdForQuotaStatementTestLimitsToCallingAccountForNormalUser() {
QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
Mockito.doReturn(accountMock).when(callContextMock).getCallingAccount();
Mockito.doReturn(Account.Type.NORMAL).when(accountMock).getType();
try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Long result = quotaResponseBuilderSpy.getAccountIdForQuotaStatement(cmd);
Assert.assertEquals(Long.valueOf(callerAccountMock.getAccountId()), result);
}
}
@Test
public void getAccountIdForQuotaStatementTestReturnsEntityOwnerIdWhenProvided() {
QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
Mockito.doReturn(42L).when(cmd).getEntityOwnerId();
Long result = quotaResponseBuilderSpy.getAccountIdForQuotaStatement(cmd);
Assert.assertEquals(Long.valueOf(42L), result);
}
@Test
public void getAccountIdForQuotaStatementTestLimitsToCallingAccountWhenCallerIsAdminAndDomainIsNotProvided() {
QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
Mockito.doReturn(accountMock).when(callContextMock).getCallingAccount();
Mockito.doReturn(Account.Type.ADMIN).when(accountMock).getType();
Mockito.doReturn(-1L).when(cmd).getEntityOwnerId();
Mockito.doReturn(null).when(cmd).getDomainId();
try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Long result = quotaResponseBuilderSpy.getAccountIdForQuotaStatement(cmd);
Assert.assertEquals(Long.valueOf(callerAccountMock.getAccountId()), result);
}
}
@Test
public void getAccountIdForQuotaStatementTestReturnsNullWhenCallerIsAdminAndDomainIsProvided() {
QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
Mockito.doReturn(accountMock).when(callContextMock).getCallingAccount();
Mockito.doReturn(Account.Type.ADMIN).when(accountMock).getType();
Mockito.doReturn(-1L).when(cmd).getEntityOwnerId();
Mockito.doReturn(10L).when(cmd).getDomainId();
try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Long result = quotaResponseBuilderSpy.getAccountIdForQuotaStatement(cmd);
Assert.assertNull(result);
}
}
@Test
public void getDomainIdForQuotaStatementTestReturnsAccountDomainIdWhenAccountIdIsProvided() {
QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
AccountVO account = Mockito.mock(AccountVO.class);
Mockito.doReturn(account).when(accountDaoMock).findByIdIncludingRemoved(55L);
Mockito.doReturn(77L).when(account).getDomainId();
Long result = quotaResponseBuilderSpy.getDomainIdForQuotaStatement(cmd, 55L);
Assert.assertEquals(Long.valueOf(77L), result);
}
@Test
public void getDomainIdForQuotaStatementTestReturnsProvidedDomainIdWhenAccountIdIsNull() {
QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
Mockito.doReturn(99L).when(cmd).getDomainId();
Long result = quotaResponseBuilderSpy.getDomainIdForQuotaStatement(cmd, null);
Assert.assertEquals(Long.valueOf(99L), result);
}
@Test
public void getDomainIdForQuotaStatementTestFallsBackToCallingAccountDomainIdWhenNeitherAccountNorDomainIsProvided() {
QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
Account account = Mockito.mock(Account.class);
Mockito.doReturn(null).when(cmd).getDomainId();
Mockito.doReturn(123L).when(account).getDomainId();
Mockito.doReturn(account).when(callContextMock).getCallingAccount();
try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Long result = quotaResponseBuilderSpy.getDomainIdForQuotaStatement(cmd, null);
Assert.assertEquals(123L, result.longValue());
}
}
}

View File

@ -28,6 +28,7 @@ import org.apache.cloudstack.quota.constant.QuotaTypes;
import org.apache.cloudstack.quota.dao.QuotaAccountDao;
import org.apache.cloudstack.quota.dao.QuotaBalanceDao;
import org.apache.cloudstack.quota.dao.QuotaUsageDao;
import org.apache.cloudstack.quota.dao.QuotaUsageJoinDao;
import org.apache.cloudstack.quota.vo.QuotaAccountVO;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.joda.time.DateTime;
@ -63,6 +64,8 @@ public class QuotaServiceImplTest extends TestCase {
@Mock
QuotaBalanceDao quotaBalanceDao;
@Mock
QuotaUsageJoinDao quotaUsageJoinDaoMock;
@Mock
QuotaResponseBuilder respBldr;
@Mock
private AccountVO accountVoMock;
@ -85,9 +88,9 @@ public class QuotaServiceImplTest extends TestCase {
quotaAccountDaoField.setAccessible(true);
quotaAccountDaoField.set(quotaServiceImplSpy, quotaAcc);
Field quotaUsageDaoField = QuotaServiceImpl.class.getDeclaredField("_quotaUsageDao");
Field quotaUsageDaoField = QuotaServiceImpl.class.getDeclaredField("quotaUsageJoinDao");
quotaUsageDaoField.setAccessible(true);
quotaUsageDaoField.set(quotaServiceImplSpy, quotaUsageDao);
quotaUsageDaoField.set(quotaServiceImplSpy, quotaUsageJoinDaoMock);
Field domainDaoField = QuotaServiceImpl.class.getDeclaredField("_domainDao");
domainDaoField.setAccessible(true);
@ -142,7 +145,8 @@ public class QuotaServiceImplTest extends TestCase {
final Date endDate = new Date();
quotaServiceImplSpy.getQuotaUsage(accountId, accountName, domainId, QuotaTypes.IP_ADDRESS, startDate, endDate);
Mockito.verify(quotaUsageDao, Mockito.times(1)).findQuotaUsage(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.eq(QuotaTypes.IP_ADDRESS), Mockito.any(Date.class), Mockito.any(Date.class));
Mockito.verify(quotaUsageJoinDaoMock, Mockito.times(1)).findQuotaUsage(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.eq(QuotaTypes.IP_ADDRESS), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any(Date.class), Mockito.any(Date.class), Mockito.any());
}
@Test

View File

@ -18,6 +18,9 @@ package com.cloud.hypervisor.kvm.resource;
import static com.cloud.host.Host.HOST_INSTANCE_CONVERSION;
import static com.cloud.host.Host.HOST_OVFTOOL_VERSION;
import static com.cloud.host.Host.HOST_VDDK_LIB_DIR;
import static com.cloud.host.Host.HOST_VDDK_SUPPORT;
import static com.cloud.host.Host.HOST_VDDK_VERSION;
import static com.cloud.host.Host.HOST_VIRTV2V_VERSION;
import static com.cloud.host.Host.HOST_VOLUME_ENCRYPTION;
import static org.apache.cloudstack.utils.linux.KVMHostInfo.isHostS390x;
@ -365,6 +368,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
public static final String WINDOWS_GUEST_CONVERSION_SUPPORTED_CHECK_CMD = "rpm -qa | grep -i virtio-win";
public static final String UBUNTU_WINDOWS_GUEST_CONVERSION_SUPPORTED_CHECK_CMD = "dpkg -l virtio-win";
public static final String UBUNTU_NBDKIT_PKG_CHECK_CMD = "dpkg -l nbdkit";
public static final String VDDK_AUTODETECT_PATH_CMD = "find / -type d -name 'vmware-vix-disklib-distrib' 2>/dev/null | head -n 1";
public static final int LIBVIRT_CGROUP_CPU_SHARES_MIN = 2;
public static final int LIBVIRT_CGROUP_CPU_SHARES_MAX = 262144;
@ -903,10 +907,16 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
private boolean convertInstanceVerboseMode = false;
private Map<String, String> convertInstanceEnv = null;
private String vddkLibDir = null;
private static final String libguestfsBackend = "direct";
protected boolean dpdkSupport = false;
protected String dpdkOvsPath;
protected String directDownloadTemporaryDownloadPath;
protected String cachePath;
private String vddkTransports = null;
private String vddkThumbprint = null;
private String vddkVersion = null;
private String detectedPasswordFileOption = null;
protected String javaTempDir = System.getProperty("java.io.tmpdir");
private String getEndIpFromStartIp(final String startIp, final int numIps) {
@ -971,6 +981,26 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
return convertInstanceEnv;
}
public String getVddkLibDir() {
return vddkLibDir;
}
public String getLibguestfsBackend() {
return libguestfsBackend;
}
public String getVddkTransports() {
return vddkTransports;
}
public String getVddkThumbprint() {
return vddkThumbprint;
}
public String getVddkVersion() {
return vddkVersion;
}
/**
* Defines resource's public and private network interface according to what is configured in agent.properties.
*/
@ -1180,6 +1210,37 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
setConvertInstanceEnv(convertEnvTmpDir, convertEnvVirtv2vTmpDir);
vddkLibDir = StringUtils.trimToNull(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VDDK_LIB_DIR));
if (StringUtils.isNotBlank(vddkLibDir) && !isVddkLibDirValid(vddkLibDir)) {
LOGGER.warn("Configured VDDK library dir [{}] is invalid (missing lib64/libvixDiskLib.so), attempting auto-detection", vddkLibDir);
vddkLibDir = null;
}
if (StringUtils.isBlank(vddkLibDir)) {
vddkLibDir = detectVddkLibDir();
}
if (StringUtils.isNotBlank(vddkLibDir)) {
LOGGER.info("Detected VDDK library dir: {}", vddkLibDir);
} else {
LOGGER.warn("Could not detect a valid VDDK library dir; VDDK conversion will be unavailable");
}
vddkVersion = detectVddkVersion();
if (StringUtils.isNotBlank(vddkVersion)) {
LOGGER.info("Detected nbdkit VDDK plugin version: {}", vddkVersion);
}
vddkTransports = StringUtils.trimToNull(
AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VDDK_TRANSPORTS));
vddkThumbprint = StringUtils.trimToNull(
AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VDDK_THUMBPRINT));
detectedPasswordFileOption = detectPasswordFileOption();
if (StringUtils.isNotBlank(detectedPasswordFileOption)) {
LOGGER.info("Detected virt-v2v password option: {}", detectedPasswordFileOption);
} else {
LOGGER.warn("Could not detect virt-v2v password option, VDDK conversions may fail");
}
pool = (String)params.get("pool");
if (pool == null) {
pool = "/root";
@ -4251,6 +4312,13 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
cmd.setHostTags(getHostTags());
boolean instanceConversionSupported = hostSupportsInstanceConversion();
cmd.getHostDetails().put(HOST_INSTANCE_CONVERSION, String.valueOf(instanceConversionSupported));
cmd.getHostDetails().put(HOST_VDDK_SUPPORT, String.valueOf(hostSupportsVddk()));
if (StringUtils.isNotBlank(vddkLibDir)) {
cmd.getHostDetails().put(HOST_VDDK_LIB_DIR, vddkLibDir);
}
if (StringUtils.isNotBlank(vddkVersion)) {
cmd.getHostDetails().put(HOST_VDDK_VERSION, vddkVersion);
}
if (instanceConversionSupported) {
cmd.getHostDetails().put(HOST_VIRTV2V_VERSION, getHostVirtV2vVersion());
}
@ -5990,6 +6058,66 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
return exitValue == 0;
}
public boolean hostSupportsVddk() {
return hostSupportsVddk(null);
}
public boolean hostSupportsVddk(String overriddenVddkLibDir) {
String effectiveVddkLibDir = StringUtils.trimToNull(overriddenVddkLibDir);
if (StringUtils.isBlank(effectiveVddkLibDir)) {
effectiveVddkLibDir = StringUtils.trimToNull(vddkLibDir);
}
if (StringUtils.isBlank(effectiveVddkLibDir) || !isVddkLibDirValid(effectiveVddkLibDir)) {
effectiveVddkLibDir = detectVddkLibDir();
}
return hostSupportsInstanceConversion() && isVddkLibDirValid(effectiveVddkLibDir) && StringUtils.isNotBlank(detectVddkVersion());
}
protected boolean isVddkLibDirValid(String path) {
if (StringUtils.isBlank(path)) {
return false;
}
File libDir = new File(path, "lib64");
if (!libDir.isDirectory()) {
return false;
}
File[] libs = libDir.listFiles((dir, name) -> name.startsWith("libvixDiskLib.so"));
return libs != null && libs.length > 0;
}
protected String detectVddkLibDir() {
String detectedPath = StringUtils.trimToNull(Script.runSimpleBashScript(VDDK_AUTODETECT_PATH_CMD));
if (StringUtils.isNotBlank(detectedPath) && isVddkLibDirValid(detectedPath)) {
return detectedPath;
}
return null;
}
protected String detectVddkVersion() {
try {
ProcessBuilder pb = new ProcessBuilder("nbdkit", "vddk", "--version");
Process process = pb.start();
String output = new String(process.getInputStream().readAllBytes());
process.waitFor();
if (StringUtils.isBlank(output)) {
return null;
}
for (String line : output.split("\\R")) {
String trimmed = StringUtils.trimToEmpty(line);
if (trimmed.startsWith("vddk ")) {
return StringUtils.trimToNull(trimmed.substring("vddk ".length()));
}
}
return null;
} catch (Exception e) {
LOGGER.error("Failed to detect vddk version: {}", e.getMessage());
return null;
}
}
public boolean hostSupportsWindowsGuestConversion() {
if (isUbuntuOrDebianHost()) {
int exitValue = Script.runSimpleBashScriptForExitValue(UBUNTU_WINDOWS_GUEST_CONVERSION_SUPPORTED_CHECK_CMD);
@ -6004,6 +6132,40 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
return exitValue == 0;
}
/**
* Detect which password option virt-v2v supports by examining its --help output
* @return "-ip" if supported (virt-v2v >= 2.8.1), "--password-file" if older version, or null if detection fails
*/
protected String detectPasswordFileOption() {
try {
ProcessBuilder pb = new ProcessBuilder("virt-v2v", "--help");
Process process = pb.start();
String output = new String(process.getInputStream().readAllBytes());
process.waitFor();
if (output.contains("-ip <filename>")) {
return "-ip";
} else if (output.contains("--password-file")) {
return "--password-file";
} else {
LOGGER.error("virt-v2v does not support -ip or --password-file");
return null;
}
} catch (Exception e) {
LOGGER.error("Failed to detect virt-v2v password option: {}", e.getMessage());
return null;
}
}
/**
* Get the detected password file option for virt-v2v
* @return the password option ("-ip" or "--password-file") or null if not detected
*/
public String getDetectedPasswordFileOption() {
return detectedPasswordFileOption;
}
public String getHostVirtV2vVersion() {
if (!hostSupportsInstanceConversion()) {
return "";

View File

@ -30,7 +30,15 @@ public class LibvirtCheckConvertInstanceCommandWrapper extends CommandWrapper<Ch
@Override
public Answer execute(CheckConvertInstanceCommand cmd, LibvirtComputingResource serverResource) {
if (!serverResource.hostSupportsInstanceConversion()) {
if (cmd.isUseVddk()) {
if (!serverResource.hostSupportsVddk(cmd.getVddkLibDir())) {
String msg = String.format("Cannot convert the instance from VMware using VDDK on host %s. " +
"Please make sure virt-v2v%s, nbdkit-vddk and a valid VDDK library directory are available on the host.",
serverResource.getPrivateIp(), serverResource.isUbuntuOrDebianHost() ? ", nbdkit" : "");
logger.info(msg);
return new CheckConvertInstanceAnswer(cmd, false, msg);
}
} else if (!serverResource.hostSupportsInstanceConversion()) {
String msg = String.format("Cannot convert the instance from VMware as the virt-v2v binary is not found on host %s. " +
"Please install virt-v2v%s on the host before attempting the instance conversion.", serverResource.getPrivateIp(), serverResource.isUbuntuOrDebianHost()? ", nbdkit" : "");
logger.info(msg);

View File

@ -20,10 +20,17 @@ package com.cloud.hypervisor.kvm.resource.wrapper;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Locale;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.commons.collections4.MapUtils;
@ -51,6 +58,7 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<Convert
private static final List<Hypervisor.HypervisorType> supportedInstanceConvertSourceHypervisors =
List.of(Hypervisor.HypervisorType.VMware);
private static final Pattern SHA1_FINGERPRINT_PATTERN = Pattern.compile("(?i)(?:SHA1\\s+)?Fingerprint\\s*=\\s*([0-9A-F:]+)");
@Override
public Answer execute(ConvertInstanceCommand cmd, LibvirtComputingResource serverResource) {
@ -61,7 +69,8 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<Convert
DataStoreTO conversionTemporaryLocation = cmd.getConversionTemporaryLocation();
long timeout = (long) cmd.getWait() * 1000;
String extraParams = cmd.getExtraParams();
String originalVMName = cmd.getOriginalVMName(); // For logging purposes, as the sourceInstance may have been cloned
boolean useVddk = cmd.isUseVddk();
String originalVMName = cmd.getOriginalVMName();
if (cmd.getCheckConversionSupport() && !serverResource.hostSupportsInstanceConversion()) {
String msg = String.format("Cannot convert the instance %s from VMware as the virt-v2v binary is not found. " +
@ -84,61 +93,75 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<Convert
logger.info(String.format("(%s) Attempting to convert the instance %s from %s to KVM",
originalVMName, sourceInstanceName, sourceHypervisorType));
final String temporaryConvertPath = temporaryStoragePool.getLocalPath();
String ovfTemplateDirOnConversionLocation;
String sourceOVFDirPath;
boolean ovfExported = false;
if (cmd.getExportOvfToConversionLocation()) {
String exportInstanceOVAUrl = getExportInstanceOVAUrl(sourceInstance, originalVMName);
if (StringUtils.isBlank(exportInstanceOVAUrl)) {
String err = String.format("Couldn't export OVA for the VM %s, due to empty url", sourceInstanceName);
logger.error(String.format("(%s) %s", originalVMName, err));
return new Answer(cmd, false, err);
}
int noOfThreads = cmd.getThreadsCountToExportOvf();
if (noOfThreads > 1 && !serverResource.ovfExportToolSupportsParallelThreads()) {
noOfThreads = 0;
}
ovfTemplateDirOnConversionLocation = UUID.randomUUID().toString();
temporaryStoragePool.createFolder(ovfTemplateDirOnConversionLocation);
sourceOVFDirPath = String.format("%s/%s/", temporaryConvertPath, ovfTemplateDirOnConversionLocation);
ovfExported = exportOVAFromVMOnVcenter(exportInstanceOVAUrl, sourceOVFDirPath, noOfThreads, originalVMName, timeout);
if (!ovfExported) {
String err = String.format("Export OVA for the VM %s failed", sourceInstanceName);
logger.error(String.format("(%s) %s", originalVMName, err));
return new Answer(cmd, false, err);
}
sourceOVFDirPath = String.format("%s%s/", sourceOVFDirPath, sourceInstanceName);
} else {
ovfTemplateDirOnConversionLocation = cmd.getTemplateDirOnConversionLocation();
sourceOVFDirPath = String.format("%s/%s/", temporaryConvertPath, ovfTemplateDirOnConversionLocation);
}
logger.info(String.format("(%s) Attempting to convert the OVF %s of the instance %s from %s to KVM",
originalVMName, ovfTemplateDirOnConversionLocation, sourceInstanceName, sourceHypervisorType));
final String temporaryConvertUuid = UUID.randomUUID().toString();
boolean verboseModeEnabled = serverResource.isConvertInstanceVerboseModeEnabled();
boolean cleanupSecondaryStorage = false;
boolean ovfExported = false;
String ovfTemplateDirOnConversionLocation = null;
try {
boolean result = performInstanceConversion(originalVMName, sourceOVFDirPath, temporaryConvertPath, temporaryConvertUuid,
timeout, verboseModeEnabled, extraParams, serverResource);
boolean result;
if (useVddk) {
logger.info("({}) Using VDDK-based conversion (direct from VMware)", originalVMName);
String vddkLibDir = resolveVddkSetting(cmd.getVddkLibDir(), serverResource.getVddkLibDir());
if (StringUtils.isBlank(vddkLibDir)) {
String err = String.format("VDDK lib dir is not configured on the host. " +
"Set '%s' in agent.properties or in details parameter of the import api call to use VDDK-based conversion.", "vddk.lib.dir");
logger.error("({}) {}", originalVMName, err);
return new Answer(cmd, false, err);
}
String vddkTransports = resolveVddkSetting(cmd.getVddkTransports(), serverResource.getVddkTransports());
String configuredVddkThumbprint = resolveVddkSetting(cmd.getVddkThumbprint(), serverResource.getVddkThumbprint());
String passwordOption = serverResource.getDetectedPasswordFileOption();
result = performInstanceConversionUsingVddk(sourceInstance, originalVMName, temporaryConvertPath,
vddkLibDir, serverResource.getLibguestfsBackend(), vddkTransports, configuredVddkThumbprint,
timeout, verboseModeEnabled, extraParams, temporaryConvertUuid, passwordOption);
} else {
logger.info("({}) Using OVF-based conversion (export + local convert)", originalVMName);
String sourceOVFDirPath;
if (cmd.getExportOvfToConversionLocation()) {
String exportInstanceOVAUrl = getExportInstanceOVAUrl(sourceInstance, originalVMName);
if (StringUtils.isBlank(exportInstanceOVAUrl)) {
String err = String.format("Couldn't export OVA for the VM %s, due to empty url", sourceInstanceName);
logger.error("({}) {}", originalVMName, err);
return new Answer(cmd, false, err);
}
int noOfThreads = cmd.getThreadsCountToExportOvf();
if (noOfThreads > 1 && !serverResource.ovfExportToolSupportsParallelThreads()) {
noOfThreads = 0;
}
ovfTemplateDirOnConversionLocation = UUID.randomUUID().toString();
temporaryStoragePool.createFolder(ovfTemplateDirOnConversionLocation);
sourceOVFDirPath = String.format("%s/%s/", temporaryConvertPath, ovfTemplateDirOnConversionLocation);
ovfExported = exportOVAFromVMOnVcenter(exportInstanceOVAUrl, sourceOVFDirPath, noOfThreads, originalVMName, timeout);
if (!ovfExported) {
String err = String.format("Export OVA for the VM %s failed", sourceInstanceName);
logger.error("({}) {}", originalVMName, err);
return new Answer(cmd, false, err);
}
sourceOVFDirPath = String.format("%s%s/", sourceOVFDirPath, sourceInstanceName);
} else {
ovfTemplateDirOnConversionLocation = cmd.getTemplateDirOnConversionLocation();
sourceOVFDirPath = String.format("%s/%s/", temporaryConvertPath, ovfTemplateDirOnConversionLocation);
}
result = performInstanceConversion(originalVMName, sourceOVFDirPath, temporaryConvertPath, temporaryConvertUuid,
timeout, verboseModeEnabled, extraParams, serverResource);
}
if (!result) {
String err = String.format(
"The virt-v2v conversion for the OVF %s failed. Please check the agent logs " +
"for the virt-v2v output. Please try on a different kvm host which " +
"has a different virt-v2v version.",
ovfTemplateDirOnConversionLocation);
logger.error(String.format("(%s) %s", originalVMName, err));
String err = String.format("Instance conversion failed for VM %s. Please check virt-v2v logs.", sourceInstanceName);
logger.error("({}) {}", originalVMName, err);
return new Answer(cmd, false, err);
}
return new ConvertInstanceAnswer(cmd, temporaryConvertUuid);
} catch (Exception e) {
String error = String.format("Error converting instance %s from %s, due to: %s",
sourceInstanceName, sourceHypervisorType, e.getMessage());
logger.error(String.format("(%s) %s", originalVMName, error), e);
String error = String.format("Error converting instance %s from %s, due to: %s", sourceInstanceName, sourceHypervisorType, e.getMessage());
logger.error("({}) {}", originalVMName, error, e);
cleanupSecondaryStorage = true;
return new Answer(cmd, false, error);
} finally {
@ -275,4 +298,198 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<Convert
protected String encodeUsername(String username) {
return URLEncoder.encode(username, Charset.defaultCharset());
}
private String resolveVddkSetting(String commandValue, String agentValue) {
return StringUtils.defaultIfBlank(StringUtils.trimToNull(commandValue), StringUtils.trimToNull(agentValue));
}
protected boolean performInstanceConversionUsingVddk(RemoteInstanceTO vmwareInstance, String originalVMName,
String temporaryConvertFolder, String vddkLibDir,
String libguestfsBackend, String vddkTransports,
String configuredVddkThumbprint,
long timeout, boolean verboseModeEnabled, String extraParams,
String temporaryConvertUuid, String passwordOption) {
String vcenterPassword = vmwareInstance.getVcenterPassword();
if (StringUtils.isBlank(vcenterPassword)) {
logger.error("({}) Could not determine vCenter password for {}", originalVMName, vmwareInstance.getVcenterHost());
return false;
}
String passwordFilePath = String.format("/tmp/v2v.pass.cloud.%s.%s",
StringUtils.defaultIfBlank(vmwareInstance.getVcenterHost(), "unknown"),
UUID.randomUUID());
try {
Files.writeString(Path.of(passwordFilePath), vcenterPassword);
Files.setPosixFilePermissions(Path.of(passwordFilePath), Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE));
logger.debug("({}) Written vCenter password to {}", originalVMName, passwordFilePath);
} catch (Exception e) {
logger.error("({}) Failed to write vCenter password file {}: {}", originalVMName, passwordFilePath, e.getMessage());
return false;
}
try {
String vpxUrl = buildVpxUrl(vmwareInstance);
StringBuilder cmd = new StringBuilder();
cmd.append("export LIBGUESTFS_BACKEND=").append(libguestfsBackend).append(" && ");
cmd.append("virt-v2v ");
cmd.append("--root first ");
cmd.append("-ic '").append(vpxUrl).append("' ");
if (StringUtils.isBlank(passwordOption)) {
logger.error("({}) Could not determine supported password file option for virt-v2v", originalVMName);
return false;
}
cmd.append(passwordOption).append(" ").append(passwordFilePath).append(" ");
cmd.append("-it vddk ");
cmd.append("-io vddk-libdir=").append(vddkLibDir).append(" ");
String vddkThumbprint = StringUtils.trimToNull(configuredVddkThumbprint);
if (StringUtils.isBlank(vddkThumbprint)) {
vddkThumbprint = getVcenterThumbprint(vmwareInstance.getVcenterHost(), timeout, originalVMName);
}
if (StringUtils.isBlank(vddkThumbprint)) {
logger.error("({}) Could not determine vCenter thumbprint for {}", originalVMName, vmwareInstance.getVcenterHost());
return false;
}
cmd.append("-io vddk-thumbprint=").append(vddkThumbprint).append(" ");
if (StringUtils.isNotBlank(vddkTransports)) {
cmd.append("-io vddk-transports=").append(vddkTransports).append(" ");
}
cmd.append(vmwareInstance.getInstanceName()).append(" ");
cmd.append("-o local ");
cmd.append("-os ").append(temporaryConvertFolder).append(" ");
cmd.append("-of qcow2 ");
cmd.append("-on ").append(temporaryConvertUuid).append(" ");
if (verboseModeEnabled) {
cmd.append("-v ");
}
if (StringUtils.isNotBlank(extraParams)) {
cmd.append(extraParams).append(" ");
}
Script script = new Script("/bin/bash", timeout, logger);
script.add("-c");
script.add(cmd.toString());
String logPrefix = String.format("(%s) virt-v2v vddk import", originalVMName);
OutputInterpreter.LineByLineOutputLogger outputLogger =
new OutputInterpreter.LineByLineOutputLogger(logger, logPrefix);
logger.info("({}) Starting virt-v2v VDDK conversion", originalVMName);
script.execute(outputLogger);
int exitValue = script.getExitValue();
if (exitValue != 0) {
logger.error("({}) virt-v2v failed with exit code {}", originalVMName, exitValue);
}
return exitValue == 0;
} finally {
try {
Files.deleteIfExists(Path.of(passwordFilePath));
logger.debug("({}) Deleted password file {}", originalVMName, passwordFilePath);
} catch (Exception e) {
logger.warn("({}) Failed to delete password file {}: {}", originalVMName, passwordFilePath, e.getMessage());
}
}
}
protected String getVcenterThumbprint(String vcenterHost, long timeout, String originalVMName) {
if (StringUtils.isBlank(vcenterHost)) {
return null;
}
String endpoint = String.format("%s:443", vcenterHost);
String command = String.format("openssl s_client -connect '%s' </dev/null 2>/dev/null | " +
"openssl x509 -fingerprint -sha1 -noout", endpoint);
Script script = new Script("/bin/bash", timeout, logger);
script.add("-c");
script.add(command);
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
script.execute(parser);
String output = parser.getLines();
if (script.getExitValue() != 0) {
logger.error("({}) Failed to fetch vCenter thumbprint for {}", originalVMName, vcenterHost);
return null;
}
String thumbprint = extractSha1Fingerprint(output);
if (StringUtils.isBlank(thumbprint)) {
logger.error("({}) Failed to parse vCenter thumbprint from output for {}", originalVMName, vcenterHost);
return null;
}
return thumbprint;
}
private String extractSha1Fingerprint(String output) {
String parsedOutput = StringUtils.trimToEmpty(output);
if (StringUtils.isBlank(parsedOutput)) {
return null;
}
for (String line : parsedOutput.split("\\R")) {
String trimmedLine = StringUtils.trimToEmpty(line);
if (StringUtils.isBlank(trimmedLine)) {
continue;
}
Matcher matcher = SHA1_FINGERPRINT_PATTERN.matcher(trimmedLine);
if (matcher.find()) {
return matcher.group(1).toUpperCase(Locale.ROOT);
}
// Fallback for raw fingerprint-only output.
if (trimmedLine.matches("(?i)[0-9a-f]{2}(:[0-9a-f]{2})+")) {
return trimmedLine.toUpperCase(Locale.ROOT);
}
}
return null;
}
/**
* Build vpx:// URL for virt-v2v
*
* Format:
* vpx://user@vcenter/DC/cluster/host?no_verify=1
*/
private String buildVpxUrl(RemoteInstanceTO vmwareInstance) {
String vmName = vmwareInstance.getInstanceName();
String vcenter = vmwareInstance.getVcenterHost();
String username = vmwareInstance.getVcenterUsername();
String datacenter = vmwareInstance.getDatacenterName();
String cluster = vmwareInstance.getClusterName();
String host = vmwareInstance.getHostName();
String encodedUsername = encodeUsername(username);
StringBuilder url = new StringBuilder();
url.append("vpx://")
.append(encodedUsername)
.append("@")
.append(vcenter)
.append("/")
.append(datacenter);
if (StringUtils.isNotBlank(cluster)) {
url.append("/").append(cluster);
}
if (StringUtils.isNotBlank(host)) {
url.append("/").append(host);
}
url.append("?no_verify=1");
logger.info("({}) Using VPX URL: {}", vmName, url);
return url.toString();
}
}

View File

@ -259,6 +259,12 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
final int migrateDowntime = libvirtComputingResource.getMigrateDowntime();
boolean isMigrateDowntimeSet = false;
final int migrateWait = libvirtComputingResource.getMigrateWait();
logger.info("vm.migrate.wait value set to: {} secs for VM: {}", migrateWait, vmName);
final int migratePauseAfter = libvirtComputingResource.getMigratePauseAfter();
logger.info("vm.migrate.pauseafter value set to: {} ms for VM: {}", migratePauseAfter, vmName);
while (!executor.isTerminated()) {
Thread.sleep(100);
sleeptime += 100;
@ -278,8 +284,6 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
}
// abort the vm migration if the job is executed more than vm.migrate.wait
final int migrateWait = libvirtComputingResource.getMigrateWait();
logger.info("vm.migrate.wait value set to: {}for VM: {}", migrateWait, vmName);
if (migrateWait > 0 && sleeptime > migrateWait * 1000) {
DomainState state = null;
try {
@ -306,8 +310,6 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
}
// pause vm if we meet the vm.migrate.pauseafter threshold and not already paused
final int migratePauseAfter = libvirtComputingResource.getMigratePauseAfter();
logger.info("vm.migrate.pauseafter value set to: {} for VM: {}", migratePauseAfter, vmName);
if (migratePauseAfter > 0 && sleeptime > migratePauseAfter) {
DomainState state = null;
try {

View File

@ -34,6 +34,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.utils.script.Script;
import org.apache.commons.lang3.StringUtils;
@ResourceWrapper(handles = ReadyCommand.class)
public final class LibvirtReadyCommandWrapper extends CommandWrapper<ReadyCommand, Answer, LibvirtComputingResource> {
@ -50,6 +51,9 @@ public final class LibvirtReadyCommandWrapper extends CommandWrapper<ReadyComman
if (libvirtComputingResource.hostSupportsInstanceConversion()) {
hostDetails.put(Host.HOST_VIRTV2V_VERSION, libvirtComputingResource.getHostVirtV2vVersion());
}
hostDetails.put(Host.HOST_VDDK_SUPPORT, Boolean.toString(libvirtComputingResource.hostSupportsVddk()));
hostDetails.put(Host.HOST_VDDK_LIB_DIR, StringUtils.defaultString(libvirtComputingResource.getVddkLibDir()));
hostDetails.put(Host.HOST_VDDK_VERSION, StringUtils.defaultString(libvirtComputingResource.getVddkVersion()));
if (libvirtComputingResource.hostSupportsOvfExport()) {
hostDetails.put(Host.HOST_OVFTOOL_VERSION, libvirtComputingResource.getHostOvfToolVersion());

View File

@ -52,6 +52,7 @@ public class LibvirtCheckConvertInstanceCommandWrapperTest {
@Test
public void testCheckInstanceCommand_success() {
Mockito.when(checkConvertInstanceCommandMock.isUseVddk()).thenReturn(false);
Mockito.when(libvirtComputingResourceMock.hostSupportsInstanceConversion()).thenReturn(true);
Answer answer = checkConvertInstanceCommandWrapper.execute(checkConvertInstanceCommandMock, libvirtComputingResourceMock);
assertTrue(answer.getResult());
@ -59,9 +60,33 @@ public class LibvirtCheckConvertInstanceCommandWrapperTest {
@Test
public void testCheckInstanceCommand_failure() {
Mockito.when(checkConvertInstanceCommandMock.isUseVddk()).thenReturn(false);
Mockito.when(libvirtComputingResourceMock.hostSupportsInstanceConversion()).thenReturn(false);
Answer answer = checkConvertInstanceCommandWrapper.execute(checkConvertInstanceCommandMock, libvirtComputingResourceMock);
assertFalse(answer.getResult());
assertTrue(StringUtils.isNotBlank(answer.getDetails()));
}
@Test
public void testCheckInstanceCommand_vddkSuccess() {
Mockito.when(checkConvertInstanceCommandMock.isUseVddk()).thenReturn(true);
Mockito.when(checkConvertInstanceCommandMock.getVddkLibDir()).thenReturn("/opt/vmware-vddk/vmware-vix-disklib-distrib");
Mockito.when(libvirtComputingResourceMock.hostSupportsVddk("/opt/vmware-vddk/vmware-vix-disklib-distrib")).thenReturn(true);
Answer answer = checkConvertInstanceCommandWrapper.execute(checkConvertInstanceCommandMock, libvirtComputingResourceMock);
assertTrue(answer.getResult());
}
@Test
public void testCheckInstanceCommand_vddkFailure() {
Mockito.when(checkConvertInstanceCommandMock.isUseVddk()).thenReturn(true);
Mockito.when(checkConvertInstanceCommandMock.getVddkLibDir()).thenReturn("/opt/vmware-vddk/vmware-vix-disklib-distrib");
Mockito.when(libvirtComputingResourceMock.hostSupportsVddk("/opt/vmware-vddk/vmware-vix-disklib-distrib")).thenReturn(false);
Answer answer = checkConvertInstanceCommandWrapper.execute(checkConvertInstanceCommandMock, libvirtComputingResourceMock);
assertFalse(answer.getResult());
assertTrue(StringUtils.isNotBlank(answer.getDetails()));
}
}

View File

@ -18,6 +18,7 @@
//
package com.cloud.hypervisor.kvm.resource.wrapper;
import java.nio.file.Files;
import java.util.List;
import java.util.UUID;
@ -189,4 +190,127 @@ public class LibvirtConvertInstanceCommandWrapperTest {
Mockito.verify(script).add("-x");
Mockito.verify(script).add("-v");
}
@Test
public void testPerformInstanceConversionUsingVddkUsesConfiguredLibguestfsBackend() {
RemoteInstanceTO remoteInstanceTO = Mockito.mock(RemoteInstanceTO.class);
Mockito.when(remoteInstanceTO.getVcenterHost()).thenReturn("vcenter.local");
Mockito.when(remoteInstanceTO.getVcenterUsername()).thenReturn("administrator@vsphere.local");
Mockito.when(remoteInstanceTO.getVcenterPassword()).thenReturn("secret");
Mockito.when(remoteInstanceTO.getDatacenterName()).thenReturn("dc1");
Mockito.when(remoteInstanceTO.getClusterName()).thenReturn("cluster1");
Mockito.when(remoteInstanceTO.getHostName()).thenReturn("host1");
Mockito.doReturn("28:19:A6:1C:90:ED:46:D7:1C:86:BC:F6:13:52:F0:B9:19:81:0D:81")
.when(convertInstanceCommandWrapper).getVcenterThumbprint(Mockito.anyString(), Mockito.anyLong(), Mockito.anyString());
try (MockedStatic<Files> filesMock = Mockito.mockStatic(Files.class);
MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) -> {
Mockito.when(mock.execute(Mockito.any())).thenReturn("");
Mockito.when(mock.getExitValue()).thenReturn(0);
})) {
filesMock.when(() -> Files.writeString(Mockito.argThat(path -> path.toString().contains("/tmp/v2v.pass.cloud.vcenter.local.")), Mockito.eq("secret")))
.thenAnswer(invocation -> invocation.getArgument(0));
filesMock.when(() -> Files.deleteIfExists(Mockito.argThat(path -> path.toString().contains("/tmp/v2v.pass.cloud.vcenter.local."))))
.thenReturn(true);
boolean result = convertInstanceCommandWrapper.performInstanceConversionUsingVddk(
remoteInstanceTO, vmName, "/tmp/convert", "/opt/vddk", "libvirt", null, null, 1000L, false, null, "tmp-uuid", "-ip");
Assert.assertTrue(result);
Script scriptMock = ignored.constructed().get(0);
Mockito.verify(scriptMock).add("-c");
Mockito.verify(scriptMock).add(Mockito.contains("export LIBGUESTFS_BACKEND=libvirt &&"));
Mockito.verify(scriptMock).add(Mockito.contains("-ip /tmp/v2v.pass.cloud.vcenter.local."));
Mockito.verify(scriptMock).add(Mockito.contains(" -on tmp-uuid "));
Mockito.verify(scriptMock).add(Mockito.contains("-io vddk-thumbprint=28:19:A6:1C:90:ED:46:D7:1C:86:BC:F6:13:52:F0:B9:19:81:0D:81 "));
}
}
@Test
public void testPerformInstanceConversionUsingVddkUsesConfiguredTransportsOrder() {
RemoteInstanceTO remoteInstanceTO = Mockito.mock(RemoteInstanceTO.class);
Mockito.when(remoteInstanceTO.getVcenterHost()).thenReturn("vcenter.local");
Mockito.when(remoteInstanceTO.getVcenterUsername()).thenReturn("administrator@vsphere.local");
Mockito.when(remoteInstanceTO.getVcenterPassword()).thenReturn("secret");
Mockito.when(remoteInstanceTO.getDatacenterName()).thenReturn("dc1");
Mockito.when(remoteInstanceTO.getClusterName()).thenReturn("cluster1");
Mockito.when(remoteInstanceTO.getHostName()).thenReturn("host1");
Mockito.doReturn("28:19:A6:1C:90:ED:46:D7:1C:86:BC:F6:13:52:F0:B9:19:81:0D:81")
.when(convertInstanceCommandWrapper).getVcenterThumbprint(Mockito.anyString(), Mockito.anyLong(), Mockito.anyString());
try (MockedStatic<Files> filesMock = Mockito.mockStatic(Files.class);
MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) -> {
Mockito.when(mock.execute(Mockito.any())).thenReturn("");
Mockito.when(mock.getExitValue()).thenReturn(0);
})) {
filesMock.when(() -> Files.writeString(Mockito.argThat(path -> path.toString().contains("/tmp/v2v.pass.cloud.vcenter.local.")), Mockito.eq("secret")))
.thenAnswer(invocation -> invocation.getArgument(0));
filesMock.when(() -> Files.deleteIfExists(Mockito.argThat(path -> path.toString().contains("/tmp/v2v.pass.cloud.vcenter.local."))))
.thenReturn(true);
boolean result = convertInstanceCommandWrapper.performInstanceConversionUsingVddk(
remoteInstanceTO, vmName, "/tmp/convert", "/opt/vddk", "direct", "nbd:nbdssl", null, 1000L, false, null, "tmp-uuid", "-ip");
Assert.assertTrue(result);
Script scriptMock = ignored.constructed().get(0);
Mockito.verify(scriptMock).add(Mockito.contains("-io vddk-transports=nbd:nbdssl "));
}
}
@Test
public void testPerformInstanceConversionUsingVddkFailsWhenThumbprintUnavailable() {
RemoteInstanceTO remoteInstanceTO = Mockito.mock(RemoteInstanceTO.class);
Mockito.when(remoteInstanceTO.getVcenterHost()).thenReturn("vcenter.local");
Mockito.when(remoteInstanceTO.getVcenterUsername()).thenReturn("administrator@vsphere.local");
Mockito.when(remoteInstanceTO.getVcenterPassword()).thenReturn("secret");
Mockito.when(remoteInstanceTO.getDatacenterName()).thenReturn("dc1");
Mockito.when(remoteInstanceTO.getClusterName()).thenReturn("cluster1");
Mockito.when(remoteInstanceTO.getHostName()).thenReturn("host1");
Mockito.doReturn(null)
.when(convertInstanceCommandWrapper).getVcenterThumbprint(Mockito.anyString(), Mockito.anyLong(), Mockito.anyString());
try (MockedStatic<Files> filesMock = Mockito.mockStatic(Files.class)) {
filesMock.when(() -> Files.writeString(Mockito.argThat(path -> path.toString().contains("/tmp/v2v.pass.cloud.vcenter.local.")), Mockito.eq("secret")))
.thenAnswer(invocation -> invocation.getArgument(0));
filesMock.when(() -> Files.deleteIfExists(Mockito.argThat(path -> path.toString().contains("/tmp/v2v.pass.cloud.vcenter.local."))))
.thenReturn(true);
boolean result = convertInstanceCommandWrapper.performInstanceConversionUsingVddk(
remoteInstanceTO, vmName, "/tmp/convert", "/opt/vddk", "direct", null, null, 1000L, false, null, "tmp-uuid", "-ip");
Assert.assertFalse(result);
}
}
@Test
public void testPerformInstanceConversionUsingVddkUsesConfiguredThumbprintFromAgentProperty() {
RemoteInstanceTO remoteInstanceTO = Mockito.mock(RemoteInstanceTO.class);
Mockito.when(remoteInstanceTO.getVcenterHost()).thenReturn("vcenter.local");
Mockito.when(remoteInstanceTO.getVcenterUsername()).thenReturn("administrator@vsphere.local");
Mockito.when(remoteInstanceTO.getVcenterPassword()).thenReturn("secret");
Mockito.when(remoteInstanceTO.getDatacenterName()).thenReturn("dc1");
Mockito.when(remoteInstanceTO.getClusterName()).thenReturn("cluster1");
Mockito.when(remoteInstanceTO.getHostName()).thenReturn("host1");
try (MockedStatic<Files> filesMock = Mockito.mockStatic(Files.class);
MockedConstruction<Script> ignored = Mockito.mockConstruction(Script.class, (mock, context) -> {
Mockito.when(mock.execute(Mockito.any())).thenReturn("");
Mockito.when(mock.getExitValue()).thenReturn(0);
})) {
filesMock.when(() -> Files.writeString(Mockito.argThat(path -> path.toString().contains("/tmp/v2v.pass.cloud.vcenter.local.")), Mockito.eq("secret")))
.thenAnswer(invocation -> invocation.getArgument(0));
filesMock.when(() -> Files.deleteIfExists(Mockito.argThat(path -> path.toString().contains("/tmp/v2v.pass.cloud.vcenter.local."))))
.thenReturn(true);
boolean result = convertInstanceCommandWrapper.performInstanceConversionUsingVddk(
remoteInstanceTO, vmName, "/tmp/convert", "/opt/vddk", "direct", null,
"AA:BB:CC:DD:EE", 1000L, false, null, "tmp-uuid", "-ip");
Assert.assertTrue(result);
Script scriptMock = ignored.constructed().get(0);
Mockito.verify(scriptMock).add(Mockito.contains("-io vddk-thumbprint=AA:BB:CC:DD:EE "));
Mockito.verify(convertInstanceCommandWrapper, Mockito.never())
.getVcenterThumbprint(Mockito.anyString(), Mockito.anyLong(), Mockito.anyString());
}
}
}

View File

@ -581,7 +581,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
List<FirewallRuleVO> firewallRules = firewallRulesDao.listByIpPurposeProtocolAndNotRevoked(publicIp.getId(), FirewallRule.Purpose.Firewall, NetUtils.TCP_PROTO);
for (FirewallRuleVO firewallRule : firewallRules) {
PortForwardingRuleVO pfRule = portForwardingRulesDao.findByNetworkAndPorts(networkId, firewallRule.getSourcePortStart(), firewallRule.getSourcePortEnd());
if (firewallRule.getSourcePortStart() == CLUSTER_NODES_DEFAULT_START_SSH_PORT || (Objects.nonNull(pfRule) && pfRule.getDestinationPortStart() == DEFAULT_SSH_PORT) ) {
if (Objects.equals(firewallRule.getSourcePortStart(), CLUSTER_NODES_DEFAULT_START_SSH_PORT) || (Objects.nonNull(pfRule) && pfRule.getDestinationPortStart() == DEFAULT_SSH_PORT) ) {
rule = firewallRule;
firewallService.revokeIngressFwRule(firewallRule.getId(), true);
logger.debug("The SSH firewall rule {} with the id {} was revoked", firewallRule.getName(), firewallRule.getId());

View File

@ -135,10 +135,14 @@ public class KubernetesClusterScaleWorker extends KubernetesClusterResourceModif
// Remove existing SSH firewall rules
FirewallRule firewallRule = removeSshFirewallRule(publicIp, network.getId());
int existingFirewallRuleSourcePortEnd;
if (firewallRule == null) {
throw new ManagementServerException("Firewall rule for node SSH access can't be provisioned");
logger.warn("SSH firewall rule not found for Kubernetes cluster: {}. It may have been manually deleted or modified.", kubernetesCluster.getName());
existingFirewallRuleSourcePortEnd = CLUSTER_NODES_DEFAULT_START_SSH_PORT + clusterVMIds.size() - 1;
} else {
existingFirewallRuleSourcePortEnd = firewallRule.getSourcePortEnd();
}
int existingFirewallRuleSourcePortEnd = firewallRule.getSourcePortEnd();
try {
removePortForwardingRules(publicIp, network, owner, CLUSTER_NODES_DEFAULT_START_SSH_PORT, existingFirewallRuleSourcePortEnd);
} catch (ResourceUnavailableException e) {

View File

@ -214,7 +214,8 @@ public class ElasticLoadBalancerManagerImpl extends ManagerBase implements Elast
maxconn = offering.getConcurrentConnections().toString();
}
LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lbs, elbVm.getPublicIpAddress(), _nicDao.getIpAddress(guestNetworkId, elbVm.getId()),
elbVm.getPrivateIpAddress(), null, null, maxconn, offering.isKeepAliveEnabled());
elbVm.getPrivateIpAddress(), null, null, maxconn, offering.isKeepAliveEnabled(),
NetworkOrchestrationService.NETWORK_LB_HAPROXY_IDLE_TIMEOUT.value());
cmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, elbVm.getPrivateIpAddress());
cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, elbVm.getInstanceName());
//FIXME: why are we setting attributes directly? Ick!! There should be accessors and

View File

@ -513,7 +513,8 @@ public class InternalLoadBalancerVMManagerImpl extends ManagerBase implements In
}
final LoadBalancerConfigCommand cmd =
new LoadBalancerConfigCommand(lbs, guestNic.getIPv4Address(), guestNic.getIPv4Address(), internalLbVm.getPrivateIpAddress(), _itMgr.toNicTO(guestNicProfile,
internalLbVm.getHypervisorType()), internalLbVm.getVpcId(), maxconn, offering.isKeepAliveEnabled());
internalLbVm.getHypervisorType()), internalLbVm.getVpcId(), maxconn, offering.isKeepAliveEnabled(),
NetworkOrchestrationService.NETWORK_LB_HAPROXY_IDLE_TIMEOUT.value());
cmd.lbStatsVisibility = _configDao.getValue(Config.NetworkLBHaproxyStatsVisbility.key());
cmd.lbStatsUri = _configDao.getValue(Config.NetworkLBHaproxyStatsUri.key());

View File

@ -44,6 +44,7 @@ import com.vmware.nsx_policy.infra.tier_0s.LocaleServices;
import com.vmware.nsx_policy.infra.tier_1s.nat.NatRules;
import com.vmware.nsx_policy.model.ApiError;
import com.vmware.nsx_policy.model.DhcpRelayConfig;
import com.vmware.nsx_policy.model.EnforcementPoint;
import com.vmware.nsx_policy.model.EnforcementPointListResult;
import com.vmware.nsx_policy.model.Group;
import com.vmware.nsx_policy.model.GroupListResult;
@ -64,12 +65,13 @@ import com.vmware.nsx_policy.model.PathExpression;
import com.vmware.nsx_policy.model.PolicyGroupMembersListResult;
import com.vmware.nsx_policy.model.PolicyNatRule;
import com.vmware.nsx_policy.model.PolicyNatRuleListResult;
import com.vmware.nsx_policy.model.PolicyGroupMemberDetails;
import com.vmware.nsx_policy.model.Rule;
import com.vmware.nsx_policy.model.SecurityPolicy;
import com.vmware.nsx_policy.model.Segment;
import com.vmware.nsx_policy.model.SegmentSubnet;
import com.vmware.nsx_policy.model.ServiceListResult;
import com.vmware.nsx_policy.model.SiteListResult;
import com.vmware.nsx_policy.model.Site;
import com.vmware.nsx_policy.model.Tier1;
import com.vmware.vapi.bindings.Service;
import com.vmware.vapi.bindings.Structure;
@ -83,6 +85,7 @@ import com.vmware.vapi.internal.protocol.RestProtocol;
import com.vmware.vapi.internal.protocol.client.rest.authn.BasicAuthenticationAppender;
import com.vmware.vapi.protocol.HttpConfiguration;
import com.vmware.vapi.std.errors.Error;
import com.vmware.vapi.std.errors.NotFound;
import org.apache.cloudstack.resource.NsxLoadBalancerMember;
import org.apache.cloudstack.resource.NsxNetworkRule;
import org.apache.cloudstack.utils.NsxControllerUtils;
@ -96,9 +99,12 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toSet;
import static org.apache.cloudstack.utils.NsxControllerUtils.getServerPoolMemberName;
import static org.apache.cloudstack.utils.NsxControllerUtils.getServerPoolName;
import static org.apache.cloudstack.utils.NsxControllerUtils.getServiceName;
@ -282,16 +288,18 @@ public class NsxApiClient {
Tier1s tier1service = (Tier1s) nsxService.apply(Tier1s.class);
return tier1service.get(tier1GatewayId);
} catch (Exception e) {
logger.debug(String.format("NSX Tier-1 gateway with name: %s not found", tier1GatewayId));
logger.debug("NSX Tier-1 gateway with name: {} not found", tier1GatewayId);
}
return null;
}
private List<com.vmware.nsx_policy.model.LocaleServices> getTier0LocalServices(String tier0Gateway) {
private Optional<com.vmware.nsx_policy.model.LocaleServices> findTier0LocalServices(String tier0Gateway) {
try {
LocaleServices tier0LocaleServices = (LocaleServices) nsxService.apply(LocaleServices.class);
LocaleServicesListResult result = tier0LocaleServices.list(tier0Gateway, null, false, null, null, null, null);
return result.getResults();
LocaleServicesListResult result = tier0LocaleServices.list(tier0Gateway, null, false, null, 1L, null, null);
return Optional.ofNullable(result.getResults())
.filter(Predicate.not(List::isEmpty))
.map(l -> l.get(0));
} catch (Exception e) {
throw new CloudRuntimeException(String.format("Failed to fetch locale services for tier gateway %s due to %s", tier0Gateway, e.getMessage()));
}
@ -302,10 +310,13 @@ public class NsxApiClient {
*/
private void createTier1LocaleServices(String tier1Id, String edgeCluster, String tier0Gateway) {
try {
List<com.vmware.nsx_policy.model.LocaleServices> localeServices = getTier0LocalServices(tier0Gateway);
Optional<com.vmware.nsx_policy.model.LocaleServices> localeServices = findTier0LocalServices(tier0Gateway);
if (localeServices.isEmpty()) {
throw new CloudRuntimeException(String.format("Failed to find locale services for tier-0 gateway %s", tier0Gateway));
}
com.vmware.nsx_policy.infra.tier_1s.LocaleServices tier1LocalService = (com.vmware.nsx_policy.infra.tier_1s.LocaleServices) nsxService.apply(com.vmware.nsx_policy.infra.tier_1s.LocaleServices.class);
com.vmware.nsx_policy.model.LocaleServices localeService = new com.vmware.nsx_policy.model.LocaleServices.Builder()
.setEdgeClusterPath(localeServices.get(0).getEdgeClusterPath()).build();
.setEdgeClusterPath(localeServices.get().getEdgeClusterPath()).build();
tier1LocalService.patch(tier1Id, TIER_1_LOCALE_SERVICE_ID, localeService);
} catch (Error error) {
throw new CloudRuntimeException(String.format("Failed to instantiate tier-1 gateway %s in edge cluster %s", tier1Id, edgeCluster));
@ -327,7 +338,7 @@ public class NsxApiClient {
String tier0GatewayPath = TIER_0_GATEWAY_PATH_PREFIX + tier0Gateway;
Tier1 tier1 = getTier1Gateway(name);
if (tier1 != null) {
logger.info(String.format("VPC network with name %s exists in NSX zone", name));
logger.info("VPC network with name {} exists in NSX zone", name);
return;
}
@ -359,7 +370,7 @@ public class NsxApiClient {
com.vmware.nsx_policy.infra.tier_1s.LocaleServices localeService = (com.vmware.nsx_policy.infra.tier_1s.LocaleServices)
nsxService.apply(com.vmware.nsx_policy.infra.tier_1s.LocaleServices.class);
if (getTier1Gateway(tier1Id) == null) {
logger.warn(String.format("The Tier 1 Gateway %s does not exist, cannot be removed", tier1Id));
logger.warn("The Tier 1 Gateway {} does not exist, cannot be removed", tier1Id);
return;
}
removeTier1GatewayNatRules(tier1Id);
@ -370,13 +381,21 @@ public class NsxApiClient {
private void removeTier1GatewayNatRules(String tier1Id) {
NatRules natRulesService = (NatRules) nsxService.apply(NatRules.class);
PolicyNatRuleListResult result = natRulesService.list(tier1Id, NAT_ID, null, false, null, null, null, null);
List<PolicyNatRule> natRules = result.getResults();
List<PolicyNatRule> natRules = PagedFetcher.<PolicyNatRuleListResult, PolicyNatRule>withPageFetcher(
cursor -> natRulesService.list(tier1Id, NAT_ID, cursor, false, null, null, null, null)
).cursorExtractor(PolicyNatRuleListResult::getCursor)
.itemsExtractor(PolicyNatRuleListResult::getResults)
.itemsSetter((page, allItems) -> {
page.setResults(allItems);
page.setResultCount((long) allItems.size());
})
.fetchAll()
.getResults();
if (CollectionUtils.isEmpty(natRules)) {
logger.debug(String.format("Didn't find any NAT rule to remove on the Tier 1 Gateway %s", tier1Id));
logger.debug("Didn't find any NAT rule to remove on the Tier 1 Gateway {}", tier1Id);
} else {
for (PolicyNatRule natRule : natRules) {
logger.debug(String.format("Removing NAT rule %s from Tier 1 Gateway %s", natRule.getId(), tier1Id));
logger.debug("Removing NAT rule {} from Tier 1 Gateway {}", natRule.getId(), tier1Id);
natRulesService.delete(tier1Id, NAT_ID, natRule.getId());
}
}
@ -384,38 +403,45 @@ public class NsxApiClient {
}
public String getDefaultSiteId() {
SiteListResult sites = getSites();
if (CollectionUtils.isEmpty(sites.getResults())) {
Optional<Site> site = findFirstSite();
if (site.isEmpty()) {
String errorMsg = "No sites are found in the linked NSX infrastructure";
logger.error(errorMsg);
throw new CloudRuntimeException(errorMsg);
}
return sites.getResults().get(0).getId();
return site.get().getId();
}
protected SiteListResult getSites() {
protected Optional<Site> findFirstSite() {
try {
Sites sites = (Sites) nsxService.apply(Sites.class);
return sites.list(null, false, null, null, null, null);
List<Site> siteList = sites.list(null, false, null, 1L, null, null)
.getResults();
return Optional.ofNullable(siteList)
.filter(Predicate.not(List::isEmpty))
.map(l -> l.get(0));
} catch (Exception e) {
throw new CloudRuntimeException(String.format("Failed to fetch sites list due to %s", e.getMessage()));
}
}
public String getDefaultEnforcementPointPath(String siteId) {
EnforcementPointListResult epList = getEnforcementPoints(siteId);
if (CollectionUtils.isEmpty(epList.getResults())) {
Optional<EnforcementPoint> ep = findFirstEnforcementPoint(siteId);
if (ep.isEmpty()) {
String errorMsg = String.format("No enforcement points are found in the linked NSX infrastructure for site ID %s", siteId);
logger.error(errorMsg);
throw new CloudRuntimeException(errorMsg);
}
return epList.getResults().get(0).getPath();
return ep.get().getPath();
}
protected EnforcementPointListResult getEnforcementPoints(String siteId) {
protected Optional<EnforcementPoint> findFirstEnforcementPoint(String siteId) {
try {
EnforcementPoints enforcementPoints = (EnforcementPoints) nsxService.apply(EnforcementPoints.class);
return enforcementPoints.list(siteId, null, false, null, null, null, null);
EnforcementPointListResult result = enforcementPoints.list(siteId, null, false, null, 1L, null, null);
return Optional.ofNullable(result.getResults())
.filter(Predicate.not(List::isEmpty))
.map(l -> l.get(0));
} catch (Exception e) {
throw new CloudRuntimeException(String.format("Failed to fetch enforcement points due to %s", e.getMessage()));
}
@ -424,7 +450,15 @@ public class NsxApiClient {
public TransportZoneListResult getTransportZones() {
try {
com.vmware.nsx.TransportZones transportZones = (com.vmware.nsx.TransportZones) nsxService.apply(com.vmware.nsx.TransportZones.class);
return transportZones.list(null, null, true, null, null, null, null, null, TransportType.OVERLAY.name(), null);
return PagedFetcher.<TransportZoneListResult, TransportZone>withPageFetcher(
cursor -> transportZones.list(cursor, null, true, null, null, null, null, null, TransportType.OVERLAY.name(), null)
).cursorExtractor(TransportZoneListResult::getCursor)
.itemsExtractor(TransportZoneListResult::getResults)
.itemsSetter((page, allItems) -> {
page.setResults(allItems);
page.setResultCount((long) allItems.size());
})
.fetchAll();
} catch (Exception e) {
throw new CloudRuntimeException(String.format("Failed to fetch transport zones due to %s", e.getMessage()));
}
@ -465,7 +499,7 @@ public class NsxApiClient {
removeSegment(segmentName, zoneId);
DhcpRelayConfigs dhcpRelayConfig = (DhcpRelayConfigs) nsxService.apply(DhcpRelayConfigs.class);
String dhcpRelayConfigId = NsxControllerUtils.getNsxDhcpRelayConfigId(zoneId, domainId, accountId, vpcId, networkId);
logger.debug(String.format("Removing the DHCP relay config with ID %s", dhcpRelayConfigId));
logger.debug("Removing the DHCP relay config with ID {}", dhcpRelayConfigId);
dhcpRelayConfig.delete(dhcpRelayConfigId);
} catch (Error error) {
ApiError ae = error.getData()._convertTo(ApiError.class);
@ -476,7 +510,7 @@ public class NsxApiClient {
}
protected void removeSegment(String segmentName, long zoneId) {
logger.debug(String.format("Removing the segment with ID %s", segmentName));
logger.debug("Removing the segment with ID {}", segmentName);
Segments segmentService = (Segments) nsxService.apply(Segments.class);
String errMsg = String.format("The segment with ID %s is not found, skipping removal", segmentName);
try {
@ -498,7 +532,7 @@ public class NsxApiClient {
portCount = retrySegmentDeletion(segmentPortsService, segmentName, enforcementPointPath, zoneId);
}
if (portCount == 0L) {
logger.debug(String.format("Removing the segment with ID %s", segmentName));
logger.debug("Removing the segment with ID {}", segmentName);
removeGroupForSegment(segmentName);
segmentService.delete(segmentName);
} else {
@ -509,8 +543,18 @@ public class NsxApiClient {
}
private PolicyGroupMembersListResult getSegmentPortList(SegmentPorts segmentPortsService, String segmentName, String enforcementPointPath) {
return segmentPortsService.list(DEFAULT_DOMAIN, segmentName, null, enforcementPointPath,
false, null, 50L, false, null);
return PagedFetcher.
<PolicyGroupMembersListResult, PolicyGroupMemberDetails>withPageFetcher(
cursor -> segmentPortsService.list(DEFAULT_DOMAIN, segmentName, cursor, enforcementPointPath,
false, null, 50L, false, null)
)
.cursorExtractor(PolicyGroupMembersListResult::getCursor)
.itemsExtractor(PolicyGroupMembersListResult::getResults)
.itemsSetter((page, allItems) -> {
page.setResults(allItems);
page.setResultCount((long) allItems.size());
})
.fetchAll();
}
private Long retrySegmentDeletion(SegmentPorts segmentPortsService, String segmentName, String enforcementPointPath, long zoneId) {
@ -546,7 +590,7 @@ public class NsxApiClient {
.setEnabled(true)
.build();
logger.debug(String.format("Creating NSX static NAT rule %s for tier-1 gateway %s (VPC: %s)", ruleName, tier1GatewayName, vpcName));
logger.debug("Creating NSX static NAT rule {} for tier-1 gateway {} (VPC: {})", ruleName, tier1GatewayName, vpcName);
natService.patch(tier1GatewayName, NatId.USER.name(), ruleName, rule);
} catch (Error error) {
ApiError ae = error.getData()._convertTo(ApiError.class);
@ -582,8 +626,7 @@ public class NsxApiClient {
natService.delete(tier1GatewayName, NatId.USER.name(), ruleName);
}
} catch (Error error) {
String msg = String.format("Cannot find NAT rule with name %s: %s, skipping deletion", ruleName, error.getMessage());
logger.debug(msg);
logger.debug("Cannot find NAT rule with name {}: {}, skipping deletion", ruleName, error.getMessage());
}
if (service == Network.Service.PortForwarding) {
@ -595,7 +638,7 @@ public class NsxApiClient {
String vmIp, String publicPort, String service) {
try {
NatRules natService = (NatRules) nsxService.apply(NatRules.class);
logger.debug(String.format("Creating NSX Port-Forwarding NAT %s for network %s", ruleName, networkName));
logger.debug("Creating NSX Port-Forwarding NAT {} for network {}", ruleName, networkName);
PolicyNatRule rule = new PolicyNatRule.Builder()
.setId(ruleName)
.setDisplayName(ruleName)
@ -656,9 +699,20 @@ public class NsxApiClient {
public void createNsxLbServerPool(List<NsxLoadBalancerMember> memberList, String tier1GatewayName, String lbServerPoolName,
String algorithm, String privatePort, String protocol) {
try {
String activeMonitorPath = getLbActiveMonitorPath(lbServerPoolName, privatePort, protocol);
List<LBPoolMember> members = getLbPoolMembers(memberList, tier1GatewayName);
LbPools lbPools = (LbPools) nsxService.apply(LbPools.class);
Optional<LBPool> nsxLbServerPool = getNsxLbServerPool(lbPools, lbServerPoolName);
// Skip if pool exists and members unchanged
if (nsxLbServerPool.isPresent()) {
List<LBPoolMember> existingMembers = nsxLbServerPool
.map(LBPool::getMembers)
.orElseGet(List::of);
if (hasSamePoolMembers(existingMembers, members)) {
logger.debug("Skipping patch for LB pool {} on Tier-1 {}: members unchanged", lbServerPoolName, tier1GatewayName);
return;
}
}
String activeMonitorPath = getLbActiveMonitorPath(lbServerPoolName, privatePort, protocol);
LBPool lbPool = new LBPool.Builder()
.setId(lbServerPoolName)
.setDisplayName(lbServerPoolName)
@ -676,9 +730,52 @@ public class NsxApiClient {
}
}
private Optional<LBPool> getNsxLbServerPool(LbPools lbPools, String lbServerPoolName) {
try {
return Optional.ofNullable(lbPools.get(lbServerPoolName));
} catch (NotFound e) {
logger.warn("Server Pool not found: {}", lbServerPoolName);
return Optional.empty();
}
}
private boolean hasSamePoolMembers(List<LBPoolMember> existingMembers, List<LBPoolMember> membersUpdate) {
Set<String> existingMembersSet = existingMembers.stream()
.map(this::buildPoolMemberKey)
.collect(toSet());
Set<String> updateMembersSet = membersUpdate.stream()
.map(this::buildPoolMemberKey)
.collect(toSet());
return existingMembersSet.size() == updateMembersSet.size()
&& existingMembersSet.containsAll(updateMembersSet);
}
private String buildPoolMemberKey(LBPoolMember member) {
return member.getIpAddress() + ':' + member.getPort() + ':' + member.getDisplayName();
}
private String getLbActiveMonitorPath(String lbServerPoolName, String port, String protocol) {
LbMonitorProfiles lbActiveMonitor = (LbMonitorProfiles) nsxService.apply(LbMonitorProfiles.class);
String lbMonitorProfileId = getActiveMonitorProfileName(lbServerPoolName, port, protocol);
Optional<Structure> monitorProfile = getMonitorProfile(lbActiveMonitor, lbMonitorProfileId);
if (monitorProfile.isEmpty()) {
patchMonitoringProfile(port, protocol, lbMonitorProfileId, lbActiveMonitor);
monitorProfile = getMonitorProfile(lbActiveMonitor, lbMonitorProfileId);
}
return monitorProfile.map(structure -> structure._getDataValue().getField("path").toString()).orElse(null);
}
private Optional<Structure> getMonitorProfile(LbMonitorProfiles lbActiveMonitor, String lbMonitorProfileId) {
try {
return Optional.ofNullable(lbActiveMonitor.get(lbMonitorProfileId));
} catch (NotFound e) {
logger.warn("LB Monitor Profile not found: {}", lbMonitorProfileId);
return Optional.empty();
}
}
private void patchMonitoringProfile(String port, String protocol, String lbMonitorProfileId, LbMonitorProfiles lbActiveMonitor) {
if ("TCP".equals(protocol.toUpperCase(Locale.ROOT))) {
LBTcpMonitorProfile lbTcpMonitorProfile = new LBTcpMonitorProfile.Builder(TCP_MONITOR_PROFILE)
.setDisplayName(lbMonitorProfileId)
@ -691,14 +788,18 @@ public class NsxApiClient {
.build();
lbActiveMonitor.patch(lbMonitorProfileId, icmpMonitorProfile);
}
LBMonitorProfileListResult listResult = listLBActiveMonitors(lbActiveMonitor);
Optional<Structure> monitorProfile = listResult.getResults().stream().filter(profile -> profile._getDataValue().getField("id").toString().equals(lbMonitorProfileId)).findFirst();
return monitorProfile.map(structure -> structure._getDataValue().getField("path").toString()).orElse(null);
}
LBMonitorProfileListResult listLBActiveMonitors(LbMonitorProfiles lbActiveMonitor) {
return lbActiveMonitor.list(null, false, null, null, null, null);
return PagedFetcher.<LBMonitorProfileListResult, Structure>withPageFetcher(
cursor -> lbActiveMonitor.list(cursor, false, null, null, null, null)
).cursorExtractor(LBMonitorProfileListResult::getCursor)
.itemsExtractor(LBMonitorProfileListResult::getResults)
.itemsSetter((page, allItems) -> {
page.setResults(allItems);
page.setResultCount((long) allItems.size());
})
.fetchAll();
}
public void createNsxLoadBalancer(String tier1GatewayName) {
@ -735,7 +836,7 @@ public class NsxApiClient {
String lbVirtualServerName = getVirtualServerName(tier1GatewayName, lbId);
String lbServiceName = getLoadBalancerName(tier1GatewayName);
LbVirtualServers lbVirtualServers = (LbVirtualServers) nsxService.apply(LbVirtualServers.class);
if (Objects.nonNull(getLbVirtualServerService(lbVirtualServers, lbServiceName))) {
if (Objects.nonNull(getLbVirtualServerService(lbVirtualServers, lbVirtualServerName))) {
return;
}
LBVirtualServer lbVirtualServer = new LBVirtualServer.Builder()
@ -763,7 +864,7 @@ public class NsxApiClient {
return lbVirtualServer;
}
} catch (Exception e) {
logger.debug(String.format("Found an LB virtual server named: %s on NSX", lbVSName));
logger.debug("Found an LB virtual server named: {} on NSX", lbVSName);
return null;
}
return null;
@ -851,8 +952,15 @@ public class NsxApiClient {
private String getLbProfileForProtocol(String protocol) {
try {
LbAppProfiles lbAppProfiles = (LbAppProfiles) nsxService.apply(LbAppProfiles.class);
LBAppProfileListResult lbAppProfileListResults = lbAppProfiles.list(null, null,
null, null, null, null);
LBAppProfileListResult lbAppProfileListResults = PagedFetcher.<LBAppProfileListResult, Structure>withPageFetcher(
cursor -> lbAppProfiles.list(cursor, null, null, null, null, null)
).cursorExtractor(LBAppProfileListResult::getCursor)
.itemsExtractor(LBAppProfileListResult::getResults)
.itemsSetter((page, allItems) -> {
page.setResults(allItems);
page.setResultCount((long) allItems.size());
})
.fetchAll();
Optional<Structure> appProfile = lbAppProfileListResults.getResults().stream().filter(profile -> profile._getDataValue().getField("path").toString().contains(protocol.toLowerCase(Locale.ROOT))).findFirst();
return appProfile.map(structure -> structure._getDataValue().getField("path").toString()).orElse(null);
} catch (Error error) {
@ -868,7 +976,15 @@ public class NsxApiClient {
Services service = (Services) nsxService.apply(Services.class);
// Find default service if present
ServiceListResult serviceList = service.list(null, true, false, null, null, null, null);
ServiceListResult serviceList = PagedFetcher.<ServiceListResult, com.vmware.nsx_policy.model.Service>withPageFetcher(
cursor -> service.list(cursor, true, false, null, null, null, null)
).cursorExtractor(ServiceListResult::getCursor)
.itemsExtractor(ServiceListResult::getResults)
.itemsSetter((page, allItems) -> {
page.setResults(allItems);
page.setResultCount((long) allItems.size());
})
.fetchAll();
List<com.vmware.nsx_policy.model.Service> services = serviceList.getResults();
List<String> matchedDefaultSvc = services.parallelStream().filter(svc ->
@ -1095,9 +1211,17 @@ public class NsxApiClient {
private List<Group> listNsxGroups() {
try {
Groups groups = (Groups) nsxService.apply(Groups.class);
GroupListResult result = groups.list(DEFAULT_DOMAIN, null, false, null, null, null, null, null);
return result.getResults();
Groups groups = (Groups) nsxService.apply(Groups.class);
GroupListResult result = PagedFetcher.<GroupListResult, Group>withPageFetcher(
cursor -> groups.list(DEFAULT_DOMAIN, cursor, false, null, null, null, null, null)
).cursorExtractor(GroupListResult::getCursor)
.itemsExtractor(GroupListResult::getResults)
.itemsSetter((page, allItems) -> {
page.setResults(allItems);
page.setResultCount((long) allItems.size());
})
.fetchAll();
return result.getResults();
} catch (Error error) {
ApiError ae = error.getData()._convertTo(ApiError.class);
String msg = String.format("Failed to list NSX groups, due to: %s", ae.getErrorMessage());

View File

@ -0,0 +1,82 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.service;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Function;
class PagedFetcher<R, T> {
private final Function<String, R> fetchPage;
private Function<R, String> cursorExtractor;
private Function<R, List<T>> itemsExtractor;
private BiConsumer<R, List<T>> itemsSetter;
static <R, T> PagedFetcher<R, T> withPageFetcher(Function<String, R> pageFetcher) {
return new PagedFetcher<>(pageFetcher);
}
PagedFetcher<R, T> cursorExtractor(Function<R, String> cursorProvider) {
this.cursorExtractor = cursorProvider;
return this;
}
PagedFetcher<R, T> itemsExtractor(Function<R, List<T>> resultsProvider) {
this.itemsExtractor = resultsProvider;
return this;
}
PagedFetcher<R, T> itemsSetter(BiConsumer<R, List<T>> resultsSetter) {
this.itemsSetter = resultsSetter;
return this;
}
private PagedFetcher(Function<String, R> pageFetcher) {
this.fetchPage = pageFetcher;
}
R fetchAll() {
Objects.requireNonNull(cursorExtractor, "Cursor extractor must be set");
Objects.requireNonNull(itemsExtractor, "Items extractor must be set");
Objects.requireNonNull(itemsSetter, "Items setter must be set");
R firstPage = fetchPage.apply(null);
String cursor = cursorExtractor.apply(firstPage);
if (cursor == null || cursor.isEmpty()) {
return firstPage;
}
List<T> firstResults = itemsExtractor.apply(firstPage);
List<T> allItems = firstResults != null
? new ArrayList<>(firstResults)
: new ArrayList<>();
while (cursor != null && !cursor.isEmpty()) {
R nextPage = fetchPage.apply(cursor);
List<T> nextItems = itemsExtractor.apply(nextPage);
if (nextItems != null && !nextItems.isEmpty()) {
allItems.addAll(nextItems);
}
cursor = cursorExtractor.apply(nextPage);
}
itemsSetter.accept(firstPage, allItems);
return firstPage;
}
}

View File

@ -18,13 +18,32 @@ package org.apache.cloudstack.service;
import com.cloud.network.Network;
import com.cloud.network.SDNProviderNetworkRule;
import com.cloud.utils.exception.CloudRuntimeException;
import com.vmware.nsx.cluster.Status;
import com.vmware.nsx.model.ClusterStatus;
import com.vmware.nsx.model.ControllerClusterStatus;
import com.vmware.nsx_policy.infra.LbAppProfiles;
import com.vmware.nsx_policy.infra.LbMonitorProfiles;
import com.vmware.nsx_policy.infra.LbPools;
import com.vmware.nsx_policy.infra.LbServices;
import com.vmware.nsx_policy.infra.LbVirtualServers;
import com.vmware.nsx_policy.infra.domains.Groups;
import com.vmware.nsx_policy.model.ApiError;
import com.vmware.nsx_policy.model.Group;
import com.vmware.nsx_policy.model.LBAppProfileListResult;
import com.vmware.nsx_policy.model.LBIcmpMonitorProfile;
import com.vmware.nsx_policy.model.LBService;
import com.vmware.nsx_policy.model.LBTcpMonitorProfile;
import com.vmware.nsx_policy.model.LBPool;
import com.vmware.nsx_policy.model.LBPoolMember;
import com.vmware.nsx_policy.model.LBVirtualServer;
import com.vmware.nsx_policy.model.PathExpression;
import com.vmware.vapi.bindings.Service;
import com.vmware.vapi.bindings.Structure;
import com.vmware.vapi.std.errors.Error;
import com.vmware.vapi.std.errors.NotFound;
import org.apache.cloudstack.resource.NsxLoadBalancerMember;
import org.apache.cloudstack.utils.NsxControllerUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@ -36,8 +55,20 @@ import org.mockito.MockitoAnnotations;
import java.util.List;
import java.util.function.Function;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class NsxApiClientTest {
private static final String TIER_1_GATEWAY_NAME = "t1";
@Mock
private Function<Class<? extends Service>, Service> nsxService;
@Mock
@ -108,4 +139,284 @@ public class NsxApiClientTest {
Mockito.when(clusterStatus.getControlClusterStatus()).thenReturn(status);
Assert.assertTrue(client.isNsxControllerActive());
}
@Test
public void testCreateNsxLbServerPoolExistingMonitorProfileSkipsMonitorPatch() {
String lbServerPoolName = NsxControllerUtils.getServerPoolName(TIER_1_GATEWAY_NAME, 1L);
List<NsxLoadBalancerMember> memberList = List.of(new NsxLoadBalancerMember(1L, "10.0.0.1", 80));
LbPools lbPools = Mockito.mock(LbPools.class);
LbMonitorProfiles lbMonitorProfiles = mockLbMonitorProfiles();
Mockito.when(nsxService.apply(LbPools.class)).thenReturn(lbPools);
Mockito.when(lbPools.get(lbServerPoolName)).thenThrow(new NotFound(null, null));
client.createNsxLbServerPool(memberList, TIER_1_GATEWAY_NAME, lbServerPoolName, "roundrobin", "80", "TCP");
verify(lbMonitorProfiles, never()).patch(anyString(), any(LBTcpMonitorProfile.class));
verify(lbPools).patch(eq(lbServerPoolName), any(LBPool.class));
}
@Test
public void testCreateNsxLbServerPoolMissingMonitorTCPProfilePerformsPatch() {
String lbServerPoolName = NsxControllerUtils.getServerPoolName(TIER_1_GATEWAY_NAME, 1L);
List<NsxLoadBalancerMember> memberList = List.of(new NsxLoadBalancerMember(1L, "10.0.0.1", 80));
LbPools lbPools = Mockito.mock(LbPools.class);
LbMonitorProfiles lbMonitorProfiles = Mockito.mock(LbMonitorProfiles.class);
Structure monitorStructure = Mockito.mock(Structure.class, Mockito.RETURNS_DEEP_STUBS);
Mockito.when(nsxService.apply(LbPools.class)).thenReturn(lbPools);
Mockito.when(nsxService.apply(LbMonitorProfiles.class)).thenReturn(lbMonitorProfiles);
Mockito.when(lbMonitorProfiles.get(anyString())).thenThrow(new NotFound(null, null)).thenReturn(monitorStructure);
Mockito.when(monitorStructure._getDataValue().getField("path").toString()).thenReturn("/infra/lb-monitor-profiles/test");
Mockito.when(lbPools.get(lbServerPoolName)).thenThrow(new NotFound(null, null));
client.createNsxLbServerPool(memberList, TIER_1_GATEWAY_NAME, lbServerPoolName, "roundrobin", "80", "TCP");
verify(lbMonitorProfiles).patch(anyString(), any(LBTcpMonitorProfile.class));
verify(lbPools).patch(eq(lbServerPoolName), any(LBPool.class));
}
@Test
public void testCreateNsxLbServerPoolMissingMonitorUDPProfilePerformsPatch() {
String lbServerPoolName = NsxControllerUtils.getServerPoolName(TIER_1_GATEWAY_NAME, 1L);
List<NsxLoadBalancerMember> memberList = List.of(new NsxLoadBalancerMember(1L, "10.0.0.1", 80));
LbPools lbPools = Mockito.mock(LbPools.class);
LbMonitorProfiles lbMonitorProfiles = Mockito.mock(LbMonitorProfiles.class);
Structure monitorStructure = Mockito.mock(Structure.class, Mockito.RETURNS_DEEP_STUBS);
Mockito.when(nsxService.apply(LbPools.class)).thenReturn(lbPools);
Mockito.when(nsxService.apply(LbMonitorProfiles.class)).thenReturn(lbMonitorProfiles);
Mockito.when(lbMonitorProfiles.get(anyString())).thenThrow(new NotFound(null, null)).thenReturn(monitorStructure);
Mockito.when(monitorStructure._getDataValue().getField("path").toString()).thenReturn("/infra/lb-monitor-profiles/test");
Mockito.when(lbPools.get(lbServerPoolName)).thenThrow(new NotFound(null, null));
client.createNsxLbServerPool(memberList, TIER_1_GATEWAY_NAME, lbServerPoolName, "roundrobin", "80", "UDP");
verify(lbMonitorProfiles).patch(anyString(), any(LBIcmpMonitorProfile.class));
verify(lbPools).patch(eq(lbServerPoolName), any(LBPool.class));
}
@Test
public void testCreateNsxLbServerPoolPoolExistsWithSameMembersSkipsPatch() {
long lbId = 1L;
String lbServerPoolName = NsxControllerUtils.getServerPoolName(TIER_1_GATEWAY_NAME, lbId);
List<NsxLoadBalancerMember> memberList = List.of(
new NsxLoadBalancerMember(1L, "10.0.0.1", 80),
new NsxLoadBalancerMember(2L, "10.0.0.2", 80)
);
List<LBPoolMember> sameMembers = List.of(
createPoolMember(2L, "10.0.0.2", 80),
createPoolMember(1L, "10.0.0.1", 80)
);
LbPools lbPools = Mockito.mock(LbPools.class);
LBPool existingPool = Mockito.mock(LBPool.class);
Mockito.when(nsxService.apply(LbPools.class)).thenReturn(lbPools);
Mockito.when(lbPools.get(lbServerPoolName)).thenReturn(existingPool);
Mockito.when(existingPool.getMembers()).thenReturn(sameMembers);
client.createNsxLbServerPool(memberList, TIER_1_GATEWAY_NAME, lbServerPoolName, "roundrobin", "80", "TCP");
verify(nsxService, never()).apply(LbMonitorProfiles.class);
verify(lbPools, never()).patch(anyString(), any(LBPool.class));
}
@Test
public void testCreateNsxLbServerPoolPoolExistsWithoutMembersAndEmptyUpdateSkipsPatch() {
String lbServerPoolName = NsxControllerUtils.getServerPoolName(TIER_1_GATEWAY_NAME, 1L);
LbPools lbPools = Mockito.mock(LbPools.class);
LBPool existingPool = Mockito.mock(LBPool.class);
Mockito.when(nsxService.apply(LbPools.class)).thenReturn(lbPools);
Mockito.when(lbPools.get(lbServerPoolName)).thenReturn(existingPool);
Mockito.when(existingPool.getMembers()).thenReturn(null);
client.createNsxLbServerPool(List.of(), TIER_1_GATEWAY_NAME, lbServerPoolName, "roundrobin", "80", "TCP");
verify(nsxService, never()).apply(LbMonitorProfiles.class);
verify(lbPools, never()).patch(anyString(), any(LBPool.class));
}
@Test
public void testCreateNsxLbServerPoolPoolExistsWithDuplicateMembersSkipsPatch() {
long lbId = 1L;
String lbServerPoolName = NsxControllerUtils.getServerPoolName(TIER_1_GATEWAY_NAME, lbId);
List<NsxLoadBalancerMember> memberList = List.of(
new NsxLoadBalancerMember(1L, "10.0.0.1", 80),
new NsxLoadBalancerMember(2L, "10.0.0.2", 80)
);
LbPools lbPools = Mockito.mock(LbPools.class);
LBPool existingPool = Mockito.mock(LBPool.class);
Mockito.when(nsxService.apply(LbPools.class)).thenReturn(lbPools);
Mockito.when(lbPools.get(lbServerPoolName)).thenReturn(existingPool);
Mockito.when(existingPool.getMembers()).thenReturn(List.of(
createPoolMember(1L, "10.0.0.1", 80),
createPoolMember(1L, "10.0.0.1", 80),
createPoolMember(2L, "10.0.0.2", 80)
));
client.createNsxLbServerPool(memberList, TIER_1_GATEWAY_NAME, lbServerPoolName, "roundrobin", "80", "TCP");
verify(nsxService, never()).apply(LbMonitorProfiles.class);
verify(lbPools, never()).patch(anyString(), any(LBPool.class));
}
@Test
public void testCreateNsxLbServerPoolPoolExistsWithDifferentMembersPerformsPatch() {
long lbId = 1L;
String lbServerPoolName = NsxControllerUtils.getServerPoolName(TIER_1_GATEWAY_NAME, lbId);
List<NsxLoadBalancerMember> memberList = List.of(
new NsxLoadBalancerMember(1L, "10.0.0.1", 80),
new NsxLoadBalancerMember(2L, "10.0.0.2", 80)
);
LbPools lbPools = Mockito.mock(LbPools.class);
LBPool existingPool = Mockito.mock(LBPool.class);
mockLbMonitorProfiles();
Mockito.when(nsxService.apply(LbPools.class)).thenReturn(lbPools);
Mockito.when(lbPools.get(lbServerPoolName)).thenReturn(existingPool);
Mockito.when(existingPool.getMembers()).thenReturn(List.of(
createPoolMember(1L, "10.0.0.10", 80)
));
client.createNsxLbServerPool(memberList, TIER_1_GATEWAY_NAME, lbServerPoolName, "roundrobin", "80", "TCP");
verify(lbPools).patch(eq(lbServerPoolName), any(LBPool.class));
}
@Test
public void testCreateNsxLbServerPoolPoolDoesNotExistPerformsPatch() {
String lbServerPoolName = NsxControllerUtils.getServerPoolName(TIER_1_GATEWAY_NAME, 1L);
List<NsxLoadBalancerMember> memberList = List.of(new NsxLoadBalancerMember(1L, "10.0.0.1", 80));
LbPools lbPools = Mockito.mock(LbPools.class);
mockLbMonitorProfiles();
Mockito.when(nsxService.apply(LbPools.class)).thenReturn(lbPools);
Mockito.when(lbPools.get(lbServerPoolName)).thenThrow(new NotFound(null, null));
client.createNsxLbServerPool(memberList, TIER_1_GATEWAY_NAME, lbServerPoolName, "roundrobin", "80", "TCP");
verify(lbPools).patch(eq(lbServerPoolName), any(LBPool.class));
}
@Test
public void testCreateAndAddNsxLbVirtualServerVirtualServerAlreadyExistsSkipsPatch() {
long lbId = 1L;
String lbVirtualServerName = NsxControllerUtils.getVirtualServerName(TIER_1_GATEWAY_NAME, lbId);
String lbServiceName = NsxControllerUtils.getLoadBalancerName(TIER_1_GATEWAY_NAME);
List<NsxLoadBalancerMember> memberList = List.of(new NsxLoadBalancerMember(1L, "10.0.0.1", 80));
LbPools lbPools = Mockito.mock(LbPools.class);
LbServices lbServices = Mockito.mock(LbServices.class);
LbVirtualServers lbVirtualServers = Mockito.mock(LbVirtualServers.class);
LBVirtualServer existingVs = Mockito.mock(LBVirtualServer.class);
mockLbMonitorProfiles();
Mockito.when(nsxService.apply(LbPools.class)).thenReturn(lbPools);
Mockito.when(nsxService.apply(LbServices.class)).thenReturn(lbServices);
Mockito.when(nsxService.apply(LbVirtualServers.class)).thenReturn(lbVirtualServers);
Mockito.when(lbPools.get(anyString())).thenThrow(new NotFound(null, null));
Mockito.when(lbServices.get(anyString())).thenReturn(null);
Mockito.when(lbVirtualServers.get(lbVirtualServerName)).thenReturn(existingVs);
client.createAndAddNsxLbVirtualServer(TIER_1_GATEWAY_NAME, lbId, "192.168.1.1", "443",
memberList, "roundrobin", "TCP", "80");
verify(lbVirtualServers).get(lbVirtualServerName);
verify(lbVirtualServers, never()).get(lbServiceName);
verify(lbVirtualServers, never()).patch(anyString(), any(LBVirtualServer.class));
}
@Test
public void testCreateAndAddNsxLbVirtualServerVirtualServerNotFoundPerformsPatch() {
long lbId = 1L;
String lbServerPoolName = NsxControllerUtils.getServerPoolName(TIER_1_GATEWAY_NAME, lbId);
String lbVirtualServerName = NsxControllerUtils.getVirtualServerName(TIER_1_GATEWAY_NAME, lbId);
String lbServiceName = NsxControllerUtils.getLoadBalancerName(TIER_1_GATEWAY_NAME);
List<NsxLoadBalancerMember> memberList = List.of(new NsxLoadBalancerMember(1L, "10.0.0.1", 80));
LbPools lbPools = Mockito.mock(LbPools.class);
LBPool lbPool = Mockito.mock(LBPool.class);
LbServices lbServices = Mockito.mock(LbServices.class);
LBService lbService = Mockito.mock(LBService.class);
LbVirtualServers lbVirtualServers = Mockito.mock(LbVirtualServers.class);
mockLbMonitorProfiles();
mockLbAppProfiles();
Mockito.when(nsxService.apply(LbPools.class)).thenReturn(lbPools);
Mockito.when(nsxService.apply(LbServices.class)).thenReturn(lbServices);
Mockito.when(nsxService.apply(LbVirtualServers.class)).thenReturn(lbVirtualServers);
Mockito.when(lbPools.get(lbServerPoolName)).thenThrow(new NotFound(null, null)).thenReturn(lbPool);
Mockito.when(lbPool.getPath()).thenReturn("/infra/lb-pools/" + lbServerPoolName);
Mockito.when(lbServices.get(lbServiceName)).thenReturn(lbService);
Mockito.when(lbService.getPath()).thenReturn("/infra/lb-services/" + lbServiceName);
Mockito.when(lbVirtualServers.get(lbVirtualServerName)).thenThrow(new NotFound(null, null));
client.createAndAddNsxLbVirtualServer(TIER_1_GATEWAY_NAME, lbId, "192.168.1.1", "443",
memberList, "roundrobin", "TCP", "80");
verify(lbVirtualServers).get(lbVirtualServerName);
verify(lbVirtualServers, never()).get(lbServiceName);
verify(lbVirtualServers).patch(eq(lbVirtualServerName), any(LBVirtualServer.class));
}
@Test
public void testCreateNsxLbServerPoolThrowsExceptionOnPatchError() {
String lbServerPoolName = NsxControllerUtils.getServerPoolName(TIER_1_GATEWAY_NAME, 1L);
List<NsxLoadBalancerMember> memberList = List.of(new NsxLoadBalancerMember(1L, "10.0.0.1", 80));
LbPools lbPools = Mockito.mock(LbPools.class);
Structure errorData = Mockito.mock(Structure.class);
ApiError apiError = new ApiError();
apiError.setErrorData(errorData);
mockLbMonitorProfiles();
Mockito.when(nsxService.apply(LbPools.class)).thenReturn(lbPools);
Mockito.when(lbPools.get(lbServerPoolName)).thenThrow(new NotFound(null, null));
when(errorData._convertTo(ApiError.class)).thenReturn(apiError);
doThrow(new Error(List.of(), errorData)).when(lbPools).patch(eq(lbServerPoolName), any(LBPool.class));
CloudRuntimeException thrownException = assertThrows(CloudRuntimeException.class, () -> {
client.createNsxLbServerPool(memberList, TIER_1_GATEWAY_NAME, lbServerPoolName, "roundrobin", "80", "TCP");
});
assertTrue(thrownException.getMessage().startsWith("Failed to create NSX LB server pool, due to"));
}
private LbMonitorProfiles mockLbMonitorProfiles() {
LbMonitorProfiles lbMonitorProfiles = Mockito.mock(LbMonitorProfiles.class);
Structure monitorStructure = Mockito.mock(Structure.class, Mockito.RETURNS_DEEP_STUBS);
Mockito.when(nsxService.apply(LbMonitorProfiles.class)).thenReturn(lbMonitorProfiles);
Mockito.when(lbMonitorProfiles.get(anyString())).thenReturn(monitorStructure);
Mockito.when(monitorStructure._getDataValue().getField("path").toString()).thenReturn("/infra/lb-monitor-profiles/test");
return lbMonitorProfiles;
}
private void mockLbAppProfiles() {
LbAppProfiles lbAppProfiles = Mockito.mock(LbAppProfiles.class);
LBAppProfileListResult appProfileListResult = Mockito.mock(LBAppProfileListResult.class);
Structure appProfile = Mockito.mock(Structure.class, Mockito.RETURNS_DEEP_STUBS);
Mockito.when(nsxService.apply(LbAppProfiles.class)).thenReturn(lbAppProfiles);
Mockito.when(lbAppProfiles.list(null, null, null, null, null, null)).thenReturn(appProfileListResult);
Mockito.when(appProfileListResult.getResults()).thenReturn(List.of(appProfile));
Mockito.when(appProfile._getDataValue().getField("path").toString()).thenReturn("/infra/lb-app-profiles/default-tcp-profile");
}
private LBPoolMember createPoolMember(long vmId, String ipAddress, int port) {
return new LBPoolMember.Builder()
.setDisplayName(NsxControllerUtils.getServerPoolMemberName(TIER_1_GATEWAY_NAME, vmId))
.setIpAddress(ipAddress)
.setPort(String.valueOf(port))
.build();
}
}

View File

@ -0,0 +1,156 @@
// 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.service;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
public class PagedFetcherTest {
private static class Page {
private String cursor;
private List<String> items;
Page(String cursor, List<String> items) {
this.cursor = cursor;
this.items = items;
}
String getCursor() {
return cursor;
}
List<String> getItems() {
return items;
}
void setItems(List<String> items) {
this.items = items;
}
}
@Test
public void testFetchAllWhenThereIsNoPagination() {
// given
Page firstPage = new Page(null, new ArrayList<>(List.of("a", "b")));
AtomicBoolean itemsSetterCalled = new AtomicBoolean(false);
PagedFetcher<Page, String> fetcher = PagedFetcher.<Page, String>withPageFetcher(
cursor -> {
assertNull(cursor);
return firstPage;
})
.cursorExtractor(Page::getCursor)
.itemsExtractor(Page::getItems)
.itemsSetter((page, items) -> itemsSetterCalled.set(true));
// when
Page result = fetcher.fetchAll();
// then
assertSame(firstPage, result);
assertEquals(List.of("a", "b"), result.getItems());
assertFalse("itemsSetter must not be called when there is no next page", itemsSetterCalled.get());
}
@Test
public void testFetchAllWhenThereIsNoPaginationAndEmptyCursor() {
// given
Page firstPage = new Page("", new ArrayList<>(List.of("x")));
AtomicBoolean itemsSetterCalled = new AtomicBoolean(false);
PagedFetcher<Page, String> fetcher = PagedFetcher
.<Page, String>withPageFetcher(cursor -> {
assertNull(cursor);
return firstPage;
})
.cursorExtractor(Page::getCursor)
.itemsExtractor(Page::getItems)
.itemsSetter((page, items) -> itemsSetterCalled.set(true));
// when
Page result = fetcher.fetchAll();
// then
assertSame(firstPage, result);
assertEquals(List.of("x"), result.getItems());
assertFalse("itemsSetter must not be called when there is no next page", itemsSetterCalled.get());
}
@Test
public void testFetchAllWhenMultiPages() {
// given
Page page1 = new Page("c1", new ArrayList<>(List.of("p1a", "p1b")));
Page page2 = new Page("c2", new ArrayList<>(List.of("p2a")));
Page page3 = new Page(null, new ArrayList<>(List.of("p3a", "p3b")));
Map<String, Page> pagesByCursor = new HashMap<>();
pagesByCursor.put(null, page1);
pagesByCursor.put("c1", page2);
pagesByCursor.put("c2", page3);
PagedFetcher<Page, String> fetcher = PagedFetcher
.<Page, String>withPageFetcher(pagesByCursor::get)
.cursorExtractor(Page::getCursor)
.itemsExtractor(Page::getItems)
.itemsSetter((page, items) -> {
assertSame(page1, page);
page.setItems(items);
});
// when
Page result = fetcher.fetchAll();
// then
assertSame("Result must be the first page object", page1, result);
assertEquals(List.of("p1a", "p1b", "p2a", "p3a", "p3b"), result.getItems());
}
@Test
public void testFetchAllFirstPageItemsNullSecondWithItems() {
// given
Page page1 = new Page("next", null);
Page page2 = new Page(null, new ArrayList<>(List.of("x", "y")));
Map<String, Page> pages = new HashMap<>();
pages.put(null, page1);
pages.put("next", page2);
PagedFetcher<Page, String> fetcher = PagedFetcher
.<Page, String>withPageFetcher(pages::get)
.cursorExtractor(Page::getCursor)
.itemsExtractor(Page::getItems)
.itemsSetter(Page::setItems);
// when
Page result = fetcher.fetchAll();
// then
assertSame(page1, result);
assertEquals(List.of("x", "y"), result.getItems());
}
}

View File

@ -153,6 +153,8 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
// CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.TRUE.toString());
mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_TEMPLATE_FROM_SNAPSHOT.toString(),
Boolean.toString(system_snapshot));
return mapCapabilities;
}
@ -720,6 +722,13 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
}
}
private static boolean canCopySnapshotToVolumeCond(DataObject srcData, DataObject dstData) {
return srcData.getType() == DataObjectType.SNAPSHOT && dstData.getType() == DataObjectType.VOLUME
&& srcData.getDataStore().getRole() == DataStoreRole.Primary
&& dstData.getDataStore().getRole() == DataStoreRole.Primary
&& srcData.getDataStore().getId() == dstData.getDataStore().getId();
}
private static boolean canCopySnapshotCond(DataObject srcData, DataObject dstData) {
return srcData.getType() == DataObjectType.SNAPSHOT && dstData.getType() == DataObjectType.SNAPSHOT
&& (dstData.getDataStore().getRole() == DataStoreRole.Image
@ -747,7 +756,10 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
{
logger.debug("LinstorPrimaryDataStoreDriverImpl.canCopy: " + srcData.getType() + " -> " + dstData.getType());
if (canCopySnapshotCond(srcData, dstData)) {
if (canCopySnapshotToVolumeCond(srcData, dstData)) {
StoragePoolVO storagePool = _storagePoolDao.findById(srcData.getDataStore().getId());
return storagePool.getStorageProviderName().equals(LinstorUtil.PROVIDER_NAME);
} else if (canCopySnapshotCond(srcData, dstData)) {
SnapshotInfo sinfo = (SnapshotInfo) srcData;
VolumeInfo volume = sinfo.getBaseVolume();
StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId());
@ -766,6 +778,18 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
return false;
}
private CopyCommandResult copySnapshotToVolume(SnapshotInfo snapshotInfo, VolumeInfo volumeInfo) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(snapshotInfo.getDataStore().getId());
String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
createResourceFromSnapshot(snapshotInfo.getId(), rscName, storagePoolVO);
VolumeObjectTO volumeTO = (VolumeObjectTO) volumeInfo.getTO();
volumeTO.setPath(volumeInfo.getUuid());
volumeTO.setSize(volumeInfo.getSize());
return new CopyCommandResult(null, new CopyCmdAnswer(volumeTO));
}
@Override
public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCallback<CopyCommandResult> callback)
{
@ -773,7 +797,9 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
+ srcData.getType() + " -> " + dstData.getType());
final CopyCommandResult res;
if (canCopySnapshotCond(srcData, dstData)) {
if (canCopySnapshotToVolumeCond(srcData, dstData)) {
res = copySnapshotToVolume((SnapshotInfo) srcData, (VolumeInfo) dstData);
} else if (canCopySnapshotCond(srcData, dstData)) {
String errMsg = null;
Answer answer = copySnapshot(srcData, dstData);
if (answer != null && !answer.getResult()) {

View File

@ -25,13 +25,23 @@ import com.linbit.linstor.api.model.ResourceGroup;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.storage.DataStoreRole;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
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.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.mock;
@ -42,6 +52,9 @@ public class LinstorPrimaryDataStoreDriverImplTest {
private DevelopersApi api;
@Mock
private PrimaryDataStoreDao _storagePoolDao;
@InjectMocks
private LinstorPrimaryDataStoreDriverImpl linstorPrimaryDataStoreDriver;
@ -85,4 +98,100 @@ public class LinstorPrimaryDataStoreDriverImplTest {
layers = LinstorUtil.getEncryptedLayerList(api, "EncryptedGrp");
Assert.assertEquals(Arrays.asList(LayerType.DRBD, LayerType.LUKS, LayerType.STORAGE), layers);
}
@Test
public void testGetCapabilitiesIncludesCreateTemplateFromSnapshotMatchesSystemSnapshot() {
Map<String, String> caps = linstorPrimaryDataStoreDriver.getCapabilities();
Assert.assertEquals(
"CAN_CREATE_TEMPLATE_FROM_SNAPSHOT should match STORAGE_SYSTEM_SNAPSHOT",
caps.get(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString()),
caps.get(DataStoreCapabilities.CAN_CREATE_TEMPLATE_FROM_SNAPSHOT.toString()));
}
@Test
public void testCanCopySnapshotToVolumeOnSamePrimary() {
DataStore primaryStore = mock(DataStore.class);
when(primaryStore.getRole()).thenReturn(DataStoreRole.Primary);
when(primaryStore.getId()).thenReturn(1L);
SnapshotInfo snapshot = mock(SnapshotInfo.class);
when(snapshot.getType()).thenReturn(DataObjectType.SNAPSHOT);
when(snapshot.getDataStore()).thenReturn(primaryStore);
VolumeInfo volume = mock(VolumeInfo.class);
when(volume.getType()).thenReturn(DataObjectType.VOLUME);
when(volume.getDataStore()).thenReturn(primaryStore);
StoragePoolVO pool = mock(StoragePoolVO.class);
when(pool.getStorageProviderName()).thenReturn(LinstorUtil.PROVIDER_NAME);
when(_storagePoolDao.findById(1L)).thenReturn(pool);
Assert.assertTrue("canCopy should return true for SNAPSHOT -> VOLUME on same Linstor primary",
linstorPrimaryDataStoreDriver.canCopy(snapshot, volume));
}
@Test
public void testCanCopySnapshotToVolumeRejectsNonLinstor() {
DataStore primaryStore = mock(DataStore.class);
when(primaryStore.getRole()).thenReturn(DataStoreRole.Primary);
when(primaryStore.getId()).thenReturn(1L);
SnapshotInfo snapshot = mock(SnapshotInfo.class);
when(snapshot.getType()).thenReturn(DataObjectType.SNAPSHOT);
when(snapshot.getDataStore()).thenReturn(primaryStore);
VolumeInfo volume = mock(VolumeInfo.class);
when(volume.getType()).thenReturn(DataObjectType.VOLUME);
when(volume.getDataStore()).thenReturn(primaryStore);
StoragePoolVO pool = mock(StoragePoolVO.class);
when(pool.getStorageProviderName()).thenReturn("SomeOtherProvider");
when(_storagePoolDao.findById(1L)).thenReturn(pool);
Assert.assertFalse("canCopy should return false for non-Linstor storage",
linstorPrimaryDataStoreDriver.canCopy(snapshot, volume));
}
@Test
public void testCanCopySnapshotToVolumeRejectsCrossPrimary() {
DataStore srcStore = mock(DataStore.class);
when(srcStore.getRole()).thenReturn(DataStoreRole.Primary);
when(srcStore.getId()).thenReturn(1L);
DataStore destStore = mock(DataStore.class);
when(destStore.getRole()).thenReturn(DataStoreRole.Primary);
when(destStore.getId()).thenReturn(2L);
SnapshotInfo snapshot = mock(SnapshotInfo.class);
when(snapshot.getType()).thenReturn(DataObjectType.SNAPSHOT);
when(snapshot.getDataStore()).thenReturn(srcStore);
VolumeInfo volume = mock(VolumeInfo.class);
when(volume.getType()).thenReturn(DataObjectType.VOLUME);
when(volume.getDataStore()).thenReturn(destStore);
Assert.assertFalse("canCopy should return false for SNAPSHOT -> VOLUME across different primary stores",
linstorPrimaryDataStoreDriver.canCopy(snapshot, volume));
}
@Test
public void testCanCopySnapshotToVolumeRejectsImageDest() {
DataStore primaryStore = mock(DataStore.class);
when(primaryStore.getRole()).thenReturn(DataStoreRole.Primary);
DataStore imageStore = mock(DataStore.class);
when(imageStore.getRole()).thenReturn(DataStoreRole.Image);
SnapshotInfo snapshot = mock(SnapshotInfo.class);
when(snapshot.getType()).thenReturn(DataObjectType.SNAPSHOT);
when(snapshot.getDataStore()).thenReturn(primaryStore);
VolumeInfo volume = mock(VolumeInfo.class);
when(volume.getType()).thenReturn(DataObjectType.VOLUME);
when(volume.getDataStore()).thenReturn(imageStore);
Assert.assertFalse("canCopy should return false when destination is Image store",
linstorPrimaryDataStoreDriver.canCopy(snapshot, volume));
}
}

View File

@ -28,7 +28,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.regex.Matcher;
import javax.inject.Inject;
@ -526,7 +525,7 @@ public class ParamProcessWorker implements DispatchWorker {
continue;
}
String entityUuid = ((Identity) objVO).getUuid();
CallContext.current().putApiResourceUuid(annotation.name(), UUID.fromString(entityUuid));
CallContext.current().putApiResourceUuid(annotation.name(), entityUuid);
}
validateNaturalNumber(internalId, annotation.name());
return internalId;
@ -551,7 +550,7 @@ public class ParamProcessWorker implements DispatchWorker {
}
// Return on first non-null Id for the uuid entity
if (internalId != null){
CallContext.current().putApiResourceUuid(annotation.name(), UUID.fromString(uuid));
CallContext.current().putApiResourceUuid(annotation.name(), uuid);
CallContext.current().putContextParameter(entity, uuid);
break;
}

View File

@ -1162,21 +1162,19 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
long reservedIpAddressesAmount = ipDedicatedAccountId == null ? 1L : 0L;
try (CheckedReservation publicIpAddressReservation = new CheckedReservation(account, Resource.ResourceType.public_ip, reservedIpAddressesAmount, reservationDao, _resourceLimitMgr)) {
List<AccountVlanMapVO> maps = _accountVlanMapDao.listAccountVlanMapsByVlan(ipVO.getVlanId());
ipVO.setAllocatedTime(new Date());
ipVO.setAllocatedToAccountId(account.getAccountId());
ipVO.setAllocatedInDomainId(account.getDomainId());
ipVO.setState(State.Reserved);
if (displayIp != null) {
ipVO.setDisplay(displayIp);
}
ipVO = _ipAddressDao.persist(ipVO);
if (reservedIpAddressesAmount > 0) {
_resourceLimitMgr.incrementResourceCount(account.getId(), Resource.ResourceType.public_ip);
}
return ipVO;
List<AccountVlanMapVO> maps = _accountVlanMapDao.listAccountVlanMapsByVlan(ipVO.getVlanId());
ipVO.setAllocatedTime(new Date());
ipVO.setAllocatedToAccountId(account.getAccountId());
ipVO.setAllocatedInDomainId(account.getDomainId());
ipVO.setState(State.Reserved);
if (displayIp != null) {
ipVO.setDisplay(displayIp);
}
ipVO = _ipAddressDao.persist(ipVO);
if (reservedIpAddressesAmount > 0) {
_resourceLimitMgr.incrementResourceCount(account.getId(), Resource.ResourceType.public_ip);
}
return ipVO;
} catch (ResourceAllocationException ex) {
logger.warn("Failed to allocate resource of type " + ex.getResourceType() + " for account " + account);
throw new AccountLimitException("Maximum number of public IP addresses for account: " + account.getAccountName() + " has been exceeded.");

View File

@ -396,7 +396,8 @@ public class CommandSetupHelper {
}
final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lbs, routerPublicIp, _routerControlHelper.getRouterIpInNetwork(guestNetworkId, router.getId()),
router.getPrivateIpAddress(), _itMgr.toNicTO(nicProfile, router.getHypervisorType()), router.getVpcId(), maxconn, offering.isKeepAliveEnabled());
router.getPrivateIpAddress(), _itMgr.toNicTO(nicProfile, router.getHypervisorType()), router.getVpcId(), maxconn, offering.isKeepAliveEnabled(),
NetworkOrchestrationService.NETWORK_LB_HAPROXY_IDLE_TIMEOUT.value());
cmd.lbStatsVisibility = _configDao.getValue(Config.NetworkLBHaproxyStatsVisbility.key());
cmd.lbStatsUri = _configDao.getValue(Config.NetworkLBHaproxyStatsUri.key());

View File

@ -1690,7 +1690,7 @@ Configurable, StateListener<VirtualMachine.State, VirtualMachine.Event, VirtualM
} else {
loadBalancingData.append("maxconn=").append(offering.getConcurrentConnections());
}
loadBalancingData.append(",idletimeout=").append(NetworkOrchestrationService.NETWORK_LB_HAPROXY_IDLE_TIMEOUT.value());
loadBalancingData.append(",sourcePortStart=").append(firewallRuleVO.getSourcePortStart())
.append(",sourcePortEnd=").append(firewallRuleVO.getSourcePortEnd());
if (firewallRuleVO instanceof LoadBalancerVO) {

View File

@ -1670,25 +1670,25 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
vpc.setUseRouterIpResolver(Boolean.TRUE.equals(useVrIpResolver));
try (CheckedReservation vpcReservation = new CheckedReservation(owner, ResourceType.vpc, null, null, 1L, reservationDao, _resourceLimitMgr)) {
if (vpc.getCidr() == null && cidrSize != null) {
// Allocate a CIDR for VPC
Ipv4GuestSubnetNetworkMap subnet = routedIpv4Manager.getOrCreateIpv4SubnetForVpc(vpc, cidrSize);
if (subnet != null) {
vpc.setCidr(subnet.getSubnet());
} else {
throw new CloudRuntimeException("Failed to allocate a CIDR with requested size for VPC.");
if (vpc.getCidr() == null && cidrSize != null) {
// Allocate a CIDR for VPC
Ipv4GuestSubnetNetworkMap subnet = routedIpv4Manager.getOrCreateIpv4SubnetForVpc(vpc, cidrSize);
if (subnet != null) {
vpc.setCidr(subnet.getSubnet());
} else {
throw new CloudRuntimeException("Failed to allocate a CIDR with requested size for VPC.");
}
}
}
Vpc newVpc = createVpc(displayVpc, vpc);
// assign Ipv4 subnet to Routed VPC
if (routedIpv4Manager.isRoutedVpc(vpc)) {
routedIpv4Manager.assignIpv4SubnetToVpc(newVpc);
}
if (CollectionUtils.isNotEmpty(bgpPeerIds)) {
routedIpv4Manager.persistBgpPeersForVpc(newVpc.getId(), bgpPeerIds);
}
return newVpc;
Vpc newVpc = createVpc(displayVpc, vpc);
// assign Ipv4 subnet to Routed VPC
if (routedIpv4Manager.isRoutedVpc(vpc)) {
routedIpv4Manager.assignIpv4SubnetToVpc(newVpc);
}
if (CollectionUtils.isNotEmpty(bgpPeerIds)) {
routedIpv4Manager.persistBgpPeersForVpc(newVpc.getId(), bgpPeerIds);
}
return newVpc;
}
}

View File

@ -277,40 +277,39 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C
}
try (CheckedReservation projectReservation = new CheckedReservation(owner, ResourceType.project, null, null, 1L, reservationDao, _resourceLimitMgr)) {
final Account ownerFinal = owner;
User finalUser = user;
Project project = Transaction.execute(new TransactionCallback<Project>() {
@Override
public Project doInTransaction(TransactionStatus status) {
final Account ownerFinal = owner;
User finalUser = user;
Project project = Transaction.execute(new TransactionCallback<Project>() {
@Override
public Project doInTransaction(TransactionStatus status) {
//Create an account associated with the project
StringBuilder acctNm = new StringBuilder("PrjAcct-");
acctNm.append(name).append("-").append(ownerFinal.getDomainId());
//Create an account associated with the project
StringBuilder acctNm = new StringBuilder("PrjAcct-");
acctNm.append(name).append("-").append(ownerFinal.getDomainId());
Account projectAccount = _accountMgr.createAccount(acctNm.toString(), Account.Type.PROJECT, null, domainId, null, null, UUID.randomUUID().toString());
Account projectAccount = _accountMgr.createAccount(acctNm.toString(), Account.Type.PROJECT, null, domainId, null, null, UUID.randomUUID().toString());
Project project = _projectDao.persist(new ProjectVO(name, displayText, ownerFinal.getDomainId(), projectAccount.getId()));
Project project = _projectDao.persist(new ProjectVO(name, displayText, ownerFinal.getDomainId(), projectAccount.getId()));
//assign owner to the project
assignAccountToProject(project, ownerFinal.getId(), ProjectAccount.Role.Admin,
Optional.ofNullable(finalUser).map(User::getId).orElse(null), null);
//assign owner to the project
assignAccountToProject(project, ownerFinal.getId(), ProjectAccount.Role.Admin,
Optional.ofNullable(finalUser).map(User::getId).orElse(null), null);
if (project != null) {
CallContext.current().setEventDetails("Project ID: " + project.getUuid());
CallContext.current().putContextParameter(Project.class, project.getUuid());
}
//Increment resource count
_resourceLimitMgr.incrementResourceCount(ownerFinal.getId(), ResourceType.project);
//Increment resource count
_resourceLimitMgr.incrementResourceCount(ownerFinal.getId(), ResourceType.project);
return project;
}
});
return project;
}
});
messageBus.publish(_name, ProjectManager.MESSAGE_CREATE_TUNGSTEN_PROJECT_EVENT, PublishScope.LOCAL, project);
messageBus.publish(_name, ProjectManager.MESSAGE_CREATE_TUNGSTEN_PROJECT_EVENT, PublishScope.LOCAL, project);
return project;
return project;
}
}
@ -604,16 +603,16 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C
boolean shouldIncrementResourceCount = projectRole != null && Role.Admin == projectRole;
try (CheckedReservation cr = new CheckedReservation(userAccount, ResourceType.project, shouldIncrementResourceCount ? 1L : 0L, reservationDao, _resourceLimitMgr)) {
if (assignUserToProject(project, user.getId(), user.getAccountId(), projectRole,
Optional.ofNullable(role).map(ProjectRole::getId).orElse(null)) != null) {
if (shouldIncrementResourceCount) {
_resourceLimitMgr.incrementResourceCount(userAccount.getId(), ResourceType.project);
if (assignUserToProject(project, user.getId(), user.getAccountId(), projectRole,
Optional.ofNullable(role).map(ProjectRole::getId).orElse(null)) != null) {
if (shouldIncrementResourceCount) {
_resourceLimitMgr.incrementResourceCount(userAccount.getId(), ResourceType.project);
}
return true;
} else {
logger.warn("Failed to add user to project: {}", project);
return false;
}
return true;
} else {
logger.warn("Failed to add user to project: {}", project);
return false;
}
}
}
}
@ -671,13 +670,13 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C
boolean shouldIncrementResourceCount = Role.Admin == newAccRole;
try (CheckedReservation checkedReservation = new CheckedReservation(account, ResourceType.project, shouldIncrementResourceCount ? 1L : 0L, reservationDao, _resourceLimitMgr)) {
futureOwner.setAccountRole(newAccRole);
_projectAccountDao.update(futureOwner.getId(), futureOwner);
if (shouldIncrementResourceCount) {
_resourceLimitMgr.incrementResourceCount(accountId, ResourceType.project);
} else {
_resourceLimitMgr.decrementResourceCount(accountId, ResourceType.project);
}
futureOwner.setAccountRole(newAccRole);
_projectAccountDao.update(futureOwner.getId(), futureOwner);
if (shouldIncrementResourceCount) {
_resourceLimitMgr.incrementResourceCount(accountId, ResourceType.project);
} else {
_resourceLimitMgr.decrementResourceCount(accountId, ResourceType.project);
}
}
}
@ -721,16 +720,16 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C
}
try (CheckedReservation checkedReservation = new CheckedReservation(futureOwnerAccount, ResourceType.project, null, null, 1L, reservationDao, _resourceLimitMgr)) {
//unset the role for the old owner
ProjectAccountVO currentOwner = _projectAccountDao.findByProjectIdAccountId(projectId, currentOwnerAccount.getId());
currentOwner.setAccountRole(Role.Regular);
_projectAccountDao.update(currentOwner.getId(), currentOwner);
_resourceLimitMgr.decrementResourceCount(currentOwnerAccount.getId(), ResourceType.project);
//unset the role for the old owner
ProjectAccountVO currentOwner = _projectAccountDao.findByProjectIdAccountId(projectId, currentOwnerAccount.getId());
currentOwner.setAccountRole(Role.Regular);
_projectAccountDao.update(currentOwner.getId(), currentOwner);
_resourceLimitMgr.decrementResourceCount(currentOwnerAccount.getId(), ResourceType.project);
//set new owner
futureOwner.setAccountRole(Role.Admin);
_projectAccountDao.update(futureOwner.getId(), futureOwner);
_resourceLimitMgr.incrementResourceCount(futureOwnerAccount.getId(), ResourceType.project);
//set new owner
futureOwner.setAccountRole(Role.Admin);
_projectAccountDao.update(futureOwner.getId(), futureOwner);
_resourceLimitMgr.incrementResourceCount(futureOwnerAccount.getId(), ResourceType.project);
}
} else {
logger.trace("Future owner {}is already the owner of the project {}", newOwnerName, project);
@ -877,16 +876,16 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C
boolean shouldIncrementResourceCount = projectRoleType != null && Role.Admin == projectRoleType;
try (CheckedReservation cr = new CheckedReservation(account, ResourceType.project, shouldIncrementResourceCount ? 1L : 0L, reservationDao, _resourceLimitMgr)) {
if (assignAccountToProject(project, account.getId(), projectRoleType, null,
Optional.ofNullable(projectRole).map(ProjectRole::getId).orElse(null)) != null) {
if (shouldIncrementResourceCount) {
_resourceLimitMgr.incrementResourceCount(account.getId(), ResourceType.project);
if (assignAccountToProject(project, account.getId(), projectRoleType, null,
Optional.ofNullable(projectRole).map(ProjectRole::getId).orElse(null)) != null) {
if (shouldIncrementResourceCount) {
_resourceLimitMgr.incrementResourceCount(account.getId(), ResourceType.project);
}
return true;
} else {
logger.warn("Failed to add account {} to project {}", accountName, project);
return false;
}
return true;
} else {
logger.warn("Failed to add account {} to project {}", accountName, project);
return false;
}
}
}
}

View File

@ -1172,7 +1172,7 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
@Override
public boolean deleteHost(final long hostId, final boolean isForced, final boolean isForceDeleteStorage) {
try {
final Boolean result = propagateResourceEvent(hostId, ResourceState.Event.DeleteHost);
final Boolean result = propagateResourceEvent(hostId, ResourceState.Event.DeleteHost, isForced, isForceDeleteStorage);
if (result != null) {
return result;
}
@ -3904,13 +3904,18 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
}
@Override
public boolean executeUserRequest(final long hostId, final ResourceState.Event event) {
public boolean executeUserRequest(final long hostId, final ResourceState.Event event) throws AgentUnavailableException {
return executeUserRequest(hostId, event, false, false);
}
@Override
public boolean executeUserRequest(final long hostId, final ResourceState.Event event, final boolean isForced, final boolean isForceDeleteStorage) throws AgentUnavailableException {
if (event == ResourceState.Event.AdminAskMaintenance) {
return doMaintain(hostId);
} else if (event == ResourceState.Event.AdminCancelMaintenance) {
return doCancelMaintenance(hostId);
} else if (event == ResourceState.Event.DeleteHost) {
return doDeleteHost(hostId, false, false);
return doDeleteHost(hostId, isForced, isForceDeleteStorage);
} else if (event == ResourceState.Event.Unmanaged) {
return doUmanageHost(hostId);
} else if (event == ResourceState.Event.UpdatePassword) {
@ -4030,6 +4035,10 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
}
public Boolean propagateResourceEvent(final long agentId, final ResourceState.Event event) throws AgentUnavailableException {
return propagateResourceEvent(agentId, event, false, false);
}
public Boolean propagateResourceEvent(final long agentId, final ResourceState.Event event, final boolean isForced, final boolean isForceDeleteStorage) throws AgentUnavailableException {
final String msPeer = getPeerName(agentId);
if (msPeer == null) {
return null;
@ -4037,7 +4046,7 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
logger.debug("Propagating resource request event:" + event.toString() + " to agent:" + agentId);
final Command[] cmds = new Command[1];
cmds[0] = new PropagateResourceEventCommand(agentId, event);
cmds[0] = new PropagateResourceEventCommand(agentId, event, isForced, isForceDeleteStorage);
final String AnsStr = _clusterMgr.execute(msPeer, agentId, _gson.toJson(cmds), true);
if (AnsStr == null) {
@ -4050,6 +4059,13 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
logger.debug("Result for agent change is " + answers[0].getResult());
}
if (!answers[0].getResult()) {
final String details = answers[0].getDetails();
if (details != null && !details.isEmpty()) {
throw new CloudRuntimeException(String.format("Failed to propagate resource event %s for host %d on peer %s: %s", event, agentId, msPeer, details));
}
}
return answers[0].getResult();
}

View File

@ -361,7 +361,7 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto
boolean success = true;
Long currentSize = answer.getVirtualSize() != 0 ? answer.getVirtualSize() : answer.getPhysicalSize();
Long lastSize = volume.getSize() != null ? volume.getSize() : 0L;
if (!checkAndUpdateSecondaryStorageResourceLimit(volume.getAccountId(), volume.getSize(), currentSize)) {
if (!checkAndUpdateSecondaryStorageResourceLimit(volume.getAccountId(), lastSize, currentSize)) {
volumeDataStore.setDownloadState(VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR);
volumeDataStore.setState(State.Failed);
volumeDataStore.setErrorString("Storage Limit Reached");

Some files were not shown because too many files have changed in this diff Show More