This is an automated email from the ASF dual-hosted git repository.

nvazquez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new 98b8f752a34 Performance improvements of AgentPropertiesFileHandler 
(#13852)
98b8f752a34 is described below

commit 98b8f752a34b53efd1bd19a6522ebefebaceb9b5
Author: Suresh Kumar Anaparti <[email protected]>
AuthorDate: Tue Nov 10 04:16:19 2026 +0530

    Performance improvements of AgentPropertiesFileHandler (#13852)
    
    Co-authored-by: mprokopchuk <[email protected]>
---
 .../cloud/agent/dao/impl/PropertiesStorage.java    |  3 +
 .../properties/AgentPropertiesFileHandler.java     | 75 ++++++++++++++++++++--
 .../properties/AgentPropertiesFileHandlerTest.java | 37 +++++++++++
 3 files changed, 109 insertions(+), 6 deletions(-)

diff --git 
a/agent/src/main/java/com/cloud/agent/dao/impl/PropertiesStorage.java 
b/agent/src/main/java/com/cloud/agent/dao/impl/PropertiesStorage.java
index 17e0ceeeede..10d481efdca 100644
--- a/agent/src/main/java/com/cloud/agent/dao/impl/PropertiesStorage.java
+++ b/agent/src/main/java/com/cloud/agent/dao/impl/PropertiesStorage.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.util.Map;
 import java.util.Properties;
 
+import com.cloud.agent.properties.AgentPropertiesFileHandler;
 import org.apache.commons.io.IOUtils;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.LogManager;
@@ -59,6 +60,8 @@ public class PropertiesStorage implements StorageComponent {
             _properties.store(output, _name);
             output.flush();
             output.close();
+            AgentPropertiesFileHandler.clearCache();
+            logger.debug("Cleared agent properties cache after persisting key: 
{}", key);
         } catch (IOException e) {
             logger.error("Uh-oh: ", e);
         } finally {
diff --git 
a/agent/src/main/java/com/cloud/agent/properties/AgentPropertiesFileHandler.java
 
b/agent/src/main/java/com/cloud/agent/properties/AgentPropertiesFileHandler.java
index b28018fcd94..83ac63e5bc6 100644
--- 
a/agent/src/main/java/com/cloud/agent/properties/AgentPropertiesFileHandler.java
+++ 
b/agent/src/main/java/com/cloud/agent/properties/AgentPropertiesFileHandler.java
@@ -17,6 +17,7 @@ package com.cloud.agent.properties;
 import com.cloud.utils.PropertiesUtil;
 import java.io.File;
 import java.io.IOException;
+import java.util.Properties;
 import org.apache.cloudstack.utils.security.KeyStoreUtils;
 import org.apache.commons.beanutils.ConvertUtils;
 import org.apache.commons.beanutils.converters.IntegerConverter;
@@ -29,13 +30,18 @@ import org.apache.logging.log4j.LogManager;
  * This class provides a facility to read the agent's properties file and get
  * its properties, according to the {@link AgentProperties} properties 
constants.
  *
+ * Properties are loaded lazily and cached until the cache is cleared (for 
example, after persisting updates).
  */
 public class AgentPropertiesFileHandler {
 
     protected static Logger LOGGER = 
LogManager.getLogger(AgentPropertiesFileHandler.class);
 
+    // Simple singleton caching - loaded once, cached forever
+    private static volatile Properties cachedProperties;
+
     /**
      * This method reads the property in the agent.properties file.
+     * Properties are loaded once and cached for the agent's lifetime.
      *
      * @param property the property to retrieve.
      * @return The value of the property. If the property is not available, the
@@ -45,16 +51,15 @@ public class AgentPropertiesFileHandler {
         T defaultValue = property.getDefaultValue();
         String name = property.getName();
 
-        File agentPropertiesFile = 
PropertiesUtil.findConfigFile(KeyStoreUtils.AGENT_PROPSFILE);
-
-        if (agentPropertiesFile == null) {
-            LOGGER.debug("File [{}] was not found, we will use default defined 
values. Property [{}]: [{}].", KeyStoreUtils.AGENT_PROPSFILE, name, 
defaultValue);
+        Properties properties = getCachedProperties();
 
+        if (properties == null) {
+            LOGGER.debug(String.format("Properties file was not found or could 
not be loaded, using default values. Property [%s]: [%s].", name, 
defaultValue));
             return defaultValue;
         }
 
         try {
-            String configValue = 
PropertiesUtil.loadFromFile(agentPropertiesFile).getProperty(name);
+            String configValue = properties.getProperty(name);
             if (StringUtils.isBlank(configValue)) {
                 LOGGER.debug("Property [{}] has empty or null value. Using 
default value [{}].", name, defaultValue);
                 return defaultValue;
@@ -71,11 +76,69 @@ public class AgentPropertiesFileHandler {
             LOGGER.debug("Property [{}] was altered. Now using the value 
[{}].", name, configValue);
             return (T)ConvertUtils.convert(configValue, 
property.getTypeClass());
 
-        } catch (IOException ex) {
+        } catch (RuntimeException ex) {
             LOGGER.debug("Failed to get property [{}]. Using default value 
[{}].", name, defaultValue, ex);
         }
 
         return defaultValue;
     }
 
+    /**
+     * Gets the cached properties, loading them once if not already loaded.
+     * Agent properties are static configuration that don't change during 
runtime.
+     *
+     * @return cached Properties object or null if file cannot be loaded
+     */
+    private static Properties getCachedProperties() {
+        Properties properties = cachedProperties;
+        if (properties == null) {
+            synchronized (AgentPropertiesFileHandler.class) {
+                properties = cachedProperties;
+                if (properties == null) {
+                    loadProperties();
+                    properties = cachedProperties;
+                }
+            }
+        }
+        return properties;
+    }
+
+    /**
+     * Loads properties from file and caches them for the agent's lifetime.
+     */
+    private static void loadProperties() {
+        File agentPropertiesFile = 
PropertiesUtil.findConfigFile(KeyStoreUtils.AGENT_PROPSFILE);
+
+        if (agentPropertiesFile == null) {
+            LOGGER.debug("File [{}] was not found.", 
KeyStoreUtils.AGENT_PROPSFILE);
+            return;
+        }
+
+        try {
+            Properties newProperties = 
PropertiesUtil.loadFromFile(agentPropertiesFile);
+            cachedProperties = newProperties;
+
+            LOGGER.info("Loaded {} properties from [{}]", 
newProperties.size(), agentPropertiesFile.getAbsolutePath());
+
+        } catch (IOException ex) {
+            LOGGER.error("Failed to load properties from file [{}].", 
agentPropertiesFile.getAbsolutePath(), ex);
+        }
+    }
+
+    /**
+     * Clears the properties cache.
+     */
+    public static synchronized void clearCache() {
+        LOGGER.info("Clearing agent properties cache");
+        cachedProperties = null;
+    }
+
+    /**
+     * Returns whether the properties cache is currently loaded.
+     *
+     * @return true if properties are cached, false otherwise.
+     */
+    public static boolean isCacheLoaded() {
+        return cachedProperties != null;
+    }
 }
diff --git 
a/agent/src/test/java/com/cloud/agent/properties/AgentPropertiesFileHandlerTest.java
 
b/agent/src/test/java/com/cloud/agent/properties/AgentPropertiesFileHandlerTest.java
index 749f0f14444..4a75446a0cb 100644
--- 
a/agent/src/test/java/com/cloud/agent/properties/AgentPropertiesFileHandlerTest.java
+++ 
b/agent/src/test/java/com/cloud/agent/properties/AgentPropertiesFileHandlerTest.java
@@ -58,12 +58,14 @@ public class AgentPropertiesFileHandlerTest extends 
TestCase {
     @Before
     public void setUp() throws Exception {
         propertiesUtilMocked = Mockito.mockStatic(PropertiesUtil.class);
+        AgentPropertiesFileHandler.clearCache();
     }
 
     @Override
     @After
     public void tearDown() throws Exception {
         propertiesUtilMocked.close();
+        AgentPropertiesFileHandler.clearCache();
     }
 
     @Test
@@ -196,4 +198,39 @@ public class AgentPropertiesFileHandlerTest extends 
TestCase {
         Assert.assertEquals(expectedResult, result);
     }
 
+    @Test
+    public void testCacheFunctionality() throws Exception {
+        String expectedResult = "cached-value";
+        AgentProperties.Property<String> agentProperty = new 
AgentProperties.Property<String>("test.property", "default", String.class);
+
+        propertiesUtilMocked.when(() -> 
PropertiesUtil.findConfigFile(Mockito.anyString())).thenReturn(fileMock);
+        propertiesUtilMocked.when(() -> 
PropertiesUtil.loadFromFile(Mockito.any())).thenReturn(propertiesMock);
+        
Mockito.doReturn(expectedResult).when(propertiesMock).getProperty(Mockito.anyString());
+
+        Assert.assertFalse("Cache should be empty initially", 
AgentPropertiesFileHandler.isCacheLoaded());
+
+        String result1 = 
AgentPropertiesFileHandler.getPropertyValue(agentProperty);
+        Assert.assertEquals("First call should return correct value", 
expectedResult, result1);
+        Assert.assertTrue("Cache should be loaded after first call", 
AgentPropertiesFileHandler.isCacheLoaded());
+
+        Mockito.verify(PropertiesUtil.class, Mockito.times(1));
+        PropertiesUtil.loadFromFile(Mockito.any(File.class));
+
+        String result2 = 
AgentPropertiesFileHandler.getPropertyValue(agentProperty);
+        Assert.assertEquals("Second call should return same cached value", 
expectedResult, result2);
+        Assert.assertTrue("Cache should still be loaded", 
AgentPropertiesFileHandler.isCacheLoaded());
+
+        Mockito.verify(PropertiesUtil.class, Mockito.times(1));
+        PropertiesUtil.loadFromFile(Mockito.any(File.class));
+
+        AgentPropertiesFileHandler.clearCache();
+        Assert.assertFalse("Cache should be empty after clear", 
AgentPropertiesFileHandler.isCacheLoaded());
+
+        String result3 = 
AgentPropertiesFileHandler.getPropertyValue(agentProperty);
+        Assert.assertEquals("Third call should return correct value after 
cache clear", expectedResult, result3);
+        Assert.assertTrue("Cache should be loaded again", 
AgentPropertiesFileHandler.isCacheLoaded());
+
+        Mockito.verify(PropertiesUtil.class, Mockito.times(2));
+        PropertiesUtil.loadFromFile(Mockito.any(File.class));
+    }
 }

Reply via email to