Skip to content

Introducing granular command timeouts global setting #9659

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
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;
Expand Down Expand Up @@ -139,6 +140,7 @@
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();

Expand Down Expand Up @@ -241,6 +243,8 @@

_monitorExecutor = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("AgentMonitor"));

initializeCommandTimeouts();

Check warning on line 246 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L246

Added line #L246 was not covered by tests

return true;
}

Expand Down Expand Up @@ -424,15 +428,77 @@
}
}

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);

Check warning on line 448 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L447-L448

Added lines #L447 - L448 were not covered by tests
if (commandTimeout != null && commandTimeout > maxWait) {
maxWait = commandTimeout;

Check warning on line 450 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L450

Added line #L450 was not covered by tests
}
}
}

Check warning on line 453 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L452-L453

Added lines #L452 - L453 were not covered by tests

return maxWait;
}

private void initializeCommandTimeouts() {
String commandWaits = GranularWaitTimeForCommands.value().trim();

Check warning on line 459 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L458-L459

Added lines #L458 - L459 were not covered by tests
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));

Check warning on line 462 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L461-L462

Added lines #L461 - L462 were not covered by tests
}
}

Check warning on line 464 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L464

Added line #L464 was not covered by tests

private Map<String, Integer> getCommandTimeoutsMap(String commandWaits) {
String[] commandPairs = commandWaits.split(",");
Map<String, Integer> commandTimeouts = new HashMap<>();

Check warning on line 468 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L466-L468

Added lines #L466 - L468 were not covered by tests

for (String commandPair : commandPairs) {
String[] parts = commandPair.trim().split("=");

Check warning on line 471 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L471

Added line #L471 was not covered by tests
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()));
}

Check warning on line 479 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L473-L479

Added lines #L473 - L479 were not covered by tests
} else {
logger.error(String.format("Error initialising the timeouts for management server internal commands. Invalid format in commands.timeout: %s", commandPair));

Check warning on line 481 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L481

Added line #L481 was not covered by tests
}
}
return commandTimeouts;
}

Check warning on line 485 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L484-L485

Added lines #L484 - L485 were not covered by tests

@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)";
if (hostId == null) {
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));

Check warning on line 495 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L494-L495

Added lines #L494 - L495 were not covered by tests
for (Command cmd : commands) {
String simpleCommandName = cmd.getClass().getSimpleName();
Integer commandTimeout = _commandTimeouts.get(simpleCommandName);

Check warning on line 498 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L497-L498

Added lines #L497 - L498 were not covered by tests
if (commandTimeout != null) {
cmd.setWait(wait);

Check warning on line 500 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L500

Added line #L500 was not covered by tests
}
}

if (CheckTxnBeforeSending.value()) {
Expand All @@ -454,7 +520,7 @@

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);

Check warning on line 523 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L523

Added line #L523 was not covered by tests
notifyAnswersToMonitors(hostId, req.getSequence(), answers);
commands.setAnswers(answers);
return answers;
Expand Down Expand Up @@ -997,6 +1063,11 @@
@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," +

Check warning on line 1067 in engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java#L1067

Added line #L1067 was not covered by tests
"then max wait time of those will be used", cmds));
}

for (final Command cmd : cmds) {
if (cmd.getWait() > wait) {
wait = cmd.getWait();
Expand Down Expand Up @@ -1821,7 +1892,7 @@
@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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,24 @@ public void testNotifyMonitorsOfConnectionWhenStoragePoolConnectionHostFailure()
}
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,8 @@
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);
Expand Down Expand Up @@ -1373,6 +1375,78 @@
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);

Check warning on line 1419 in server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java#L1418-L1419

Added lines #L1418 - L1419 were not covered by tests
}

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);

Check warning on line 1427 in server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java#L1426-L1427

Added lines #L1426 - L1427 were not covered by tests
}

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);

Check warning on line 1446 in server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java#L1444-L1446

Added lines #L1444 - L1446 were not covered by tests
}
}

/**
* Returns a boolean indicating whether a Config's range should be validated. It should not be validated when:</br>
* <ul>
Expand Down
Loading
Loading