Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package datadog.communication.ddagent;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AgentVersion {

private static final Logger log = LoggerFactory.getLogger(AgentVersion.class);

/**
* Checks if the given version string represents a version that is below the specified major,
* minor, and patch version.
*
* @param version the version string to check (e.g., "7.64.0")
* @param maxMajor maximum major version (exclusive)
* @param maxMinor maximum minor version (exclusive)
* @param maxPatch maximum patch version (exclusive)
* @return true if version is below the specified maximum (or if not parseable), false otherwise
*/
public static boolean isVersionBelow(String version, int maxMajor, int maxMinor, int maxPatch) {
if (version == null || version.isEmpty()) {
return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

try {
// Parse version string in format "major.minor.patch" (e.g., "7.65.0")
// Assumes the 'version' is below if it can't be parsed.
int majorDot = version.indexOf('.');
if (majorDot == -1) {
return true;
}

int major = Integer.parseInt(version.substring(0, majorDot));

if (major < maxMajor) {
return true;
} else if (major > maxMajor) {
return false;
}

// major == maxMajor
int minorDot = version.indexOf('.', majorDot + 1);
if (minorDot == -1) {
return true;
}

int minor = Integer.parseInt(version.substring(majorDot + 1, minorDot));
if (minor < maxMinor) {
return true;
} else if (minor > maxMinor) {
return false;
}

// major == maxMajor && minor == maxMinor
// Find end of patch version (may have suffix like "-rc.1")
int patchEnd = minorDot + 1;
while (patchEnd < version.length() && Character.isDigit(version.charAt(patchEnd))) {
patchEnd++;
}

int patch = Integer.parseInt(version.substring(minorDot + 1, patchEnd));
if (patch != maxPatch) {
return patch < maxPatch;
} else {
// If there's a suffix (like "-rc.1"), consider it below the non-suffixed version
return patchEnd < version.length();
}
} catch (NumberFormatException | IndexOutOfBoundsException e) {
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ private static class State {
String dataStreamsEndpoint;
boolean supportsLongRunning;
boolean supportsDropping;
boolean supportsClientSideStats;
String state;
String configEndpoint;
String debuggerLogEndpoint;
Expand Down Expand Up @@ -306,6 +307,7 @@ private boolean processInfoResponse(State newState, String response) {
Boolean.TRUE.equals(map.getOrDefault("long_running_spans", false));

if (metricsEnabled) {
newState.supportsClientSideStats = !AgentVersion.isVersionBelow(newState.version, 7, 65, 0);
Object canDrop = map.get("client_drop_p0s");
newState.supportsDropping =
null != canDrop
Expand Down Expand Up @@ -358,7 +360,8 @@ private static void discoverStatsDPort(final Map<String, Object> info) {
public boolean supportsMetrics() {
return metricsEnabled
&& null != discoveryState.metricsEndpoint
&& discoveryState.supportsDropping;
&& discoveryState.supportsDropping
&& discoveryState.supportsClientSideStats;
}

public boolean supportsDebugger() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,67 @@ class DDAgentFeaturesDiscoveryTest extends DDSpecification {
)
}

def "test metrics disabled for agent version below 7.65"() {
setup:
OkHttpClient client = Mock(OkHttpClient)
DDAgentFeaturesDiscovery features = new DDAgentFeaturesDiscovery(client, monitoring, agentUrl, true, true)

when: "agent version is below 7.65"
features.discover()

then:
1 * client.newCall(_) >> { Request request ->
def response = """
{
"version": "${version}",
"endpoints": ["/v0.5/traces", "/v0.6/stats"],
"client_drop_p0s": true,
"config": {}
}
"""
infoResponse(request, response)
}
features.getMetricsEndpoint() == V6_METRICS_ENDPOINT
features.supportsDropping() == true
features.supportsMetrics() == expected

where:
version | expected
"7.64.0" | false
"7.64.9" | false
"7.64.9-rc.1" | false
"7.65.0" | true
"7.65.1" | true
"7.70.0" | true
"8.0.0" | true
}

def "test metrics disabled for agent with unparseable version"() {
setup:
OkHttpClient client = Mock(OkHttpClient)
DDAgentFeaturesDiscovery features = new DDAgentFeaturesDiscovery(client, monitoring, agentUrl, true, true)

when: "agent version is unparseable"
features.discover()

then:
1 * client.newCall(_) >> { Request request ->
def response = """
{
"version": "${version}",
"endpoints": ["/v0.5/traces", "/v0.6/stats"],
"client_drop_p0s": true,
"config": {}
}
"""
infoResponse(request, response)
}
!features.supportsMetrics()

where:
version << ["invalid", "7", "7.65", "", null]
}

def "should send container id as header on the info request and parse the hash in the response"() {
setup:
OkHttpClient client = Mock(OkHttpClient)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package datadog.communication.ddagent;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class AgentVersionTest {

@Test
void testIsVersionBelow_VersionBelowThreshold() {
assertTrue(AgentVersion.isVersionBelow("7.64.0", 7, 65, 0));
assertTrue(AgentVersion.isVersionBelow("7.64.9", 7, 65, 0));
assertTrue(AgentVersion.isVersionBelow("6.99.99", 7, 65, 0));
assertTrue(AgentVersion.isVersionBelow("7.0.0", 7, 65, 0));
}

@Test
void testIsVersionBelow_VersionEqualToThreshold() {
assertFalse(AgentVersion.isVersionBelow("7.65.0", 7, 65, 0));
}

@Test
void testIsVersionBelow_VersionAboveThreshold() {
assertFalse(AgentVersion.isVersionBelow("7.65.1", 7, 65, 0));
assertFalse(AgentVersion.isVersionBelow("7.66.0", 7, 65, 0));
assertFalse(AgentVersion.isVersionBelow("8.0.0", 7, 65, 0));
assertFalse(AgentVersion.isVersionBelow("7.65.10", 7, 65, 0));
}

@Test
void testIsVersionBelow_MajorVersionComparison() {
assertTrue(AgentVersion.isVersionBelow("6.100.100", 7, 0, 0));
assertFalse(AgentVersion.isVersionBelow("8.0.0", 7, 100, 100));
}

@Test
void testIsVersionBelow_MinorVersionComparison() {
assertTrue(AgentVersion.isVersionBelow("7.64.100", 7, 65, 0));
assertFalse(AgentVersion.isVersionBelow("7.66.0", 7, 65, 100));
}

@Test
void testIsVersionBelow_WithSuffix() {
assertTrue(AgentVersion.isVersionBelow("7.64.0-rc.1", 7, 65, 0));
assertTrue(AgentVersion.isVersionBelow("7.65.0-rc.1", 7, 65, 0));
assertFalse(AgentVersion.isVersionBelow("7.65.1-snapshot", 7, 65, 0));
}

@Test
void testIsVersionBelow_NullOrEmpty() {
assertTrue(AgentVersion.isVersionBelow(null, 7, 65, 0));
assertTrue(AgentVersion.isVersionBelow("", 7, 65, 0));
}

@Test
void testIsVersionBelow_InvalidFormat() {
assertTrue(AgentVersion.isVersionBelow("invalid", 7, 65, 0));
assertTrue(AgentVersion.isVersionBelow("7", 7, 65, 0));
assertTrue(AgentVersion.isVersionBelow("7.", 7, 65, 0));
assertTrue(AgentVersion.isVersionBelow("7.65", 7, 65, 0));
assertTrue(AgentVersion.isVersionBelow("7.65.", 7, 65, 0));
assertTrue(AgentVersion.isVersionBelow("a.b.c", 7, 65, 0));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.99.0",
"version": "7.65.0",
"git_commit": "fab047e10",
"build_date": "2020-12-04 15:57:06.74187 +0200 EET m=+0.029001792",
"endpoints": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class MetricsReliabilityTest extends DDCoreSpecification {
httpServer {
handlers {
get("/info") {
final def res = '{"endpoints":[' + (state.agentMetricsAvailable ? '"/v0.6/stats", ' : '') + '"/v0.4/traces"], "client_drop_p0s" : true}'
final def res = '{"version":"7.65.0","endpoints":[' + (state.agentMetricsAvailable ? '"/v0.6/stats", ' : '') + '"/v0.4/traces"], "client_drop_p0s" : true}'
state.hash = Strings.sha256(res)
response.send(res)
state.latch.countDown()
Expand Down