Skip to content

Commit 463ccf8

Browse files
mprokopchuknvazquez
authored andcommitted
Performance improvements of AgentPropertiesFileHandler
1 parent aa82348 commit 463ccf8

3 files changed

Lines changed: 109 additions & 6 deletions

File tree

‎agent/src/main/java/com/cloud/agent/dao/impl/PropertiesStorage.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Map;
2424
import java.util.Properties;
2525

26+
import com.cloud.agent.properties.AgentPropertiesFileHandler;
2627
import org.apache.commons.io.IOUtils;
2728
import org.apache.logging.log4j.Logger;
2829
import org.apache.logging.log4j.LogManager;
@@ -59,6 +60,8 @@ public synchronized void persist(String key, String value) {
5960
_properties.store(output, _name);
6061
output.flush();
6162
output.close();
63+
AgentPropertiesFileHandler.clearCache();
64+
logger.debug("Cleared agent properties cache after persisting key: {}", key);
6265
} catch (IOException e) {
6366
logger.error("Uh-oh: ", e);
6467
} finally {

‎agent/src/main/java/com/cloud/agent/properties/AgentPropertiesFileHandler.java‎

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.cloud.utils.PropertiesUtil;
1818
import java.io.File;
1919
import java.io.IOException;
20+
import java.util.Properties;
2021
import org.apache.cloudstack.utils.security.KeyStoreUtils;
2122
import org.apache.commons.beanutils.ConvertUtils;
2223
import org.apache.commons.beanutils.converters.IntegerConverter;
@@ -29,13 +30,18 @@
2930
* This class provides a facility to read the agent's properties file and get
3031
* its properties, according to the {@link AgentProperties} properties constants.
3132
*
33+
* Properties are loaded lazily and cached until the cache is cleared (for example, after persisting updates).
3234
*/
3335
public class AgentPropertiesFileHandler {
3436

3537
protected static Logger LOGGER = LogManager.getLogger(AgentPropertiesFileHandler.class);
3638

39+
// Simple singleton caching - loaded once, cached forever
40+
private static volatile Properties cachedProperties;
41+
3742
/**
3843
* This method reads the property in the agent.properties file.
44+
* Properties are loaded once and cached for the agent's lifetime.
3945
*
4046
* @param property the property to retrieve.
4147
* @return The value of the property. If the property is not available, the
@@ -45,16 +51,15 @@ public static <T> T getPropertyValue(AgentProperties.Property<T> property) {
4551
T defaultValue = property.getDefaultValue();
4652
String name = property.getName();
4753

48-
File agentPropertiesFile = PropertiesUtil.findConfigFile(KeyStoreUtils.AGENT_PROPSFILE);
49-
50-
if (agentPropertiesFile == null) {
51-
LOGGER.debug("File [{}] was not found, we will use default defined values. Property [{}]: [{}].", KeyStoreUtils.AGENT_PROPSFILE, name, defaultValue);
54+
Properties properties = getCachedProperties();
5255

56+
if (properties == null) {
57+
LOGGER.debug(String.format("Properties file was not found or could not be loaded, using default values. Property [%s]: [%s].", name, defaultValue));
5358
return defaultValue;
5459
}
5560

5661
try {
57-
String configValue = PropertiesUtil.loadFromFile(agentPropertiesFile).getProperty(name);
62+
String configValue = properties.getProperty(name);
5863
if (StringUtils.isBlank(configValue)) {
5964
LOGGER.debug("Property [{}] has empty or null value. Using default value [{}].", name, defaultValue);
6065
return defaultValue;
@@ -71,11 +76,69 @@ public static <T> T getPropertyValue(AgentProperties.Property<T> property) {
7176
LOGGER.debug("Property [{}] was altered. Now using the value [{}].", name, configValue);
7277
return (T)ConvertUtils.convert(configValue, property.getTypeClass());
7378

74-
} catch (IOException ex) {
79+
} catch (RuntimeException ex) {
7580
LOGGER.debug("Failed to get property [{}]. Using default value [{}].", name, defaultValue, ex);
7681
}
7782

7883
return defaultValue;
7984
}
8085

86+
/**
87+
* Gets the cached properties, loading them once if not already loaded.
88+
* Agent properties are static configuration that don't change during runtime.
89+
*
90+
* @return cached Properties object or null if file cannot be loaded
91+
*/
92+
private static Properties getCachedProperties() {
93+
Properties properties = cachedProperties;
94+
if (properties == null) {
95+
synchronized (AgentPropertiesFileHandler.class) {
96+
properties = cachedProperties;
97+
if (properties == null) {
98+
loadProperties();
99+
properties = cachedProperties;
100+
}
101+
}
102+
}
103+
return properties;
104+
}
105+
106+
/**
107+
* Loads properties from file and caches them for the agent's lifetime.
108+
*/
109+
private static void loadProperties() {
110+
File agentPropertiesFile = PropertiesUtil.findConfigFile(KeyStoreUtils.AGENT_PROPSFILE);
111+
112+
if (agentPropertiesFile == null) {
113+
LOGGER.debug("File [{}] was not found.", KeyStoreUtils.AGENT_PROPSFILE);
114+
return;
115+
}
116+
117+
try {
118+
Properties newProperties = PropertiesUtil.loadFromFile(agentPropertiesFile);
119+
cachedProperties = newProperties;
120+
121+
LOGGER.info("Loaded {} properties from [{}]", newProperties.size(), agentPropertiesFile.getAbsolutePath());
122+
123+
} catch (IOException ex) {
124+
LOGGER.error("Failed to load properties from file [{}].", agentPropertiesFile.getAbsolutePath(), ex);
125+
}
126+
}
127+
128+
/**
129+
* Clears the properties cache.
130+
*/
131+
public static synchronized void clearCache() {
132+
LOGGER.info("Clearing agent properties cache");
133+
cachedProperties = null;
134+
}
135+
136+
/**
137+
* Returns whether the properties cache is currently loaded.
138+
*
139+
* @return true if properties are cached, false otherwise.
140+
*/
141+
public static boolean isCacheLoaded() {
142+
return cachedProperties != null;
143+
}
81144
}

‎agent/src/test/java/com/cloud/agent/properties/AgentPropertiesFileHandlerTest.java‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,14 @@ public class AgentPropertiesFileHandlerTest extends TestCase {
5858
@Before
5959
public void setUp() throws Exception {
6060
propertiesUtilMocked = Mockito.mockStatic(PropertiesUtil.class);
61+
AgentPropertiesFileHandler.clearCache();
6162
}
6263

6364
@Override
6465
@After
6566
public void tearDown() throws Exception {
6667
propertiesUtilMocked.close();
68+
AgentPropertiesFileHandler.clearCache();
6769
}
6870

6971
@Test
@@ -196,4 +198,39 @@ public void getPropertyValueTestValidLongPropertyReturnPropertyValue() throws Ex
196198
Assert.assertEquals(expectedResult, result);
197199
}
198200

201+
@Test
202+
public void testCacheFunctionality() throws Exception {
203+
String expectedResult = "cached-value";
204+
AgentProperties.Property<String> agentProperty = new AgentProperties.Property<String>("test.property", "default", String.class);
205+
206+
propertiesUtilMocked.when(() -> PropertiesUtil.findConfigFile(Mockito.anyString())).thenReturn(fileMock);
207+
propertiesUtilMocked.when(() -> PropertiesUtil.loadFromFile(Mockito.any())).thenReturn(propertiesMock);
208+
Mockito.doReturn(expectedResult).when(propertiesMock).getProperty(Mockito.anyString());
209+
210+
Assert.assertFalse("Cache should be empty initially", AgentPropertiesFileHandler.isCacheLoaded());
211+
212+
String result1 = AgentPropertiesFileHandler.getPropertyValue(agentProperty);
213+
Assert.assertEquals("First call should return correct value", expectedResult, result1);
214+
Assert.assertTrue("Cache should be loaded after first call", AgentPropertiesFileHandler.isCacheLoaded());
215+
216+
Mockito.verify(PropertiesUtil.class, Mockito.times(1));
217+
PropertiesUtil.loadFromFile(Mockito.any(File.class));
218+
219+
String result2 = AgentPropertiesFileHandler.getPropertyValue(agentProperty);
220+
Assert.assertEquals("Second call should return same cached value", expectedResult, result2);
221+
Assert.assertTrue("Cache should still be loaded", AgentPropertiesFileHandler.isCacheLoaded());
222+
223+
Mockito.verify(PropertiesUtil.class, Mockito.times(1));
224+
PropertiesUtil.loadFromFile(Mockito.any(File.class));
225+
226+
AgentPropertiesFileHandler.clearCache();
227+
Assert.assertFalse("Cache should be empty after clear", AgentPropertiesFileHandler.isCacheLoaded());
228+
229+
String result3 = AgentPropertiesFileHandler.getPropertyValue(agentProperty);
230+
Assert.assertEquals("Third call should return correct value after cache clear", expectedResult, result3);
231+
Assert.assertTrue("Cache should be loaded again", AgentPropertiesFileHandler.isCacheLoaded());
232+
233+
Mockito.verify(PropertiesUtil.class, Mockito.times(2));
234+
PropertiesUtil.loadFromFile(Mockito.any(File.class));
235+
}
199236
}

0 commit comments

Comments
 (0)