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

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


The following commit(s) were added to refs/heads/4.20 by this push:
     new 9bc283e5c26 Introducing granular command timeouts global setting 
(#9659)
9bc283e5c26 is described below

commit 9bc283e5c265f4c7714249a7cee046aadfe065c6
Author: Harikrishna <[email protected]>
AuthorDate: Tue Jan 7 17:06:32 2025 +0530

    Introducing granular command timeouts global setting (#9659)
    
    * Introducing granular command timeouts global setting
    
    * fix marvin tests
    
    * Fixed log messages
    
    * some more log message fix
    
    * Fix empty value setting
    
    * Converted the global setting to non-dynamic
    
    * set wait on command only when granular wait is defined. This is to keep 
the backward compatibility
    
    * Improve error logging
---
 .../main/java/com/cloud/agent/AgentManager.java    |   4 +
 .../com/cloud/agent/manager/AgentManagerImpl.java  |  79 +++++++-
 .../cloud/agent/manager/AgentManagerImplTest.java  |  20 ++
 .../configuration/ConfigurationManagerImpl.java    |  74 +++++++
 .../configuration/ConfigurationManagerTest.java    | 218 ++++++++++++++-------
 test/integration/smoke/test_global_settings.py     |  49 +++++
 6 files changed, 370 insertions(+), 74 deletions(-)

diff --git 
a/engine/components-api/src/main/java/com/cloud/agent/AgentManager.java 
b/engine/components-api/src/main/java/com/cloud/agent/AgentManager.java
index 2182dfc542d..81525ca13f1 100644
--- a/engine/components-api/src/main/java/com/cloud/agent/AgentManager.java
+++ b/engine/components-api/src/main/java/com/cloud/agent/AgentManager.java
@@ -50,6 +50,10 @@ public interface AgentManager {
     ConfigKey<Integer> ReadyCommandWait = new ConfigKey<Integer>("Advanced", 
Integer.class, "ready.command.wait",
             "60", "Time in seconds to wait for Ready command to return", true);
 
+    ConfigKey<String> GranularWaitTimeForCommands = new 
ConfigKey<>("Advanced", String.class, "commands.timeout", "",
+            "This timeout overrides the wait global config. This holds a comma 
separated key value pairs containing timeout (in seconds) for specific 
commands. " +
+                    "For example: DhcpEntryCommand=600, 
SavePasswordCommand=300, VmDataCommand=300", false);
+
     public enum TapAgentsAction {
         Add, Del, Contains,
     }
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 9333410e0aa..63e97519534 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
@@ -53,6 +53,7 @@ import 
org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext;
 import org.apache.cloudstack.managed.context.ManagedContextRunnable;
 import org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDao;
 import org.apache.cloudstack.utils.identity.ManagementServerNode;
+import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang3.BooleanUtils;
 
 import com.cloud.agent.AgentManager;
@@ -139,6 +140,7 @@ public class AgentManagerImpl extends ManagerBase 
implements AgentManager, Handl
     protected List<Pair<Integer, Listener>> _cmdMonitors = new 
ArrayList<Pair<Integer, Listener>>(17);
     protected List<Pair<Integer, StartupCommandProcessor>> _creationMonitors = 
new ArrayList<Pair<Integer, StartupCommandProcessor>>(17);
     protected List<Long> _loadingAgents = new ArrayList<Long>();
+    protected Map<String, Integer> _commandTimeouts = new HashMap<>();
     private int _monitorId = 0;
     private final Lock _agentStatusLock = new ReentrantLock();
 
@@ -241,6 +243,8 @@ public class AgentManagerImpl extends ManagerBase 
implements AgentManager, Handl
 
         _monitorExecutor = new ScheduledThreadPoolExecutor(1, new 
NamedThreadFactory("AgentMonitor"));
 
+        initializeCommandTimeouts();
+
         return true;
     }
 
@@ -424,6 +428,62 @@ public class AgentManagerImpl extends ManagerBase 
implements AgentManager, Handl
         }
     }
 
+    protected int getTimeout(final Commands commands, int timeout) {
+        int result;
+        if (timeout > 0) {
+            result = timeout;
+        } else {
+            result = Wait.value();
+        }
+
+        int granularTimeout = getTimeoutFromGranularWaitTime(commands);
+        return (granularTimeout > 0) ? granularTimeout : result;
+    }
+
+    protected int getTimeoutFromGranularWaitTime(final Commands commands) {
+        int maxWait = 0;
+        if (MapUtils.isNotEmpty(_commandTimeouts)) {
+            for (final Command cmd : commands) {
+                String simpleCommandName = cmd.getClass().getSimpleName();
+                Integer commandTimeout = 
_commandTimeouts.get(simpleCommandName);
+                if (commandTimeout != null && commandTimeout > maxWait) {
+                    maxWait = commandTimeout;
+                }
+            }
+        }
+
+        return maxWait;
+    }
+
+    private void initializeCommandTimeouts() {
+        String commandWaits = GranularWaitTimeForCommands.value().trim();
+        if (StringUtils.isNotEmpty(commandWaits)) {
+            _commandTimeouts = getCommandTimeoutsMap(commandWaits);
+            logger.info(String.format("Timeouts for management server internal 
commands successfully initialized from global setting commands.timeout: %s", 
_commandTimeouts));
+        }
+    }
+
+    private Map<String, Integer> getCommandTimeoutsMap(String commandWaits) {
+        String[] commandPairs = commandWaits.split(",");
+        Map<String, Integer> commandTimeouts = new HashMap<>();
+
+        for (String commandPair : commandPairs) {
+            String[] parts = commandPair.trim().split("=");
+            if (parts.length == 2) {
+                try {
+                    String commandName = parts[0].trim();
+                    int commandTimeout = Integer.parseInt(parts[1].trim());
+                    commandTimeouts.put(commandName, commandTimeout);
+                } catch (NumberFormatException e) {
+                    logger.error(String.format("Initialising the timeouts 
using commands.timeout: %s for management server internal commands failed with 
error %s", commandPair, e.getMessage()));
+                }
+            } else {
+                logger.error(String.format("Error initialising the timeouts 
for management server internal commands. Invalid format in commands.timeout: 
%s", commandPair));
+            }
+        }
+        return commandTimeouts;
+    }
+
     @Override
     public Answer[] send(final Long hostId, final Commands commands, int 
timeout) throws AgentUnavailableException, OperationTimedoutException {
         assert hostId != null : "Who's not checking the agent id before 
sending?  ... (finger wagging)";
@@ -431,8 +491,14 @@ public class AgentManagerImpl extends ManagerBase 
implements AgentManager, Handl
             throw new AgentUnavailableException(-1);
         }
 
-        if (timeout <= 0) {
-            timeout = Wait.value();
+        int wait = getTimeout(commands, timeout);
+        logger.debug(String.format("Wait time setting on %s is %d seconds", 
commands, wait));
+        for (Command cmd : commands) {
+            String simpleCommandName = cmd.getClass().getSimpleName();
+            Integer commandTimeout = _commandTimeouts.get(simpleCommandName);
+            if (commandTimeout != null) {
+                cmd.setWait(wait);
+            }
         }
 
         if (CheckTxnBeforeSending.value()) {
@@ -454,7 +520,7 @@ public class AgentManagerImpl extends ManagerBase 
implements AgentManager, Handl
 
         final Request req = new Request(hostId, agent.getName(), _nodeId, 
cmds, commands.stopOnError(), true);
         req.setSequence(agent.getNextSequence());
-        final Answer[] answers = agent.send(req, timeout);
+        final Answer[] answers = agent.send(req, wait);
         notifyAnswersToMonitors(hostId, req.getSequence(), answers);
         commands.setAnswers(answers);
         return answers;
@@ -997,6 +1063,11 @@ public class AgentManagerImpl extends ManagerBase 
implements AgentManager, Handl
     @Override
     public Answer[] send(final Long hostId, final Commands cmds) throws 
AgentUnavailableException, OperationTimedoutException {
         int wait = 0;
+        if (cmds.size() > 1) {
+            logger.debug(String.format("Checking the wait time in seconds to 
be used for the following commands : %s. If there are multiple commands sent at 
once," +
+                    "then max wait time of those will be used", cmds));
+        }
+
         for (final Command cmd : cmds) {
             if (cmd.getWait() > wait) {
                 wait = cmd.getWait();
@@ -1821,7 +1892,7 @@ public class AgentManagerImpl extends ManagerBase 
implements AgentManager, Handl
     @Override
     public ConfigKey<?>[] getConfigKeys() {
         return new ConfigKey<?>[] { CheckTxnBeforeSending, Workers, Port, 
Wait, AlertWait, DirectAgentLoadSize,
-                DirectAgentPoolSize, DirectAgentThreadCap, 
EnableKVMAutoEnableDisable, ReadyCommandWait };
+                DirectAgentPoolSize, DirectAgentThreadCap, 
EnableKVMAutoEnableDisable, ReadyCommandWait, GranularWaitTimeForCommands };
     }
 
     protected class SetHostParamsListener implements Listener {
diff --git 
a/engine/orchestration/src/test/java/com/cloud/agent/manager/AgentManagerImplTest.java
 
b/engine/orchestration/src/test/java/com/cloud/agent/manager/AgentManagerImplTest.java
index 452cfd90056..52b7ed77533 100644
--- 
a/engine/orchestration/src/test/java/com/cloud/agent/manager/AgentManagerImplTest.java
+++ 
b/engine/orchestration/src/test/java/com/cloud/agent/manager/AgentManagerImplTest.java
@@ -83,4 +83,24 @@ public class AgentManagerImplTest {
         }
         Mockito.verify(mgr, 
Mockito.times(1)).handleDisconnectWithoutInvestigation(Mockito.any(attache.getClass()),
 Mockito.eq(Status.Event.AgentDisconnected), Mockito.eq(true), 
Mockito.eq(true));
     }
+
+    @Test
+    public void testGetTimeoutWithPositiveTimeout() {
+        Commands commands = Mockito.mock(Commands.class);
+        int timeout = 30;
+        int result = mgr.getTimeout(commands, timeout);
+
+        Assert.assertEquals(30, result);
+    }
+
+    @Test
+    public void testGetTimeoutWithGranularTimeout() {
+        Commands commands = Mockito.mock(Commands.class);
+        
Mockito.doReturn(50).when(mgr).getTimeoutFromGranularWaitTime(commands);
+
+        int timeout = 0;
+        int result = mgr.getTimeout(commands, timeout);
+
+        Assert.assertEquals(50, result);
+    }
 }
diff --git 
a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java 
b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
index dee1aa81758..d7e2160ef35 100644
--- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
+++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
@@ -1245,6 +1245,8 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
             type = configuration.getType();
         }
 
+        validateSpecificConfigurationValues(name, value, type);
+
         boolean isTypeValid = validateValueType(value, type);
         if (!isTypeValid) {
             return String.format("Value [%s] is not a valid [%s].", value, 
type);
@@ -1373,6 +1375,78 @@ public class ConfigurationManagerImpl extends 
ManagerBase implements Configurati
         return validateIfStringValueIsInRange(name, value, range);
     }
 
+    /**
+     * Validates configuration values for the given name, value, and type.
+     * <ul>
+     *   <li>The value must be a comma-separated list of key-value pairs, 
where each value must be a positive integer.</li>
+     *   <li>Each key-value pair must be in the format "command=value", with 
the value being a positive integer greater than 0,
+     *          otherwise fails with an error message</li>
+     *   <li>Throws an {@link InvalidParameterValueException} if validation 
fails.</li>
+     * </ul>
+     *
+     * @param name  the configuration name
+     * @param value the configuration value as a comma-separated string of 
key-value pairs
+     * @param type  the configuration type, expected to be String
+     * @throws InvalidParameterValueException if validation fails with a 
specific error message
+     */
+    protected void validateSpecificConfigurationValues(String name, String 
value, Class<?> type) {
+        if (type.equals(String.class)) {
+            if 
(name.equals(AgentManager.GranularWaitTimeForCommands.toString())) {
+                Pair<Boolean, String> validationResult = 
validateCommaSeparatedKeyValueConfigWithPositiveIntegerValues(value);
+                if (!validationResult.first()) {
+                    String errMsg = validationResult.second();
+                    logger.error(validationResult.second());
+                    throw new InvalidParameterValueException(errMsg);
+                }
+            }
+        }
+    }
+
+    protected Pair<Boolean, String> 
validateCommaSeparatedKeyValueConfigWithPositiveIntegerValues(String value) {
+        try {
+            if (StringUtils.isNotEmpty(value)) {
+                String[] commands = value.split(",");
+                for (String command : commands) {
+                    command = command.trim();
+                    if (!command.contains("=")) {
+                        String errorMessage = String.format("Validation 
failed: Command '%s' does not contain '='.", command);
+                        return new Pair<>(false, errorMessage);
+                    }
+
+                    String[] parts = command.split("=");
+                    if (parts.length != 2) {
+                        String errorMessage = String.format("Validation 
failed: Command '%s' is not properly formatted.", command);
+                        return new Pair<>(false, errorMessage);
+                    }
+
+                    String commandName = parts[0].trim();
+                    String valueString = parts[1].trim();
+
+                    if (commandName.isEmpty()) {
+                        String errorMessage = String.format("Validation 
failed: Command name is missing in '%s'.", command);
+                        return new Pair<>(false, errorMessage);
+                    }
+
+                    try {
+                        int num = Integer.parseInt(valueString);
+                        if (num <= 0) {
+                            String errorMessage = String.format("Validation 
failed: The value for command '%s' is not greater than 0. Invalid value: %d", 
commandName, num);
+                            return new Pair<>(false, errorMessage);
+                        }
+                    } catch (NumberFormatException e) {
+                        String errorMessage = String.format("Validation 
failed: The value for command '%s' is not a valid integer. Invalid value: %s", 
commandName, valueString);
+                        return new Pair<>(false, errorMessage);
+                    }
+                }
+            }
+
+            return new Pair<>(true, "");
+        } catch (Exception e) {
+            String errorMessage = String.format("Validation failed: An error 
occurred while parsing the command string. Error: %s", e.getMessage());
+            return new Pair<>(false, errorMessage);
+        }
+    }
+
     /**
      * Returns a boolean indicating whether a Config's range should be 
validated. It should not be validated when:</br>
      * <ul>
diff --git 
a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java 
b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java
index ceffe019377..badf730a061 100644
--- a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java
+++ b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java
@@ -17,6 +17,7 @@
 
 package com.cloud.configuration;
 
+import com.cloud.agent.AgentManager;
 import com.cloud.api.query.dao.NetworkOfferingJoinDao;
 import com.cloud.api.query.vo.NetworkOfferingJoinVO;
 import com.cloud.configuration.Resource.ResourceType;
@@ -71,6 +72,7 @@ import com.cloud.user.ResourceLimitService;
 import com.cloud.user.User;
 import com.cloud.user.UserVO;
 import com.cloud.user.dao.AccountDao;
+import com.cloud.utils.Pair;
 import com.cloud.utils.db.Filter;
 import com.cloud.utils.db.SearchCriteria;
 import com.cloud.utils.db.TransactionLegacy;
@@ -124,7 +126,10 @@ import java.util.Set;
 import java.util.UUID;
 
 import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -361,7 +366,7 @@ public class ConfigurationManagerTest {
         try {
             configurationMgr.dedicatePublicIpRange(dedicatePublicIpRangesCmd);
         } catch (Exception e) {
-            Assert.assertTrue(e.getMessage().contains("Unable to find vlan by 
id"));
+            assertTrue(e.getMessage().contains("Unable to find vlan by id"));
         } finally {
             txn.close("runDedicatePublicIpRangeInvalidRange");
         }
@@ -390,7 +395,7 @@ public class ConfigurationManagerTest {
         try {
             configurationMgr.dedicatePublicIpRange(dedicatePublicIpRangesCmd);
         } catch (Exception e) {
-            Assert.assertTrue(e.getMessage().contains("Public IP range has 
already been dedicated"));
+            assertTrue(e.getMessage().contains("Public IP range has already 
been dedicated"));
         } finally {
             txn.close("runDedicatePublicIpRangePublicIpRangeDedicated");
         }
@@ -416,7 +421,7 @@ public class ConfigurationManagerTest {
         try {
             configurationMgr.dedicatePublicIpRange(dedicatePublicIpRangesCmd);
         } catch (Exception e) {
-            Assert.assertTrue(e.getMessage().contains("Public IP range can be 
dedicated to an account only in the zone of type Advanced"));
+            assertTrue(e.getMessage().contains("Public IP range can be 
dedicated to an account only in the zone of type Advanced"));
         } finally {
             txn.close("runDedicatePublicIpRangeInvalidZone");
         }
@@ -443,7 +448,7 @@ public class ConfigurationManagerTest {
         try {
             configurationMgr.dedicatePublicIpRange(dedicatePublicIpRangesCmd);
         } catch (Exception e) {
-            Assert.assertTrue(e.getMessage().contains("Public IP address in 
range is allocated to another account"));
+            assertTrue(e.getMessage().contains("Public IP address in range is 
allocated to another account"));
         } finally {
             txn.close("runDedicatePublicIpRangeIPAddressAllocated");
         }
@@ -465,7 +470,7 @@ public class ConfigurationManagerTest {
         
when(configurationMgr._accountVlanMapDao.remove(anyLong())).thenReturn(true);
         try {
             Boolean result = 
configurationMgr.releasePublicIpRange(releasePublicIpRangesCmd);
-            Assert.assertTrue(result);
+            assertTrue(result);
         } catch (Exception e) {
             logger.info("exception in testing 
runReleasePublicIpRangePostiveTest1 message: " + e.toString());
         } finally {
@@ -499,7 +504,7 @@ public class ConfigurationManagerTest {
         
when(configurationMgr._accountVlanMapDao.remove(anyLong())).thenReturn(true);
         try {
             Boolean result = 
configurationMgr.releasePublicIpRange(releasePublicIpRangesCmd);
-            Assert.assertTrue(result);
+            assertTrue(result);
         } catch (Exception e) {
             logger.info("exception in testing 
runReleasePublicIpRangePostiveTest2 message: " + e.toString());
         } finally {
@@ -514,7 +519,7 @@ public class ConfigurationManagerTest {
         try {
             configurationMgr.releasePublicIpRange(releasePublicIpRangesCmd);
         } catch (Exception e) {
-            Assert.assertTrue(e.getMessage().contains("Please specify a valid 
IP range id"));
+            assertTrue(e.getMessage().contains("Please specify a valid IP 
range id"));
         } finally {
             txn.close("runReleasePublicIpRangeInvalidIpRange");
         }
@@ -530,7 +535,7 @@ public class ConfigurationManagerTest {
         try {
             configurationMgr.releasePublicIpRange(releasePublicIpRangesCmd);
         } catch (Exception e) {
-            Assert.assertTrue(e.getMessage().contains("as it not dedicated to 
any domain and any account"));
+            assertTrue(e.getMessage().contains("as it not dedicated to any 
domain and any account"));
         } finally {
             txn.close("runReleaseNonDedicatedPublicIpRange");
         }
@@ -570,10 +575,10 @@ public class ConfigurationManagerTest {
         try {
             
configurationMgr.validateSourceNatServiceCapablities(sourceNatServiceCapabilityMap);
         } catch (InvalidParameterValueException e) {
-            Assert.assertTrue(e.getMessage(), e.getMessage().contains("Either 
peraccount or perzone source NAT type can be specified for 
SupportedSourceNatTypes"));
+            assertTrue(e.getMessage(), e.getMessage().contains("Either 
peraccount or perzone source NAT type can be specified for 
SupportedSourceNatTypes"));
             caught = true;
         }
-        Assert.assertTrue("should not be accepted", caught);
+        assertTrue("should not be accepted", caught);
     }
 
     @Test
@@ -585,10 +590,10 @@ public class ConfigurationManagerTest {
         try {
             
configurationMgr.validateSourceNatServiceCapablities(sourceNatServiceCapabilityMap);
         } catch (InvalidParameterValueException e) {
-            Assert.assertTrue(e.getMessage(), e.getMessage().contains("Unknown 
specified value for RedundantRouter"));
+            assertTrue(e.getMessage(), e.getMessage().contains("Unknown 
specified value for RedundantRouter"));
             caught = true;
         }
-        Assert.assertTrue("should not be accepted", caught);
+        assertTrue("should not be accepted", caught);
     }
 
     @Test
@@ -600,10 +605,10 @@ public class ConfigurationManagerTest {
         try {
             
configurationMgr.validateSourceNatServiceCapablities(sourceNatServiceCapabilityMap);
         } catch (InvalidParameterValueException e) {
-            Assert.assertTrue(e.getMessage(), e.getMessage().contains("Only 
SupportedSourceNatTypes, Network.Capability[name=RedundantRouter] capabilities 
can be specified for source nat service"));
+            assertTrue(e.getMessage(), e.getMessage().contains("Only 
SupportedSourceNatTypes, Network.Capability[name=RedundantRouter] capabilities 
can be specified for source nat service"));
             caught = true;
         }
-        Assert.assertTrue("should not be accepted", caught);
+        assertTrue("should not be accepted", caught);
     }
 
     @Test
@@ -622,10 +627,10 @@ public class ConfigurationManagerTest {
         try {
             
configurationMgr.validateStaticNatServiceCapablities(staticNatServiceCapabilityMap);
         } catch (InvalidParameterValueException e) {
-            Assert.assertTrue(e.getMessage(), e.getMessage().contains("(frue 
and talse)"));
+            assertTrue(e.getMessage(), e.getMessage().contains("(frue and 
talse)"));
             caught = true;
         }
-        Assert.assertTrue("should not be accepted", caught);
+        assertTrue("should not be accepted", caught);
     }
 
     @Test
@@ -634,7 +639,7 @@ public class ConfigurationManagerTest {
         Map<Capability, String> sourceNatServiceCapabilityMap = new 
HashMap<>();
         sourceNatServiceCapabilityMap.put(Capability.SupportedSourceNatTypes, 
"peraccount");
         sourceNatServiceCapabilityMap.put(Capability.RedundantRouter, "true");
-        Assert.assertTrue(configurationMgr.isRedundantRouter(providers, 
Network.Service.SourceNat, sourceNatServiceCapabilityMap));
+        assertTrue(configurationMgr.isRedundantRouter(providers, 
Network.Service.SourceNat, sourceNatServiceCapabilityMap));
     }
 
     @Test
@@ -642,7 +647,7 @@ public class ConfigurationManagerTest {
         Map<Network.Service, Set<Network.Provider>> serviceCapabilityMap = new 
HashMap<>();
         Map<Capability, String> sourceNatServiceCapabilityMap = new 
HashMap<>();
         sourceNatServiceCapabilityMap.put(Capability.SupportedSourceNatTypes, 
"perzone");
-        
Assert.assertTrue(configurationMgr.isSharedSourceNat(serviceCapabilityMap, 
sourceNatServiceCapabilityMap));
+        assertTrue(configurationMgr.isSharedSourceNat(serviceCapabilityMap, 
sourceNatServiceCapabilityMap));
     }
 
     @Test
@@ -650,7 +655,7 @@ public class ConfigurationManagerTest {
         Map<Network.Service, Set<Network.Provider>> serviceCapabilityMap = new 
HashMap<>();
         Map<Capability, String> sourceNatServiceCapabilityMap = new 
HashMap<>();
         sourceNatServiceCapabilityMap.put(Capability.SupportedSourceNatTypes, 
"peraccount");
-        
Assert.assertFalse(configurationMgr.isSharedSourceNat(serviceCapabilityMap, 
sourceNatServiceCapabilityMap));
+        assertFalse(configurationMgr.isSharedSourceNat(serviceCapabilityMap, 
sourceNatServiceCapabilityMap));
     }
 
     @Test
@@ -659,7 +664,7 @@ public class ConfigurationManagerTest {
         sourceNatServiceCapabilityMap.put(Capability.SupportedSourceNatTypes, 
"peraccount");
         sourceNatServiceCapabilityMap.put(Capability.RedundantRouter, "True");
 
-        
Assert.assertTrue(configurationMgr.sourceNatCapabilitiesContainValidValues(sourceNatServiceCapabilityMap));
+        
assertTrue(configurationMgr.sourceNatCapabilitiesContainValidValues(sourceNatServiceCapabilityMap));
     }
 
     @Test
@@ -690,13 +695,13 @@ public class ConfigurationManagerTest {
         try {
             
configurationMgr.validateStaticNatServiceCapablities(staticNatServiceCapabilityMap);
         } catch (InvalidParameterValueException e) {
-            Assert.assertTrue(
+            assertTrue(
                     e.getMessage(),
                     e.getMessage().contains(
                         "Capability " + Capability.AssociatePublicIP.getName() 
+ " can only be set when capability " + Capability.ElasticIp.getName() + " is 
true"));
             caught = true;
         }
-        Assert.assertTrue("should not be accepted", caught);
+        assertTrue("should not be accepted", caught);
     }
 
     @Test
@@ -961,27 +966,27 @@ public class ConfigurationManagerTest {
         //Ipv4 Test
         boolean result;
         result = configurationMgr.hasSameSubnet(false, null, null, null, null, 
null, null, false, null, null, null, null, null);
-        Assert.assertFalse(result);
+        assertFalse(result);
         try {
             configurationMgr.hasSameSubnet(true, "10.0.0.1", "255.255.255.0", 
"10.0.0.2", "255.255.255.0", "10.0.0.2", "10.0.0.10", false, null, null, null, 
null, null);
             Assert.fail();
         } catch (InvalidParameterValueException e) {
-            Assert.assertEquals(e.getMessage(), "The gateway of the subnet 
should be unique. The subnet already has a gateway 10.0.0.1");
+            assertEquals(e.getMessage(), "The gateway of the subnet should be 
unique. The subnet already has a gateway 10.0.0.1");
         }
         try {
             configurationMgr.hasSameSubnet(true, "10.0.0.1", "255.255.0.0", 
"10.0.0.2", "255.255.255.0", "10.0.0.2", "10.0.0.10", false, null, null, null, 
null, null);
             Assert.fail();
         } catch (InvalidParameterValueException e){
-            Assert.assertEquals(e.getMessage(), "The subnet you are trying to 
add is a subset of the existing subnet having gateway 10.0.0.1 and netmask 
255.255.0.0");
+            assertEquals(e.getMessage(), "The subnet you are trying to add is 
a subset of the existing subnet having gateway 10.0.0.1 and netmask 
255.255.0.0");
         }
         try {
             configurationMgr.hasSameSubnet(true, "10.0.0.1", "255.255.255.0", 
"10.0.0.2", "255.255.0.0", "10.0.0.2", "10.0.0.10", false, null, null, null, 
null, null);
             Assert.fail();
         } catch (InvalidParameterValueException e) {
-            Assert.assertEquals(e.getMessage(), "The subnet you are trying to 
add is a superset of the existing subnet having gateway 10.0.0.1 and netmask 
255.255.255.0");
+            assertEquals(e.getMessage(), "The subnet you are trying to add is 
a superset of the existing subnet having gateway 10.0.0.1 and netmask 
255.255.255.0");
         }
         result = configurationMgr.hasSameSubnet(true, "10.0.0.1", 
"255.255.255.0", "10.0.0.1", "255.255.255.0", "10.0.0.2", "10.0.0.10", false, 
null, null, null, null, null);
-        Assert.assertTrue(result);
+        assertTrue(result);
 
         //Ipv6 Test
         Network ipV6Network = mock(Network.class);
@@ -992,35 +997,35 @@ public class ConfigurationManagerTest {
         doThrow(new InvalidParameterValueException("ip6Gateway and ip6Cidr 
should be defined when startIPv6/endIPv6 are passed 
in")).when(configurationMgr._networkModel).checkIp6Parameters(Mockito.anyString(),
 Mockito.anyString(), Mockito.isNull(String.class), 
Mockito.isNull(String.class));
 
         configurationMgr.hasSameSubnet(false, null, null, null, null, null, 
null, true, "2001:db8:0:f101::1", "2001:db8:0:f101::0/64", 
"2001:db8:0:f101::2", "2001:db8:0:f101::a", ipV6Network);
-        Assert.assertTrue(result);
+        assertTrue(result);
         try {
             configurationMgr.hasSameSubnet(false, null, null, null, null, 
null, null, true, "2001:db8:0:f101::2", "2001:db8:0:f101::0/64", 
"2001:db8:0:f101::2", "2001:db8:0:f101::a", ipV6Network);
             Assert.fail();
         } catch (InvalidParameterValueException e){
-            Assert.assertEquals(e.getMessage(), "The input gateway 
2001:db8:0:f101::2 is not same as network gateway 2001:db8:0:f101::1");
+            assertEquals(e.getMessage(), "The input gateway 2001:db8:0:f101::2 
is not same as network gateway 2001:db8:0:f101::1");
         }
         try {
             configurationMgr.hasSameSubnet(false, null, null, null, null, 
null, null, true, "2001:db8:0:f101::1", "2001:db8:0:f101::0/63", 
"2001:db8:0:f101::2", "2001:db8:0:f101::a", ipV6Network);
             Assert.fail();
         } catch (InvalidParameterValueException e){
-            Assert.assertEquals(e.getMessage(), "The input cidr 
2001:db8:0:f101::0/63 is not same as network cidr 2001:db8:0:f101::0/64");
+            assertEquals(e.getMessage(), "The input cidr 2001:db8:0:f101::0/63 
is not same as network cidr 2001:db8:0:f101::0/64");
         }
 
         try {
             configurationMgr.hasSameSubnet(false, null, null, null, null, 
null, null, true, "2001:db8:0:f101::1", "2001:db8:0:f101::0/64", 
"2001:db9:0:f101::2", "2001:db9:0:f101::a", ipV6Network);
             Assert.fail();
         } catch (InvalidParameterValueException e) {
-            Assert.assertEquals(e.getMessage(), "Exception from Mock: 
startIPv6 is not in ip6cidr indicated network!");
+            assertEquals(e.getMessage(), "Exception from Mock: startIPv6 is 
not in ip6cidr indicated network!");
         }
         try {
             configurationMgr.hasSameSubnet(false, null, null, null, null, 
null, null, true, "2001:db8:0:f101::1", "2001:db8:0:f101::0/64", 
"2001:db8:0:f101::a", "2001:db9:0:f101::2", ipV6Network);
             Assert.fail();
         } catch(InvalidParameterValueException e) {
-            Assert.assertEquals(e.getMessage(), "Exception from Mock: endIPv6 
is not in ip6cidr indicated network!");
+            assertEquals(e.getMessage(), "Exception from Mock: endIPv6 is not 
in ip6cidr indicated network!");
         }
 
         result = configurationMgr.hasSameSubnet(false, null, null, null, null, 
null, null, true, null, null, "2001:db8:0:f101::2", "2001:db8:0:f101::a", 
ipV6Network);
-        Assert.assertTrue(result);
+        assertTrue(result);
     }
 
     @Test(expected = CloudRuntimeException.class)
@@ -1035,12 +1040,12 @@ public class ConfigurationManagerTest {
 
     @Test
     public void testGetVlanNumberFromUriVlan() {
-        Assert.assertEquals("7", 
configurationMgr.getVlanNumberFromUri("vlan://7"));
+        assertEquals("7", configurationMgr.getVlanNumberFromUri("vlan://7"));
     }
 
     @Test
     public void testGetVlanNumberFromUriUntagged() {
-        Assert.assertEquals("untagged", 
configurationMgr.getVlanNumberFromUri("vlan://untagged"));
+        assertEquals("untagged", 
configurationMgr.getVlanNumberFromUri("vlan://untagged"));
     }
 
     @Test
@@ -1080,48 +1085,48 @@ public class ConfigurationManagerTest {
 
     @Test
     public void shouldUpdateDiskOfferingTests(){
-        
Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(),
 Mockito.anyString(), Mockito.anyInt(), Mockito.anyBoolean(), 
Mockito.anyString(), Mockito.anyString(), 
Mockito.any(DiskOffering.State.class)));
-        
Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(),
 nullable(String.class), nullable(Integer.class), nullable(Boolean.class), 
nullable(String.class), nullable(String.class), 
nullable(DiskOffering.State.class)));
-        
Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
 nullable(String.class), nullable(Integer.class), nullable(Boolean.class), 
nullable(String.class), nullable(String.class), 
Mockito.any(DiskOffering.State.class)));
-        
Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
 Mockito.anyString(), nullable(Integer.class), nullable(Boolean.class), 
nullable(String.class), nullable(String.class), 
nullable(DiskOffering.State.class)));
-        
Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
 nullable(String.class), Mockito.anyInt(), nullable(Boolean.class), 
nullable(String.class), nullable(String.class), 
nullable(DiskOffering.State.class)));
-        
Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
 nullable(String.class), nullable(int.class), Mockito.anyBoolean(), 
nullable(String.class), nullable(String.class), 
nullable(DiskOffering.State.class)));
-        
Assert.assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class),
 nullable(String.class), nullable(int.class), nullable(Boolean.class), 
Mockito.anyString(), Mockito.anyString(), nullable(DiskOffering.State.class)));
+        
assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(), 
Mockito.anyString(), Mockito.anyInt(), Mockito.anyBoolean(), 
Mockito.anyString(), Mockito.anyString(), 
Mockito.any(DiskOffering.State.class)));
+        
assertTrue(configurationMgr.shouldUpdateDiskOffering(Mockito.anyString(), 
nullable(String.class), nullable(Integer.class), nullable(Boolean.class), 
nullable(String.class), nullable(String.class), 
nullable(DiskOffering.State.class)));
+        
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), 
nullable(String.class), nullable(Integer.class), nullable(Boolean.class), 
nullable(String.class), nullable(String.class), 
Mockito.any(DiskOffering.State.class)));
+        
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), 
Mockito.anyString(), nullable(Integer.class), nullable(Boolean.class), 
nullable(String.class), nullable(String.class), 
nullable(DiskOffering.State.class)));
+        
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), 
nullable(String.class), Mockito.anyInt(), nullable(Boolean.class), 
nullable(String.class), nullable(String.class), 
nullable(DiskOffering.State.class)));
+        
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), 
nullable(String.class), nullable(int.class), Mockito.anyBoolean(), 
nullable(String.class), nullable(String.class), 
nullable(DiskOffering.State.class)));
+        
assertTrue(configurationMgr.shouldUpdateDiskOffering(nullable(String.class), 
nullable(String.class), nullable(int.class), nullable(Boolean.class), 
Mockito.anyString(), Mockito.anyString(), nullable(DiskOffering.State.class)));
     }
 
     @Test
     public void shouldUpdateDiskOfferingTestFalse(){
-        Assert.assertFalse(configurationMgr.shouldUpdateDiskOffering(null, 
null, null, null, null, null, null));
+        assertFalse(configurationMgr.shouldUpdateDiskOffering(null, null, 
null, null, null, null, null));
     }
 
     @Test
     public void shouldUpdateIopsRateParametersTestFalse() {
-        
Assert.assertFalse(configurationMgr.shouldUpdateIopsRateParameters(null, null, 
null, null, null, null));
+        assertFalse(configurationMgr.shouldUpdateIopsRateParameters(null, 
null, null, null, null, null));
     }
 
     @Test
     public void shouldUpdateIopsRateParametersTests(){
-        
Assert.assertTrue(configurationMgr.shouldUpdateIopsRateParameters(Mockito.anyLong(),
 Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), 
Mockito.anyLong()));
-        
Assert.assertTrue(configurationMgr.shouldUpdateIopsRateParameters(nullable(Long.class),
 Mockito.anyLong(), nullable(Long.class), nullable(Long.class), 
nullable(Long.class), nullable(Long.class)));
-        
Assert.assertTrue(configurationMgr.shouldUpdateIopsRateParameters(nullable(Long.class),
 nullable(Long.class), Mockito.anyLong(), nullable(Long.class), 
nullable(Long.class), nullable(Long.class)));
-        
Assert.assertTrue(configurationMgr.shouldUpdateIopsRateParameters(nullable(Long.class),
 nullable(Long.class), nullable(Long.class), Mockito.anyLong(), 
nullable(Long.class), nullable(Long.class)));
-        
Assert.assertTrue(configurationMgr.shouldUpdateIopsRateParameters(nullable(Long.class),
 nullable(Long.class), nullable(Long.class), nullable(Long.class), 
Mockito.anyLong(), nullable(Long.class)));
-        
Assert.assertTrue(configurationMgr.shouldUpdateIopsRateParameters(nullable(Long.class),
 nullable(Long.class), nullable(Long.class), nullable(Long.class), 
nullable(Long.class), Mockito.anyLong()));
+        
assertTrue(configurationMgr.shouldUpdateIopsRateParameters(Mockito.anyLong(), 
Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), 
Mockito.anyLong()));
+        
assertTrue(configurationMgr.shouldUpdateIopsRateParameters(nullable(Long.class),
 Mockito.anyLong(), nullable(Long.class), nullable(Long.class), 
nullable(Long.class), nullable(Long.class)));
+        
assertTrue(configurationMgr.shouldUpdateIopsRateParameters(nullable(Long.class),
 nullable(Long.class), Mockito.anyLong(), nullable(Long.class), 
nullable(Long.class), nullable(Long.class)));
+        
assertTrue(configurationMgr.shouldUpdateIopsRateParameters(nullable(Long.class),
 nullable(Long.class), nullable(Long.class), Mockito.anyLong(), 
nullable(Long.class), nullable(Long.class)));
+        
assertTrue(configurationMgr.shouldUpdateIopsRateParameters(nullable(Long.class),
 nullable(Long.class), nullable(Long.class), nullable(Long.class), 
Mockito.anyLong(), nullable(Long.class)));
+        
assertTrue(configurationMgr.shouldUpdateIopsRateParameters(nullable(Long.class),
 nullable(Long.class), nullable(Long.class), nullable(Long.class), 
nullable(Long.class), Mockito.anyLong()));
     }
 
     @Test
     public void shouldUpdateBytesRateParametersTestFalse() {
-        
Assert.assertFalse(configurationMgr.shouldUpdateBytesRateParameters(null, null, 
null, null, null, null));
+        assertFalse(configurationMgr.shouldUpdateBytesRateParameters(null, 
null, null, null, null, null));
     }
 
     @Test
     public void shouldUpdateBytesRateParametersTests(){
-        
Assert.assertTrue(configurationMgr.shouldUpdateBytesRateParameters(Mockito.anyLong(),
 Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), 
Mockito.anyLong()));
-        
Assert.assertTrue(configurationMgr.shouldUpdateBytesRateParameters(nullable(Long.class),
 Mockito.anyLong(), nullable(Long.class), nullable(Long.class), 
nullable(Long.class), nullable(Long.class)));
-        
Assert.assertTrue(configurationMgr.shouldUpdateBytesRateParameters(nullable(Long.class),
 nullable(Long.class), Mockito.anyLong(), nullable(Long.class), 
nullable(Long.class), nullable(Long.class)));
-        
Assert.assertTrue(configurationMgr.shouldUpdateBytesRateParameters(nullable(Long.class),
 nullable(Long.class), nullable(Long.class), Mockito.anyLong(), 
nullable(Long.class), nullable(Long.class)));
-        
Assert.assertTrue(configurationMgr.shouldUpdateBytesRateParameters(nullable(Long.class),
 nullable(Long.class), nullable(Long.class), nullable(Long.class), 
Mockito.anyLong(), nullable(Long.class)));
-        
Assert.assertTrue(configurationMgr.shouldUpdateBytesRateParameters(nullable(Long.class),
 nullable(Long.class), nullable(Long.class), nullable(Long.class), 
nullable(Long.class), Mockito.anyLong()));
+        
assertTrue(configurationMgr.shouldUpdateBytesRateParameters(Mockito.anyLong(), 
Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), 
Mockito.anyLong()));
+        
assertTrue(configurationMgr.shouldUpdateBytesRateParameters(nullable(Long.class),
 Mockito.anyLong(), nullable(Long.class), nullable(Long.class), 
nullable(Long.class), nullable(Long.class)));
+        
assertTrue(configurationMgr.shouldUpdateBytesRateParameters(nullable(Long.class),
 nullable(Long.class), Mockito.anyLong(), nullable(Long.class), 
nullable(Long.class), nullable(Long.class)));
+        
assertTrue(configurationMgr.shouldUpdateBytesRateParameters(nullable(Long.class),
 nullable(Long.class), nullable(Long.class), Mockito.anyLong(), 
nullable(Long.class), nullable(Long.class)));
+        
assertTrue(configurationMgr.shouldUpdateBytesRateParameters(nullable(Long.class),
 nullable(Long.class), nullable(Long.class), nullable(Long.class), 
Mockito.anyLong(), nullable(Long.class)));
+        
assertTrue(configurationMgr.shouldUpdateBytesRateParameters(nullable(Long.class),
 nullable(Long.class), nullable(Long.class), nullable(Long.class), 
nullable(Long.class), Mockito.anyLong()));
     }
 
     @Test
@@ -1235,10 +1240,10 @@ public class ConfigurationManagerTest {
             return prefixVO;
         });
         configurationMgr.createDataCenterGuestIpv6Prefix(cmd);
-        Assert.assertEquals(1, persistedPrefix.size());
+        assertEquals(1, persistedPrefix.size());
         DataCenterGuestIpv6PrefixVO prefixVO = persistedPrefix.get(0);
-        Assert.assertEquals(zoneId, prefixVO.getDataCenterId());
-        Assert.assertEquals(prefix, prefixVO.getPrefix());
+        assertEquals(zoneId, prefixVO.getDataCenterId());
+        assertEquals(prefix, prefixVO.getPrefix());
     }
 
     @Test
@@ -1255,17 +1260,17 @@ public class ConfigurationManagerTest {
                         Mockito.mock(DataCenterGuestIpv6PrefixVO.class),
                         Mockito.mock(DataCenterGuestIpv6PrefixVO.class)));
         List<? extends DataCenterGuestIpv6Prefix> prefixes = 
configurationMgr.listDataCenterGuestIpv6Prefixes(cmd);
-        Assert.assertEquals(1, prefixes.size());
+        assertEquals(1, prefixes.size());
         ListGuestNetworkIpv6PrefixesCmd cmd1 = 
Mockito.mock(ListGuestNetworkIpv6PrefixesCmd.class);
         Mockito.when(cmd1.getId()).thenReturn(null);
         Mockito.when(cmd1.getZoneId()).thenReturn(1L);
         prefixes = configurationMgr.listDataCenterGuestIpv6Prefixes(cmd1);
-        Assert.assertEquals(2, prefixes.size());
+        assertEquals(2, prefixes.size());
         ListGuestNetworkIpv6PrefixesCmd cmd2 = 
Mockito.mock(ListGuestNetworkIpv6PrefixesCmd.class);
         Mockito.when(cmd2.getId()).thenReturn(null);
         Mockito.when(cmd2.getZoneId()).thenReturn(null);
         prefixes = configurationMgr.listDataCenterGuestIpv6Prefixes(cmd2);
-        Assert.assertEquals(3, prefixes.size());
+        assertEquals(3, prefixes.size());
     }
 
     @Test(expected = InvalidParameterValueException.class)
@@ -1304,8 +1309,8 @@ public class ConfigurationManagerTest {
             return true;
         });
         configurationMgr.deleteDataCenterGuestIpv6Prefix(cmd);
-        Assert.assertEquals(1, removedPrefix.size());
-        Assert.assertEquals(prefixId, removedPrefix.get(0));
+        assertEquals(1, removedPrefix.size());
+        assertEquals(prefixId, removedPrefix.get(0));
     }
 
     @Test(expected = InvalidParameterValueException.class)
@@ -1355,8 +1360,8 @@ public class ConfigurationManagerTest {
         mockPersistDatacenterForCreateZone();
         DataCenter zone = configurationMgr.createZone(cmd);
         Assert.assertNotNull(zone);
-        Assert.assertEquals(NetworkType.Advanced, zone.getNetworkType());
-        Assert.assertEquals(DataCenter.Type.Edge, zone.getType());
+        assertEquals(NetworkType.Advanced, zone.getNetworkType());
+        assertEquals(DataCenter.Type.Edge, zone.getType());
     }
 
     @Test
@@ -1368,8 +1373,8 @@ public class ConfigurationManagerTest {
         mockPersistDatacenterForCreateZone();
         DataCenter zone = configurationMgr.createZone(cmd);
         Assert.assertNotNull(zone);
-        Assert.assertEquals(NetworkType.Advanced, zone.getNetworkType());
-        Assert.assertEquals(DataCenter.Type.Core, zone.getType());
+        assertEquals(NetworkType.Advanced, zone.getNetworkType());
+        assertEquals(DataCenter.Type.Core, zone.getType());
     }
 
     @Test
@@ -1381,7 +1386,7 @@ public class ConfigurationManagerTest {
         mockPersistDatacenterForCreateZone();
         DataCenter zone = configurationMgr.createZone(cmd);
         Assert.assertNotNull(zone);
-        Assert.assertEquals(NetworkType.Basic, zone.getNetworkType());
+        assertEquals(NetworkType.Basic, zone.getNetworkType());
     }
 
     @Test(expected = InvalidParameterValueException.class)
@@ -1428,4 +1433,77 @@ public class ConfigurationManagerTest {
         Mockito.doNothing().when(messageBus).publish(Mockito.any(), 
Mockito.any(), Mockito.any(), Mockito.any());
         configurationMgr.createPod(zoneId, "TestPod", null, null, null, null, 
null);
     }
+
+    @Test
+    public void 
testValidateSpecificConfigurationValues_ValidFormatWithPositiveIntegers() {
+        String name = AgentManager.GranularWaitTimeForCommands.toString();
+        String validValue = "CopyCommand=120, DeleteCommand= 60";
+
+        try {
+            configurationMgr.validateSpecificConfigurationValues(name, 
validValue, String.class);
+        } catch (InvalidParameterValueException e) {
+            Assert.fail("Exception should not be thrown for a valid command 
string with positive integers, but there is an error " + e);
+        }
+    }
+
+    @Test
+    public void testValidateSpecificConfigurationValues_InvalidFormat() {
+        String name = AgentManager.GranularWaitTimeForCommands.toString();
+        String invalidValue = "{\"CopyCommand\": 120}";
+
+        try {
+            configurationMgr.validateSpecificConfigurationValues(name, 
invalidValue, String.class);
+            Assert.fail("Exception should be thrown for an invalid command 
string.");
+        } catch (InvalidParameterValueException e) {
+            assertTrue(e.getMessage().contains("does not contain '='."));
+        }
+    }
+
+    @Test
+    public void testValidCommandString() {
+        final String input = "DhcpEntryCommand=600, SavePasswordCommand=300, 
VmDataCommand=300";
+        final Pair<Boolean, String> result = 
configurationMgr.validateCommaSeparatedKeyValueConfigWithPositiveIntegerValues(input);
+        assertTrue("Expected validation to pass", result.first());
+        assertEquals("Expected no error message", "", result.second());
+    }
+
+    @Test
+    public void testInvalidCommandValue() {
+        final String input = "DhcpEntryCommand=600, SavePasswordCommand=300, 
VmDataCommand=invalid";
+        final Pair<Boolean, String> result = 
configurationMgr.validateCommaSeparatedKeyValueConfigWithPositiveIntegerValues(input);
+        assertFalse("Expected validation to fail", result.first());
+        assertEquals("Expected specific error message",
+                "Validation failed: The value for command 'VmDataCommand' is 
not a valid integer. Invalid value: invalid",
+                result.second());
+    }
+
+    @Test
+    public void testCommandWithZeroValue() {
+        final String input = "DhcpEntryCommand=600, SavePasswordCommand=0, 
VmDataCommand=300";
+        final Pair<Boolean, String> result = 
configurationMgr.validateCommaSeparatedKeyValueConfigWithPositiveIntegerValues(input);
+        assertFalse("Expected validation to fail", result.first());
+        assertEquals("Expected specific error message",
+                "Validation failed: The value for command 
'SavePasswordCommand' is not greater than 0. Invalid value: 0",
+                result.second());
+    }
+
+    @Test
+    public void testCommandWithNegativeValue() {
+        final String input = "DhcpEntryCommand=-100, SavePasswordCommand=300, 
VmDataCommand=300";
+        final Pair<Boolean, String> result = 
configurationMgr.validateCommaSeparatedKeyValueConfigWithPositiveIntegerValues(input);
+        assertFalse("Expected validation to fail", result.first());
+        assertEquals("Expected specific error message",
+                "Validation failed: The value for command 'DhcpEntryCommand' 
is not greater than 0. Invalid value: -100",
+                result.second());
+    }
+
+    @Test
+    public void testInvalidCommandStructure() {
+        final String input = "DhcpEntryCommand600, SavePasswordCommand=300, 
VmDataCommand=300";
+        final Pair<Boolean, String> result = 
configurationMgr.validateCommaSeparatedKeyValueConfigWithPositiveIntegerValues(input);
+        assertFalse("Expected validation to fail", result.first());
+        assertEquals("Expected specific error message",
+                "Validation failed: Command 'DhcpEntryCommand600' does not 
contain '='.",
+                result.second());
+    }
 }
diff --git a/test/integration/smoke/test_global_settings.py 
b/test/integration/smoke/test_global_settings.py
index 2018384ab3a..53f55736d4f 100644
--- a/test/integration/smoke/test_global_settings.py
+++ b/test/integration/smoke/test_global_settings.py
@@ -76,6 +76,12 @@ class TestUpdateConfigWithScope(cloudstackTestCase):
         updateConfigurationCmd.scopeid = 1
         self.apiClient.updateConfiguration(updateConfigurationCmd)
 
+
+        updateConfigurationCmd = updateConfiguration.updateConfigurationCmd()
+        updateConfigurationCmd.name = "commands.timeout"
+        updateConfigurationCmd.value = ""
+        self.apiClient.updateConfiguration(updateConfigurationCmd)
+
 class TestListConfigurations(cloudstackTestCase):
     """
     Test to list configurations (global settings)
@@ -181,3 +187,46 @@ class TestListConfigurations(cloudstackTestCase):
                                          subgroup=subgroup)
         self.assertNotEqual(len(listConfigurationsResponse), 0, "Check if the 
list configurations API returns a non-empty response")
         self.debug("Total %d configurations for group %s, subgroup %s" % 
(len(listConfigurationsResponse), group, subgroup))
+
+    @attr(tags=["devcloud", "basic", "advanced"], required_hardware="false")
+    def test_UpdateCommandsTimeoutConfigParamWithValidValue(self):
+        """
+        test update configuration setting for commands.timeout with valid value
+        @return:
+        """
+        updateConfigurationCmd = updateConfiguration.updateConfigurationCmd()
+        updateConfigurationCmd.name = "commands.timeout"
+        updateConfigurationCmd.value = "DhcpEntryCommand= 600, 
SavePasswordCommand= 300, VmDataCommand= 300"
+
+        updateConfigurationResponse = 
self.apiClient.updateConfiguration(updateConfigurationCmd)
+        self.debug("updated the parameter %s with value %s" % 
(updateConfigurationResponse.name, updateConfigurationResponse.value))
+
+        listConfigurationsCmd = listConfigurations.listConfigurationsCmd()
+        listConfigurationsCmd.name = updateConfigurationResponse.name
+        listConfigurationsResponse = 
self.apiClient.listConfigurations(listConfigurationsCmd)
+
+        for item in listConfigurationsResponse:
+            if item.name == updateConfigurationResponse.name:
+                configParam = item
+
+        self.assertEqual(configParam.value, updateConfigurationResponse.value, 
"Check if the update API returned is the same as the one we got in the list 
API")
+
+
+    @attr(tags=["devcloud", "basic", "advanced"], required_hardware="false")
+    def test_UpdateCommandsTimeoutConfigParamWithInvalidValue(self):
+        """
+        Test update configuration setting for commands.timeout with invalid 
valid value
+        @return:
+        """
+        updateConfigurationCmd = updateConfiguration.updateConfigurationCmd()
+        updateConfigurationCmd.name = "commands.timeout"
+        updateConfigurationCmd.value = "StartCommand: 1"  # Intentionally 
providing invalid format
+
+        try:
+            self.apiClient.updateConfiguration(updateConfigurationCmd)
+            self.fail("API call should have failed due to invalid format, but 
it succeeded.")
+        except Exception as e:
+            self.debug("Caught expected exception: %s" % str(e))
+            error_message = str(e)
+            self.assertIn("errorCode: 431", error_message, "Expected error 
code 431 for invalid format")
+            self.assertIn("Validation failed", error_message, "Expected 
validation failure message")

Reply via email to