From 8f2735ab46288dc7307a4aad50ffc77dd35bfa6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Wed, 11 Jun 2025 14:39:26 -0300 Subject: [PATCH 1/9] Accept case insensitive values in boolean settings (#10663) --- .../ConfigurationManagerImpl.java | 74 +++++++----- .../ConfigurationManagerImplTest.java | 112 ++++++++++++++++++ 2 files changed, 155 insertions(+), 31 deletions(-) diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index c8ab6b4b069..0f41a7e6b7a 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -703,6 +703,10 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Override @DB public String updateConfiguration(final long userId, final String name, final String category, String value, final String scope, final Long resourceId) { + if (Boolean.class == getConfigurationTypeWrapperClass(name)) { + value = value.toLowerCase(); + } + final String validationMsg = validateConfigurationValue(name, value, scope); if (validationMsg != null) { logger.error("Invalid value [{}] for configuration [{}] due to [{}].", value, name, validationMsg); @@ -1241,18 +1245,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati return "Invalid scope id provided for the parameter " + name; } } - Class type; - Config configuration = Config.getConfig(name); - if (configuration == null) { - logger.warn("Did not find configuration " + name + " in Config.java. Perhaps moved to ConfigDepot"); - ConfigKey configKey = _configDepot.get(name); - if(configKey == null) { - logger.warn("Did not find configuration " + name + " in ConfigDepot too."); - return null; - } - type = configKey.type(); - } else { - type = configuration.getType(); + Class type = getConfigurationTypeWrapperClass(name); + if (type == null) { + return null; } validateSpecificConfigurationValues(name, value, type); @@ -1262,7 +1257,28 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati return String.format("Value [%s] is not a valid [%s].", value, type); } - return validateValueRange(name, value, type, configuration); + return validateValueRange(name, value, type, Config.getConfig(name)); + } + + /** + * Returns the configuration type's wrapper class. + * @param name name of the configuration. + * @return if the configuration exists, returns its type's wrapper class; if not, returns null. + */ + protected Class getConfigurationTypeWrapperClass(String name) { + Config configuration = Config.getConfig(name); + if (configuration != null) { + return configuration.getType(); + } + + logger.warn("Did not find configuration [{}] in Config.java. Perhaps moved to ConfigDepot.", name); + ConfigKey configKey = _configDepot.get(name); + if (configKey == null) { + logger.warn("Did not find configuration [{}] in ConfigDepot too.", name); + return null; + } + + return configKey.type(); } protected void validateConfigurationAllowedOnlyForDefaultAdmin(String configName, String value) { @@ -1299,7 +1315,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati * @@ -8176,11 +8192,17 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati }; } + + /** + * Returns a string representing the specified configuration's type. + * @param configName name of the configuration. + * @return if the configuration exists, returns its type; if not, returns {@link Configuration.ValueType#String}. + */ @Override public String getConfigurationType(final String configName) { final ConfigurationVO cfg = _configDao.findByName(configName); if (cfg == null) { - logger.warn("Configuration " + configName + " not found"); + logger.warn("Configuration [{}] not found", configName); return Configuration.ValueType.String.name(); } @@ -8188,24 +8210,14 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati return Configuration.ValueType.Range.name(); } - Class type = null; - final Config c = Config.getConfig(configName); - if (c == null) { - logger.warn("Configuration " + configName + " no found. Perhaps moved to ConfigDepot"); - final ConfigKey configKey = _configDepot.get(configName); - if (configKey == null) { - logger.warn("Couldn't find configuration " + configName + " in ConfigDepot too."); - return Configuration.ValueType.String.name(); - } - type = configKey.type(); - } else { - type = c.getType(); - } - - return getInputType(type, cfg); + Class type = getConfigurationTypeWrapperClass(configName); + return parseConfigurationTypeIntoString(type, cfg); } - private String getInputType(Class type, ConfigurationVO cfg) { + /** + * Parses a configuration type's wrapper class into its string representation. + */ + protected String parseConfigurationTypeIntoString(Class type, ConfigurationVO cfg) { if (type == null) { return Configuration.ValueType.String.name(); } diff --git a/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java b/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java index 1309842b706..227f66dd72f 100644 --- a/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java +++ b/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java @@ -16,6 +16,7 @@ // under the License. package com.cloud.configuration; +import com.cloud.alert.AlertManager; import com.cloud.capacity.dao.CapacityDao; import com.cloud.dc.DataCenterVO; import com.cloud.dc.VlanVO; @@ -52,6 +53,7 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; import com.cloud.vm.dao.VMInstanceDao; +import org.apache.cloudstack.acl.RoleService; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.UpdateDiskOfferingCmd; @@ -65,6 +67,7 @@ import org.apache.cloudstack.framework.config.impl.ConfigurationVO; import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO; import org.apache.cloudstack.vm.UnmanagedVMsManager; +import org.apache.cloudstack.config.Configuration; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -897,4 +900,113 @@ public class ConfigurationManagerImplTest { configurationManagerImplSpy.validateConfigurationAllowedOnlyForDefaultAdmin(AccountManagerImpl.listOfRoleTypesAllowedForOperationsOfSameRoleType.key(), invalidValue); } } + + + @Test + public void getConfigurationTypeWrapperClassTestReturnsConfigType() { + Config configuration = Config.AlertEmailAddresses; + + Assert.assertEquals(configuration.getType(), configurationManagerImplSpy.getConfigurationTypeWrapperClass(configuration.key())); + } + + @Test + public void getConfigurationTypeWrapperClassTestReturnsConfigKeyType() { + String configurationName = "configuration.name"; + + Mockito.when(configDepot.get(configurationName)).thenReturn(configKeyMock); + Mockito.when(configKeyMock.type()).thenReturn(Integer.class); + + Assert.assertEquals(Integer.class, configurationManagerImplSpy.getConfigurationTypeWrapperClass(configurationName)); + } + + @Test + public void getConfigurationTypeWrapperClassTestReturnsNullWhenConfigurationDoesNotExist() { + String configurationName = "configuration.name"; + + Mockito.when(configDepot.get(configurationName)).thenReturn(null); + Assert.assertNull(configurationManagerImplSpy.getConfigurationTypeWrapperClass(configurationName)); + } + + @Test + public void parseConfigurationTypeIntoStringTestReturnsStringWhenTypeIsNull() { + Assert.assertEquals(Configuration.ValueType.String.name(), configurationManagerImplSpy.parseConfigurationTypeIntoString(null, null)); + } + + @Test + public void parseConfigurationTypeIntoStringTestReturnsStringWhenTypeIsStringAndConfigurationKindIsNull() { + Mockito.when(configurationVOMock.getKind()).thenReturn(null); + Assert.assertEquals(Configuration.ValueType.String.name(), configurationManagerImplSpy.parseConfigurationTypeIntoString(String.class, configurationVOMock)); + } + + @Test + public void parseConfigurationTypeIntoStringTestReturnsKindWhenTypeIsStringAndKindIsNotNull() { + Mockito.when(configurationVOMock.getKind()).thenReturn(ConfigKey.Kind.CSV.name()); + Assert.assertEquals(ConfigKey.Kind.CSV.name(), configurationManagerImplSpy.parseConfigurationTypeIntoString(String.class, configurationVOMock)); + } + + @Test + public void parseConfigurationTypeIntoStringTestReturnsKindWhenTypeIsCharacterAndKindIsNotNull() { + Mockito.when(configurationVOMock.getKind()).thenReturn(ConfigKey.Kind.CSV.name()); + Assert.assertEquals(ConfigKey.Kind.CSV.name(), configurationManagerImplSpy.parseConfigurationTypeIntoString(Character.class, configurationVOMock)); + } + + @Test + public void parseConfigurationTypeIntoStringTestReturnsNumberWhenTypeIsInteger() { + Assert.assertEquals(Configuration.ValueType.Number.name(), configurationManagerImplSpy.parseConfigurationTypeIntoString(Integer.class, configurationVOMock)); + } + + @Test + public void parseConfigurationTypeIntoStringTestReturnsNumberWhenTypeIsLong() { + Assert.assertEquals(Configuration.ValueType.Number.name(), configurationManagerImplSpy.parseConfigurationTypeIntoString(Long.class, configurationVOMock)); + } + + @Test + public void parseConfigurationTypeIntoStringTestReturnsNumberWhenTypeIsShort() { + Assert.assertEquals(Configuration.ValueType.Number.name(), configurationManagerImplSpy.parseConfigurationTypeIntoString(Short.class, configurationVOMock)); + } + + @Test + public void parseConfigurationTypeIntoStringTestReturnsDecimalWhenTypeIsFloat() { + Assert.assertEquals(Configuration.ValueType.Decimal.name(), configurationManagerImplSpy.parseConfigurationTypeIntoString(Float.class, configurationVOMock)); + } + + @Test + public void parseConfigurationTypeIntoStringTestReturnsDecimalWhenTypeIsDouble() { + Assert.assertEquals(Configuration.ValueType.Decimal.name(), configurationManagerImplSpy.parseConfigurationTypeIntoString(Double.class, configurationVOMock)); + } + + @Test + public void parseConfigurationTypeIntoStringTestReturnsBooleanWhenTypeIsBoolean() { + Assert.assertEquals(Configuration.ValueType.Boolean.name(), configurationManagerImplSpy.parseConfigurationTypeIntoString(Boolean.class, configurationVOMock)); + } + + @Test + public void parseConfigurationTypeIntoStringTestReturnsStringWhenTypeDoesNotMatchAnyAvailableType() { + Assert.assertEquals(Configuration.ValueType.String.name(), configurationManagerImplSpy.parseConfigurationTypeIntoString(Object.class, configurationVOMock)); + } + + @Test + public void getConfigurationTypeTestReturnsStringWhenConfigurationDoesNotExist() { + Mockito.when(configDao.findByName(Mockito.anyString())).thenReturn(null); + Assert.assertEquals(Configuration.ValueType.String.name(), configurationManagerImplSpy.getConfigurationType(Mockito.anyString())); + } + + @Test + public void getConfigurationTypeTestReturnsRangeForConfigurationsThatAcceptIntervals() { + String configurationName = AlertManager.CPUCapacityThreshold.key(); + + Mockito.when(configDao.findByName(configurationName)).thenReturn(configurationVOMock); + Assert.assertEquals(Configuration.ValueType.Range.name(), configurationManagerImplSpy.getConfigurationType(configurationName)); + } + + @Test + public void getConfigurationTypeTestReturnsStringRepresentingConfigurationType() { + ConfigKey configuration = RoleService.EnableDynamicApiChecker; + + Mockito.when(configDao.findByName(configuration.key())).thenReturn(configurationVOMock); + Mockito.doReturn(configuration.type()).when(configurationManagerImplSpy).getConfigurationTypeWrapperClass(configuration.key()); + + configurationManagerImplSpy.getConfigurationType(configuration.key()); + Mockito.verify(configurationManagerImplSpy).parseConfigurationTypeIntoString(configuration.type(), configurationVOMock); + } } From cbd2b5a022476e8b85f8ce745d01db5370fb185f Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Thu, 19 Jun 2025 04:03:58 -0400 Subject: [PATCH 2/9] Add check for ldap truststore password (#11055) --- .../cloudstack/ldap/LdapContextFactory.java | 34 +++++++++++++++++-- .../cloudstack/ldap/LdapManagerImpl.java | 5 +++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapContextFactory.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapContextFactory.java index 0161adf9fda..ee48e8cc027 100644 --- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapContextFactory.java +++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapContextFactory.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.ldap; +import java.io.FileInputStream; import java.io.IOException; import java.util.Hashtable; @@ -24,6 +25,7 @@ import javax.naming.Context; import javax.naming.NamingException; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; +import java.security.KeyStore; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; @@ -72,8 +74,36 @@ public class LdapContextFactory { if (sslStatus) { s_logger.info("LDAP SSL enabled."); environment.put(Context.SECURITY_PROTOCOL, "ssl"); - System.setProperty("javax.net.ssl.trustStore", _ldapConfiguration.getTrustStore(domainId)); - System.setProperty("javax.net.ssl.trustStorePassword", _ldapConfiguration.getTrustStorePassword(domainId)); + String trustStore = _ldapConfiguration.getTrustStore(domainId); + String trustStorePassword = _ldapConfiguration.getTrustStorePassword(domainId); + + if (!validateTrustStore(trustStore, trustStorePassword)) { + throw new RuntimeException("Invalid truststore or truststore password"); + } + + System.setProperty("javax.net.ssl.trustStore", trustStore); + System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword); + } + } + + private boolean validateTrustStore(String trustStore, String trustStorePassword) { + if (trustStore == null) { + return true; + } + + if (trustStorePassword == null) { + return false; + } + + try { + KeyStore.getInstance("JKS").load( + new FileInputStream(trustStore), + trustStorePassword.toCharArray() + ); + return true; + } catch (Exception e) { + s_logger.warn("Failed to validate truststore: " + e.getMessage()); + return false; } } diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java index 6ed79a0c69f..352e439b50c 100644 --- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java +++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java @@ -186,6 +186,11 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag } catch (NamingException | IOException e) { LOGGER.debug("NamingException while doing an LDAP bind", e); throw new InvalidParameterValueException("Unable to bind to the given LDAP server"); + } catch (RuntimeException e) { + if (e.getMessage().contains("Invalid truststore")) { + throw new InvalidParameterValueException("Invalid truststore or truststore password"); + } + throw e; } finally { closeContext(context); } From 5790091b25de4a170e12f1558c12dcd4424a25c7 Mon Sep 17 00:00:00 2001 From: Nicolas Vazquez Date: Thu, 19 Jun 2025 06:34:35 -0300 Subject: [PATCH 3/9] [Vmware to KVM Migration] Improve the Force MS option text (#11035) --- ui/public/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 027bbbd123f..f3ab19bb88a 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1005,7 +1005,7 @@ "label.for": "for", "label.forbidden": "Forbidden", "label.forced": "Force", -"label.force.ms.to.import.vm.files": "Force MS to export OVF from VMware to temporary storage", +"label.force.ms.to.import.vm.files": "Enable to force OVF Download via Management Server. Disable to use KVM Host ovftool (if installed)", "label.force.stop": "Force stop", "label.force.reboot": "Force reboot", "label.forceencap": "Force UDP encapsulation of ESP packets", From 75147b781163958200287aa9979e60bf602b2f89 Mon Sep 17 00:00:00 2001 From: Nicolas Vazquez Date: Mon, 23 Jun 2025 07:49:51 -0300 Subject: [PATCH 4/9] [Vmware to KVM Migration] Display virt-v2v and ovftool versions for supported hosts for migration (#11019) * [Vmware to KVM Migration] Display virt-v2v and ovftool versions for supported hosts for migration * Fix UI display * Address review comments * Fix ovftool and version display - also display versions on host details view --- api/src/main/java/com/cloud/host/Host.java | 9 ++++-- .../cloud/agent/manager/AgentManagerImpl.java | 17 ++++++++++- .../resource/LibvirtComputingResource.java | 29 +++++++++++++++++-- .../wrapper/LibvirtReadyCommandWrapper.java | 8 +++++ ui/public/locales/en.json | 2 ++ ui/src/views/infra/HostInfo.vue | 16 ++++++++++ .../views/tools/ImportUnmanagedInstance.vue | 6 ++++ 7 files changed, 81 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/com/cloud/host/Host.java b/api/src/main/java/com/cloud/host/Host.java index 56b4ed75a31..a3b6ccadc01 100644 --- a/api/src/main/java/com/cloud/host/Host.java +++ b/api/src/main/java/com/cloud/host/Host.java @@ -53,9 +53,12 @@ public interface Host extends StateObject, Identity, Partition, HAResour return strs; } } - public static final String HOST_UEFI_ENABLE = "host.uefi.enable"; - public static final String HOST_VOLUME_ENCRYPTION = "host.volume.encryption"; - public static final String HOST_INSTANCE_CONVERSION = "host.instance.conversion"; + + String HOST_UEFI_ENABLE = "host.uefi.enable"; + String HOST_VOLUME_ENCRYPTION = "host.volume.encryption"; + String HOST_INSTANCE_CONVERSION = "host.instance.conversion"; + String HOST_OVFTOOL_VERSION = "host.ovftool.version"; + String HOST_VIRTV2V_VERSION = "host.virtv2v.version"; /** * @return name of the machine. diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java index 9137d1c1dff..aff528efede 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java @@ -54,6 +54,7 @@ import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.ThreadContext; @@ -703,11 +704,25 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl Map detailsMap = readyAnswer.getDetailsMap(); if (detailsMap != null) { String uefiEnabled = detailsMap.get(Host.HOST_UEFI_ENABLE); + String virtv2vVersion = detailsMap.get(Host.HOST_VIRTV2V_VERSION); + String ovftoolVersion = detailsMap.get(Host.HOST_OVFTOOL_VERSION); logger.debug("Got HOST_UEFI_ENABLE [{}] for host [{}]:", uefiEnabled, host); - if (uefiEnabled != null) { + if (ObjectUtils.anyNotNull(uefiEnabled, virtv2vVersion, ovftoolVersion)) { _hostDao.loadDetails(host); + boolean updateNeeded = false; if (!uefiEnabled.equals(host.getDetails().get(Host.HOST_UEFI_ENABLE))) { host.getDetails().put(Host.HOST_UEFI_ENABLE, uefiEnabled); + updateNeeded = true; + } + if (StringUtils.isNotBlank(virtv2vVersion) && !virtv2vVersion.equals(host.getDetails().get(Host.HOST_VIRTV2V_VERSION))) { + host.getDetails().put(Host.HOST_VIRTV2V_VERSION, virtv2vVersion); + updateNeeded = true; + } + if (StringUtils.isNotBlank(ovftoolVersion) && !ovftoolVersion.equals(host.getDetails().get(Host.HOST_OVFTOOL_VERSION))) { + host.getDetails().put(Host.HOST_OVFTOOL_VERSION, ovftoolVersion); + updateNeeded = true; + } + if (updateNeeded) { _hostDao.saveDetails(host); } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 13518de5cb3..7fb3839860f 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -17,6 +17,8 @@ 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_VIRTV2V_VERSION; import static com.cloud.host.Host.HOST_VOLUME_ENCRYPTION; import java.io.BufferedReader; @@ -3766,7 +3768,14 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv cmd.setIqn(getIqn()); cmd.getHostDetails().put(HOST_VOLUME_ENCRYPTION, String.valueOf(hostSupportsVolumeEncryption())); cmd.setHostTags(getHostTags()); - cmd.getHostDetails().put(HOST_INSTANCE_CONVERSION, String.valueOf(hostSupportsInstanceConversion())); + boolean instanceConversionSupported = hostSupportsInstanceConversion(); + cmd.getHostDetails().put(HOST_INSTANCE_CONVERSION, String.valueOf(instanceConversionSupported)); + if (instanceConversionSupported) { + cmd.getHostDetails().put(HOST_VIRTV2V_VERSION, getHostVirtV2vVersion()); + } + if (hostSupportsOvfExport()) { + cmd.getHostDetails().put(HOST_OVFTOOL_VERSION, getHostOvfToolVersion()); + } HealthCheckResult healthCheckResult = getHostHealthCheckResult(); if (healthCheckResult != HealthCheckResult.IGNORE) { cmd.setHostHealthCheckResult(healthCheckResult == HealthCheckResult.SUCCESS); @@ -5368,8 +5377,24 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return exitValue == 0; } + public String getHostVirtV2vVersion() { + if (!hostSupportsInstanceConversion()) { + return ""; + } + String cmd = String.format("%s | awk '{print $2}'", INSTANCE_CONVERSION_SUPPORTED_CHECK_CMD); + String version = Script.runSimpleBashScript(cmd); + return StringUtils.isNotBlank(version) ? version.split(",")[0] : ""; + } + + public String getHostOvfToolVersion() { + if (!hostSupportsOvfExport()) { + return ""; + } + return Script.runSimpleBashScript(OVF_EXPORT_TOOl_GET_VERSION_CMD); + } + public boolean ovfExportToolSupportsParallelThreads() { - String ovfExportToolVersion = Script.runSimpleBashScript(OVF_EXPORT_TOOl_GET_VERSION_CMD); + String ovfExportToolVersion = getHostOvfToolVersion(); if (StringUtils.isBlank(ovfExportToolVersion)) { return false; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java index 8f23e79e4a3..485254c6bb9 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java @@ -47,6 +47,14 @@ public final class LibvirtReadyCommandWrapper extends CommandWrapper + +
+ {{ $t('label.host.virtv2v.version') }} +
+ {{ host.details['host.virtv2v.version'] }} +
+
+
+ +
+ {{ $t('label.host.ovftool.version') }} +
+ {{ host.details['host.ovftool.version'] }} +
+
+
{{ $t('label.hosttags') }} diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue index 50a0d631808..95c14f141d2 100644 --- a/ui/src/views/tools/ImportUnmanagedInstance.vue +++ b/ui/src/views/tools/ImportUnmanagedInstance.vue @@ -944,6 +944,12 @@ export default { } else { host.name = host.name + ' (' + this.$t('label.not.supported') + ')' } + if (host.details['host.virtv2v.version']) { + host.name = host.name + ' (virt-v2v=' + host.details['host.virtv2v.version'] + ')' + } + if (host.details['host.ovftool.version']) { + host.name = host.name + ' (ovftool=' + host.details['host.ovftool.version'] + ')' + } }) }) }, From ba0204f8edd6140d893e92bf33522f9a50637d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Tue, 24 Jun 2025 09:23:57 -0300 Subject: [PATCH 5/9] Block volume shrink on Xen (#11004) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Jandre --- .../wrapper/xenbase/CitrixResizeVolumeCommandWrapper.java | 7 +++++-- .../main/java/com/cloud/storage/VolumeApiServiceImpl.java | 3 +++ ui/src/views/storage/ChangeOfferingForVolume.vue | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixResizeVolumeCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixResizeVolumeCommandWrapper.java index 2ddf1bd1851..abf46b2771c 100755 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixResizeVolumeCommandWrapper.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixResizeVolumeCommandWrapper.java @@ -49,9 +49,12 @@ public final class CitrixResizeVolumeCommandWrapper extends CommandWrapper= newSize) { - logger.info("No need to resize volume: " + volId +", current size " + toHumanReadableSize(command.getCurrentSize()) + " is same as new size " + toHumanReadableSize(newSize)); + if (command.getCurrentSize() == newSize) { + logger.info("No need to resize volume [{}], current size [{}] is same as new size [{}].", volId, toHumanReadableSize(command.getCurrentSize()), toHumanReadableSize(newSize)); return new ResizeVolumeAnswer(command, true, "success", newSize); + } else if (command.getCurrentSize() > newSize) { + logger.error("XenServer does not support volume shrink. Volume [{}] current size [{}] is smaller than new size [{}]", volId, toHumanReadableSize(command.getCurrentSize()), toHumanReadableSize(newSize)); + return new ResizeVolumeAnswer(command, false, "operation not supported"); } if (command.isManaged()) { resizeSr(conn, command); diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 6a2aa09c38a..84e4bbf258f 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -2429,6 +2429,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic throw new InvalidParameterValueException("Going from existing size of " + currentSize + " to size of " + newSize + " would shrink the volume." + "Need to sign off by supplying the shrinkok parameter with value of true."); } + if (ApiDBUtils.getHypervisorTypeFromFormat(volume.getDataCenterId(), volume.getFormat()) == HypervisorType.XenServer) { + throw new InvalidParameterValueException("Shrink volume is not supported for the XenServer hypervisor."); + } } } /* Check resource limit for this account */ diff --git a/ui/src/views/storage/ChangeOfferingForVolume.vue b/ui/src/views/storage/ChangeOfferingForVolume.vue index 5ab80390999..dedc2e8dc04 100644 --- a/ui/src/views/storage/ChangeOfferingForVolume.vue +++ b/ui/src/views/storage/ChangeOfferingForVolume.vue @@ -90,7 +90,7 @@ :checked="autoMigrate" @change="val => { autoMigrate = val }"/> - + Date: Wed, 2 Jul 2025 15:45:32 +0530 Subject: [PATCH 6/9] Do not rely on Memory engine even transiently in DB setup scripts (#11106) It is not safe for use with replication, and is straight up incompatible with highly-available active-active type MySQL distributions such as Galera Co-authored-by: Tristan Deloche --- setup/db/create-schema.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql index 3f14fccd010..10141185eb4 100755 --- a/setup/db/create-schema.sql +++ b/setup/db/create-schema.sql @@ -398,7 +398,7 @@ CREATE TABLE `cloud`.`op_lock` ( `waiters` int NOT NULL DEFAULT 0 COMMENT 'How many have the thread acquired this lock (reentrant)', PRIMARY KEY (`key`), INDEX `i_op_lock__mac_ip_thread`(`mac`, `ip`, `thread`) -) ENGINE=Memory DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `cloud`.`configuration` ( `category` varchar(255) NOT NULL DEFAULT 'Advanced', @@ -1793,7 +1793,7 @@ CREATE TABLE `cloud`.`op_nwgrp_work` ( INDEX `i_op_nwgrp_work__taken`(`taken`), INDEX `i_op_nwgrp_work__step`(`step`), INDEX `i_op_nwgrp_work__seq_no`(`seq_no`) -) ENGINE=MEMORY DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `cloud`.`op_vm_ruleset_log` ( `id` bigint unsigned UNIQUE NOT NULL AUTO_INCREMENT COMMENT 'id', From 3b54194aef18c44a37489614ce3391eceab55b2f Mon Sep 17 00:00:00 2001 From: Harikrishna Date: Thu, 3 Jul 2025 13:15:35 +0530 Subject: [PATCH 7/9] Correct quota type indexes (#11085) --- ui/src/utils/quota.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/utils/quota.js b/ui/src/utils/quota.js index 485c99131d2..b8adbb93518 100644 --- a/ui/src/utils/quota.js +++ b/ui/src/utils/quota.js @@ -107,7 +107,7 @@ export const QUOTA_TYPES = [ }, { id: 29, - type: 'VPC' + type: 'BUCKET' }, { id: 30, @@ -115,7 +115,7 @@ export const QUOTA_TYPES = [ }, { id: 31, - type: 'BACKUP_OBJECT' + type: 'VPC' } ] From c24e4eea855bd21e72ce7aa182fada2671dbe857 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 3 Jul 2025 13:20:36 +0530 Subject: [PATCH 8/9] server: fix orphan db transaction issue (#11095) Signed-off-by: Abhishek Kumar --- .../com/cloud/alert/AlertManagerImpl.java | 32 ++++++++++++----- .../com/cloud/storage/StorageManagerImpl.java | 36 +++++++++++-------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/server/src/main/java/com/cloud/alert/AlertManagerImpl.java b/server/src/main/java/com/cloud/alert/AlertManagerImpl.java index f35a0664a85..377b2134d78 100644 --- a/server/src/main/java/com/cloud/alert/AlertManagerImpl.java +++ b/server/src/main/java/com/cloud/alert/AlertManagerImpl.java @@ -89,6 +89,10 @@ import com.cloud.utils.Pair; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; + import org.jetbrains.annotations.Nullable; public class AlertManagerImpl extends ManagerBase implements AlertManager, Configurable { @@ -290,8 +294,13 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi Math.min(CapacityManager.CapacityCalculateWorkers.value(), hostIds.size()))); for (Long hostId : hostIds) { futures.put(hostId, executorService.submit(() -> { - final HostVO host = hostDao.findById(hostId); - _capacityMgr.updateCapacityForHost(host); + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + final HostVO host = hostDao.findById(hostId); + _capacityMgr.updateCapacityForHost(host); + } + }); return null; })); } @@ -316,13 +325,18 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi Math.min(CapacityManager.CapacityCalculateWorkers.value(), storagePoolIds.size()))); for (Long poolId: storagePoolIds) { futures.put(poolId, executorService.submit(() -> { - final StoragePoolVO pool = _storagePoolDao.findById(poolId); - long disk = _capacityMgr.getAllocatedPoolCapacity(pool, null); - if (pool.isShared()) { - _storageMgr.createCapacityEntry(pool, Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED, disk); - } else { - _storageMgr.createCapacityEntry(pool, Capacity.CAPACITY_TYPE_LOCAL_STORAGE, disk); - } + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + final StoragePoolVO pool = _storagePoolDao.findById(poolId); + long disk = _capacityMgr.getAllocatedPoolCapacity(pool, null); + if (pool.isShared()) { + _storageMgr.createCapacityEntry(pool, Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED, disk); + } else { + _storageMgr.createCapacityEntry(pool, Capacity.CAPACITY_TYPE_LOCAL_STORAGE, disk); + } + } + }); return null; })); } diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index f9199b94288..90113f66aaf 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -257,6 +257,7 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; @@ -1591,22 +1592,27 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C if (exceptionOccurred.get()) { return null; } - HostVO host = _hostDao.findById(hostId); - try { - connectHostToSharedPool(host, primaryStore.getId()); - poolHostIds.add(hostId); - } catch (Exception e) { - if (handleExceptionsPartially && e.getCause() instanceof StorageConflictException) { - exceptionOccurred.set(true); - throw e; + Transaction.execute(new TransactionCallbackWithExceptionNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) throws Exception { + HostVO host = _hostDao.findById(hostId); + try { + connectHostToSharedPool(host, primaryStore.getId()); + poolHostIds.add(hostId); + } catch (Exception e) { + if (handleExceptionsPartially && e.getCause() instanceof StorageConflictException) { + exceptionOccurred.set(true); + throw e; + } + logger.warn("Unable to establish a connection between {} and {}", host, primaryStore, e); + String reason = getStoragePoolMountFailureReason(e.getMessage()); + if (handleExceptionsPartially && reason != null) { + exceptionOccurred.set(true); + throw new CloudRuntimeException(reason); + } + } } - logger.warn("Unable to establish a connection between {} and {}", host, primaryStore, e); - String reason = getStoragePoolMountFailureReason(e.getMessage()); - if (handleExceptionsPartially && reason != null) { - exceptionOccurred.set(true); - throw new CloudRuntimeException(reason); - } - } + }); return null; })); } From 1a251c8b7899f5cee9d586047aaa469d9bec4ea6 Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Thu, 3 Jul 2025 12:55:21 +0200 Subject: [PATCH 9/9] merge forward fix --- .../org/apache/cloudstack/ldap/LdapContextFactory.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapContextFactory.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapContextFactory.java index 4199b0757d8..1c759c6a45e 100644 --- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapContextFactory.java +++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapContextFactory.java @@ -54,14 +54,14 @@ public class LdapContextFactory { return createInitialDirContext(bindPrincipal, bindPassword, providerUrl, true, domainId); } - private LdapContext createInitialDirContext(final String principal, final String password, final boolean isSystemContext, Long domainId) throws NamingException, IOException { + private LdapContext createInitialDirContext(final String principal, final String password, final boolean isSystemContext, Long domainId) throws NamingException { return createInitialDirContext(principal, password, null, isSystemContext, domainId); } private LdapContext createInitialDirContext(final String principal, final String password, final String providerUrl, final boolean isSystemContext, Long domainId) - throws NamingException, IOException { + throws NamingException { Hashtable environment = getEnvironment(principal, password, providerUrl, isSystemContext, domainId); - logger.debug("initializing ldap with provider url: " + environment.get(Context.PROVIDER_URL)); + logger.debug("initializing ldap with provider url: {}", environment.get(Context.PROVIDER_URL)); return new InitialLdapContext(environment, null); } @@ -103,7 +103,7 @@ public class LdapContextFactory { ); return true; } catch (Exception e) { - s_logger.warn("Failed to validate truststore: " + e.getMessage()); + logger.warn("Failed to validate truststore: {}", e.getMessage()); return false; } }