Skip to content
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

Introducing granular command timeouts global setting #9659

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
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,78 @@
}
}

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)) {
try {
_commandTimeouts = getCommandTimeoutsMap(commandWaits);
logger.info(String.format("Timeouts for management server internal commands successfully initialized from global setting commands.timeout: %s", _commandTimeouts));
} catch (Exception e) {
logger.error("Error initializing command timeouts map: " + e.getMessage());
_commandTimeouts = new HashMap<>();
}

Check warning on line 467 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-L467

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

Check warning on line 469 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#L469

Added line #L469 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 473 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-L473

Added lines #L471 - L473 were not covered by tests

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

Check warning on line 476 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#L476

Added line #L476 was not covered by tests
if (parts.length == 2) {
String commandName = parts[0].trim();
int commandTimeout = Integer.parseInt(parts[1].trim());
commandTimeouts.put(commandName, commandTimeout);
} else {
logger.warn(String.format("Invalid format in commands.timeout global setting: %s", commandPair));

Check warning on line 482 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#L478-L482

Added lines #L478 - L482 were not covered by tests
}
}
return commandTimeouts;
}

Check warning on line 486 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#L485-L486

Added lines #L485 - L486 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 496 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#L495-L496

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

Check warning on line 499 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#L498-L499

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

Check warning on line 501 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#L501

Added line #L501 was not covered by tests
}
}

if (CheckTxnBeforeSending.value()) {
Expand All @@ -454,7 +521,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 524 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#L524

Added line #L524 was not covered by tests
notifyAnswersToMonitors(hostId, req.getSequence(), answers);
commands.setAnswers(answers);
return answers;
Expand Down Expand Up @@ -988,6 +1055,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 1059 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#L1059

Added line #L1059 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 @@ -1802,7 +1874,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 @@ -1226,6 +1226,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 @@ -1354,6 +1356,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 1400 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#L1399-L1400

Added lines #L1399 - L1400 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 1408 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#L1407-L1408

Added lines #L1407 - L1408 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 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#L1425-L1427

Added lines #L1425 - L1427 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