From cd545137250305d8251c62ce96add5d0f5ddc383 Mon Sep 17 00:00:00 2001 From: Brad House Date: Mon, 9 Nov 2026 19:07:30 +0000 Subject: [PATCH] server: return the scoped value from a scoped updateConfiguration updateConfiguration returned the global configuration row, and since the response value is now taken from that row, a zone, cluster, storage pool, account, domain, image store or management server scoped update answered with the global value, not the value just set. When a scope is given, report the value set in that scope. Signed-off-by: Brad House --- .../ConfigurationManagerImpl.java | 8 ++- .../ConfigurationManagerImplTest.java | 52 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 17dbced54233..07f0a2a41f1d 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -1152,7 +1152,13 @@ public Configuration updateConfiguration(final UpdateCfgCmd cmd) throws InvalidP encryptEventValueIfConfigIsEncrypted(config, value), scope != null ? scope : ConfigKey.Scope.Global.name()); - return _configDao.findByName(name); + ConfigurationVO updatedConfig = _configDao.findByName(name); + if (updatedConfig != null && scope != null) { + // The configuration table holds the global value. Report the value just set in the + // requested scope instead. This object is only used for the response; it is not persisted. + updatedConfig.setValue(updatedValue); + } + return updatedConfig; } else { throw new CloudRuntimeException("Unable to update configuration parameter " + name); } diff --git a/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java b/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java index 9a0b150780e4..e8557ce4e989 100644 --- a/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java +++ b/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java @@ -48,6 +48,7 @@ import com.cloud.storage.dao.VMTemplateZoneDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.user.Account; +import com.cloud.user.AccountManager; import com.cloud.user.AccountManagerImpl; import com.cloud.user.User; import com.cloud.utils.DomainHelper; @@ -61,6 +62,7 @@ import org.apache.cloudstack.acl.RoleService; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd; +import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.UpdateDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.zone.DeleteZoneCmd; @@ -1410,4 +1412,54 @@ public void testGetExternalNetworkProviderReturnsNullWhenNoExternalProviders() { mapWithEmptySet.put(Network.Service.Firewall, Collections.emptySet()); Assert.assertNull(ConfigurationManagerImpl.getExternalNetworkProvider(null, mapWithEmptySet)); } + + private Configuration updateConfigurationWithMocks(ConfigurationVO cfg, String value, Long clusterId) { + String name = cfg.getName(); + String category = cfg.getCategory(); + Mockito.when(configDao.findByName(name)).thenReturn(cfg); + + UpdateCfgCmd cmd = Mockito.mock(UpdateCfgCmd.class); + Mockito.when(cmd.getCfgName()).thenReturn(name); + Mockito.when(cmd.getValue()).thenReturn(value); + Mockito.when(cmd.getZoneId()).thenReturn(null); + Mockito.when(cmd.getClusterId()).thenReturn(clusterId); + Mockito.when(cmd.getStoragePoolId()).thenReturn(null); + Mockito.when(cmd.getImageStoreId()).thenReturn(null); + Mockito.when(cmd.getManagementServerId()).thenReturn(null); + Mockito.when(cmd.getAccountId()).thenReturn(null); + Mockito.when(cmd.getDomainId()).thenReturn(null); + + AccountManager accountManager = Mockito.mock(AccountManager.class); + ReflectionTestUtils.setField(configurationManagerImplSpy, "_accountMgr", accountManager); + Mockito.doNothing().when(configurationManagerImplSpy).validateIpAddressRelatedConfigValues(name, value); + Mockito.doNothing().when(configurationManagerImplSpy).validateConflictingConfigValue(name, value); + ConfigKey.Scope scope = clusterId == null ? null : ConfigKey.Scope.Cluster; + Mockito.doReturn(value).when(configurationManagerImplSpy).updateConfiguration(anyLong(), Mockito.eq(name), + Mockito.eq(category), Mockito.eq(value), Mockito.eq(scope), Mockito.eq(clusterId)); + + CallContext callContext = Mockito.mock(CallContext.class); + Mockito.when(callContext.getCallingUserId()).thenReturn(User.UID_ADMIN); + Mockito.when(callContext.getCallingAccount()).thenReturn(accountMock); + try (MockedStatic callContextMock = Mockito.mockStatic(CallContext.class)) { + callContextMock.when(CallContext::current).thenReturn(callContext); + return configurationManagerImplSpy.updateConfiguration(cmd); + } + } + + @Test + public void updateConfigurationWithScopeReturnsValueSetInScope() { + ConfigurationVO cfg = new ConfigurationVO("Advanced", "DEFAULT", "test", "drs.algorithm", "balanced", "description"); + Configuration result = updateConfigurationWithMocks(cfg, "condensed", 1L); + Assert.assertEquals("drs.algorithm", result.getName()); + Assert.assertEquals("condensed", result.getValue()); + } + + @Test + public void updateConfigurationWithoutScopeReturnsConfigurationRowUnchanged() { + ConfigurationVO cfg = Mockito.spy(new ConfigurationVO("Advanced", "DEFAULT", "test", "drs.algorithm", "condensed", "description")); + Configuration result = updateConfigurationWithMocks(cfg, "condensed", null); + Assert.assertSame(cfg, result); + Assert.assertEquals("condensed", result.getValue()); + Mockito.verify(cfg, Mockito.never()).setValue(Mockito.anyString()); + } }