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

ECS Fargate Docker id updates #2050

Merged
merged 1 commit into from
Sep 25, 2024
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 @@ -9,6 +9,7 @@

import com.google.common.annotations.VisibleForTesting;
import com.newrelic.agent.Agent;
import com.newrelic.agent.config.internal.SystemEnvironmentFacade;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
Expand Down Expand Up @@ -39,9 +40,8 @@
* We should grab the "cpu" line. The long id number is the number we want.
*
* For AWS ECS (fargate and non-fargate) we check the metadata returned from the URL defined in either the
* v3 or v4 metadata URL. These checks are only made if the cgroup files don't return anything and the
* metadata URL(s) are present in the target env variables. The docker id returned in the metadata JSON response
* is a 32-digit hex followed by a 10-digit number in the "DockerId" key.
* v3 or v4 metadata URL. These checks are only made if the metadata URL(s) are present in the target env variables.
* The docker id returned in the metadata JSON response is a 32-digit hex followed by a 10-digit number in the "DockerId" key.
*
* In either case, this is the full docker id, not the short id that appears when you run a "docker ps".
*/
Expand All @@ -51,44 +51,33 @@ public class DockerData {
private static final String FILE_WITH_CONTAINER_ID_V2 = "/proc/self/mountinfo";
private static final String CPU = "cpu";

private static final String AWS_ECS_METADATA_V3_ENV_VAR = "ECS_CONTAINER_METADATA_URI";
private static final String AWS_ECS_METADATA_UNVERSIONED_ENV_VAR = "ECS_CONTAINER_METADATA_URI";
private static final String AWS_ECS_METADATA_V4_ENV_VAR = "ECS_CONTAINER_METADATA_URI_V4";
private static final String FARGATE_DOCKER_ID_KEY = "DockerId";

private static final Pattern VALID_CONTAINER_ID = Pattern.compile("^[0-9a-f]{64}$");
private static final Pattern DOCKER_CONTAINER_STRING_V1 = Pattern.compile("^.*[^0-9a-f]+([0-9a-f]{64,}).*");
private static final Pattern DOCKER_CONTAINER_STRING_V2 = Pattern.compile(".*/docker/containers/([0-9a-f]{64,}).*");

public String getDockerContainerId(boolean isLinux) {
public String getDockerContainerIdForEcsFargate(boolean isLinux) {
if (isLinux) {
String result;
//try to get the container id from the v2 location
File containerIdFileV2 = new File(FILE_WITH_CONTAINER_ID_V2);
result = getDockerIdFromFile(containerIdFileV2, CGroup.V2);
if (result != null) {
return result;
}

//try to get container id from the v1 location
File containerIdFileV1 = new File(FILE_WITH_CONTAINER_ID_V1);
result = getDockerIdFromFile(containerIdFileV1, CGroup.V1);
if (result != null) {
return result;
}

// Try v4 ESC Fargate metadata call, then finally v3
// Try v4 ESC Fargate metadata call, then fallback to the un-versioned call
String fargateUrl = null;
try {
fargateUrl = System.getenv(AWS_ECS_METADATA_V4_ENV_VAR);
if (fargateUrl != null) {
Agent.LOG.log(Level.INFO, "Attempting to fetch ECS Fargate container id from URL (v4): {0}", fargateUrl);
result = retrieveDockerIdFromFargateMetadata(new AwsFargateMetadataFetcher(fargateUrl));
if (result != null) {
return result;
}
}

fargateUrl = System.getenv(AWS_ECS_METADATA_V3_ENV_VAR);
fargateUrl = System.getenv(AWS_ECS_METADATA_UNVERSIONED_ENV_VAR);
if (fargateUrl != null) {
Agent.LOG.log(Level.INFO, "Attempting to fetch ECS Fargate container id from URL (unversioned): {0}", fargateUrl);
return retrieveDockerIdFromFargateMetadata(new AwsFargateMetadataFetcher(fargateUrl));
}
} catch (MalformedURLException e) {
Expand All @@ -99,6 +88,27 @@ public String getDockerContainerId(boolean isLinux) {
return null;
}

public String getDockerContainerIdFromCGroups(boolean isLinux) {
if (isLinux) {
String result;
//try to get the container id from the v2 location
File containerIdFileV2 = new File(FILE_WITH_CONTAINER_ID_V2);
result = getDockerIdFromFile(containerIdFileV2, CGroup.V2);
if (result != null) {
return result;
}

//try to get container id from the v1 location
File containerIdFileV1 = new File(FILE_WITH_CONTAINER_ID_V1);
result = getDockerIdFromFile(containerIdFileV1, CGroup.V1);
if (result != null) {
return result;
}
}

return null;
}

String getDockerIdFromFile(File mountInfoFile, CGroup cgroup) {
if (mountInfoFile.exists() && mountInfoFile.canRead()) {
try {
Expand Down Expand Up @@ -197,7 +207,7 @@ private boolean checkAndGetMatch(Pattern p, StringBuilder result, String segment
@VisibleForTesting
String retrieveDockerIdFromFargateMetadata(AwsFargateMetadataFetcher awsFargateMetadataFetcher) {
String dockerId = null;
StringBuffer jsonBlob = new StringBuffer();
StringBuilder jsonBlob = new StringBuilder();

try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(awsFargateMetadataFetcher.openStream()))) {
Expand All @@ -209,10 +219,11 @@ String retrieveDockerIdFromFargateMetadata(AwsFargateMetadataFetcher awsFargateM

JSONObject jsonObject = (JSONObject) new JSONParser().parse(jsonBlob.toString());
dockerId = (String) jsonObject.get(FARGATE_DOCKER_ID_KEY);
Agent.LOG.log(Level.INFO, "ECS Fargate container id: {0} ", dockerId);
} catch (IOException e) {
Agent.LOG.log(Level.FINEST, "Error opening input stream retrieving AWS Fargate metadata");
Agent.LOG.log(Level.WARNING, "Error opening input stream retrieving AWS Fargate metadata");
} catch (ParseException e) {
Agent.LOG.log(Level.FINEST, "Error parsing JSON blob for AWS Fargate metadata");
Agent.LOG.log(Level.WARNING, "Error parsing JSON blob for AWS Fargate metadata");
}

return dockerId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ public class UtilizationData {
private static final String VENDORS_KEY = "vendors";
private static final String BOOT_ID = "boot_id";
private static final String DOCKER = "docker";
private static final String ECS = "ecs";
private static final String DOCKER_ID_KEY = "id";
private static final String ECS_ID_KEY = "ecsDockerId";
private static final String CONFIG_KEY = "config";
private static final String KUBERNETES = "kubernetes";

Expand All @@ -39,23 +41,26 @@ public class UtilizationData {
private final ArrayList<String> ipAddress;
private final Integer logicalProcessorCount;
private final String dockerContainerId;
private final String ecsFargateDockerContainerId;
private final String bootId;
private final Long totalRamMib;
private final UtilizationConfig dataConfig;
private final KubernetesData kubernetesData;

public UtilizationData(String host, String fullHost, ArrayList<String> ip, Integer logicalProcessorCt, String dockerId, String bootId,
CloudData cloudData, Future<Long> ramFuture, UtilizationConfig configData, KubernetesData kubernetesData) {
this(host, fullHost, ip, logicalProcessorCt, dockerId, bootId, cloudData, getTotalRamMibFromFuture(ramFuture), configData, kubernetesData);
public UtilizationData(String host, String fullHost, ArrayList<String> ip, Integer logicalProcessorCt, String dockerId, String ecsFargateDockerContainerId,
String bootId, CloudData cloudData, Future<Long> ramFuture, UtilizationConfig configData, KubernetesData kubernetesData) {
this(host, fullHost, ip, logicalProcessorCt, dockerId, ecsFargateDockerContainerId, bootId, cloudData,
getTotalRamMibFromFuture(ramFuture), configData, kubernetesData);
}

public UtilizationData(String host, String fullHost, ArrayList<String> ip, Integer logicalProcessorCt, String dockerId, String bootId,
CloudData cloudData, Long ram, UtilizationConfig configData, KubernetesData kubernetesData) {
public UtilizationData(String host, String fullHost, ArrayList<String> ip, Integer logicalProcessorCt, String dockerId, String ecsFargateDockerContainerId,
String bootId, CloudData cloudData, Long ram, UtilizationConfig configData, KubernetesData kubernetesData) {
this.hostname = host;
this.fullHostName = fullHost;
this.ipAddress = ip;
this.logicalProcessorCount = Integer.valueOf(0).equals(logicalProcessorCt) ? null : logicalProcessorCt;
this.dockerContainerId = dockerId;
this.ecsFargateDockerContainerId = ecsFargateDockerContainerId;
this.bootId = bootId;
this.cloudData = cloudData;
this.totalRamMib = Long.valueOf(0).equals(ram) ? null : ram;
Expand Down Expand Up @@ -116,6 +121,12 @@ public Map<String, Object> map() {
vendors.put(DOCKER, docker);
}

if (ecsFargateDockerContainerId != null) {
Map<String, String> ecs = new HashMap<>();
ecs.put(ECS_ID_KEY, ecsFargateDockerContainerId);
vendors.put(ECS, ecs);
}

if (!vendors.isEmpty()) {
data.put(VENDORS_KEY, vendors);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class UtilizationService extends AbstractService {
private final ArrayList<String> ipAddress;
private final String bootId;
private final String dockerContainerId;
private final String ecsFargateDockerContainerId;
private final int processorCount;
private final Future<Long> totalRamInMibFuture;
private final UtilizationConfig configData;
Expand Down Expand Up @@ -93,12 +94,13 @@ public UtilizationService() {
isLinux = isLinuxOs();
bootId = DataFetcher.getBootId();
dockerContainerId = detectDocker ? getDockerContainerId() : null;
ecsFargateDockerContainerId = detectAws ? getEcsFargateDockerContainerId() : null;
processorCount = DataFetcher.getLogicalProcessorCount();
totalRamInMibFuture = executor.submit(DataFetcher.getTotalRamInMibCallable());
configData = UtilizationConfig.createFromConfigService();
kubernetesData = getKubernetesData();
utilizationData = new UtilizationData(hostName, fullHostName, ipAddress, processorCount, dockerContainerId, bootId, null, totalRamInMibFuture,
configData, kubernetesData);
utilizationData = new UtilizationData(hostName, fullHostName, ipAddress, processorCount, dockerContainerId, ecsFargateDockerContainerId,
bootId, null, totalRamInMibFuture, configData, kubernetesData);
}

@Override
Expand Down Expand Up @@ -190,7 +192,11 @@ KubernetesData getKubernetesData() {
* Do not call DockerData.getDockerContainerId(boolean, String) directly, call this method instead.
*/
String getDockerContainerId() {
return getDockerData().getDockerContainerId(isLinux);
return getDockerData().getDockerContainerIdFromCGroups(isLinux);
}

String getEcsFargateDockerContainerId() {
return getDockerData().getDockerContainerIdForEcsFargate(isLinux);
}

class UtilizationTask implements Callable<UtilizationData> {
Expand Down Expand Up @@ -227,8 +233,8 @@ private UtilizationData doUpdateUtilizationData() {
}
}

return new UtilizationData(hostName, fullHostName, ipAddress, processorCount, dockerContainerId, bootId, foundData, totalRamInMibFuture, configData,
kubernetesData);
return new UtilizationData(hostName, fullHostName, ipAddress, processorCount, dockerContainerId, ecsFargateDockerContainerId, bootId,
foundData, totalRamInMibFuture, configData, kubernetesData);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class DockerDataTest {

@Test
public void testGetDockerIdNotLinux() {
Assert.assertNull(dockerData.getDockerContainerId(false));
Assert.assertNull(dockerData.getDockerContainerIdFromCGroups(false));
}
@Test
public void testCheckLineAndGetIdValidV2() {
Expand Down Expand Up @@ -367,7 +367,7 @@ public void retrieveDockerIdFromFargateMetadata_withInputStreamException_returns

@Test
public void getDockerContainerId_withNoDockerIdSource_returnsNull() {
Assert.assertNull(dockerData.getDockerContainerId(true));
Assert.assertNull(dockerData.getDockerContainerIdFromCGroups(true));
}

private void processFile(File file, String answer, CGroup cgroup) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ private void runTest(JSONObject jsonTest, String type) throws ParseException {

// no cross-agent tests for docker yet.
String containerId = null;
String ecsFargateDockerId = null;

CloudData data = null;

Expand Down Expand Up @@ -210,8 +211,8 @@ private void runTest(JSONObject jsonTest, String type) throws ParseException {
addresses.addAll(ipAddress);
}

UtilizationData utilizationData = new UtilizationData(hostname, fullHostname, addresses, logical_processors, containerId, boot_id, data, total_ram_mib,
UtilizationConfig.createFromConfigService(), KubernetesData.extractKubernetesValues(systemPropertyProvider));
UtilizationData utilizationData = new UtilizationData(hostname, fullHostname, addresses, logical_processors, containerId, ecsFargateDockerId,
boot_id, data, total_ram_mib, UtilizationConfig.createFromConfigService(), KubernetesData.extractKubernetesValues(systemPropertyProvider));
Assert.assertEquals("cross agent test '" + testname + "' failed.", expectedOutput, toJSONObject(utilizationData.map()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static void before() throws Exception {
@Test
public void testUtilizationDataVersion() {
UtilizationConfig utilConfig = new UtilizationConfig(null, null, null);
UtilizationData data = new UtilizationData(null, null, null, 0, null, invalidBootId, awsCloudData, 0L, utilConfig,
UtilizationData data = new UtilizationData(null, null, null, 0, null, null, invalidBootId, awsCloudData, 0L, utilConfig,
KubernetesData.EMPTY_KUBERNETES_DATA);
Map<String, Object> map = data.map();
Assert.assertTrue(map.containsKey("metadata_version"));
Expand All @@ -39,7 +39,7 @@ public void testUtilizationDataVersion() {

@Test
public void testUtilizationKeysInvalidBootId() {
UtilizationData data = new UtilizationData(null, null, null, 0, null, invalidBootId, awsCloudData, 0L, UtilizationConfig.EMPTY_DATA,
UtilizationData data = new UtilizationData(null, null, null, 0, null, null, invalidBootId, awsCloudData, 0L, UtilizationConfig.EMPTY_DATA,
KubernetesData.EMPTY_KUBERNETES_DATA);
Map<String, Object> map = data.map();
Assert.assertTrue(map.containsKey("metadata_version"));
Expand All @@ -51,7 +51,7 @@ public void testUtilizationKeysInvalidBootId() {

@Test
public void testUtilizationKeysValidBootId() {
UtilizationData data = new UtilizationData(null, null, null, 0, null, validBootId, awsCloudData, 0L, UtilizationConfig.EMPTY_DATA,
UtilizationData data = new UtilizationData(null, null, null, 0, null, null, validBootId, awsCloudData, 0L, UtilizationConfig.EMPTY_DATA,
KubernetesData.EMPTY_KUBERNETES_DATA);
Map<String, Object> map = data.map();
Assert.assertTrue(map.containsKey("metadata_version"));
Expand All @@ -64,7 +64,7 @@ public void testUtilizationKeysValidBootId() {
@Test
public void testUtilizationKeysValidEntityIdentity() {
UtilizationData data = new UtilizationData("newrelic", "newrelic.com", new ArrayList<>(Arrays.asList("1.2.3.4")),
0, null, validBootId, awsCloudData, 0L, UtilizationConfig.EMPTY_DATA, KubernetesData.EMPTY_KUBERNETES_DATA);
0, null, null, validBootId, awsCloudData, 0L, UtilizationConfig.EMPTY_DATA, KubernetesData.EMPTY_KUBERNETES_DATA);
Map<String, Object> map = data.map();
Assert.assertTrue(map.containsKey("metadata_version"));
Assert.assertTrue(map.containsKey("logical_processors"));
Expand All @@ -78,7 +78,7 @@ public void testUtilizationKeysValidEntityIdentity() {
@Test
public void testUtilizationKeysInValidEntityIdentity() {
UtilizationData data = new UtilizationData("newrelic", "newrelic", new ArrayList<>(Arrays.asList("1.2.3.4")),
0, null, validBootId, awsCloudData, 0L, UtilizationConfig.EMPTY_DATA, KubernetesData.EMPTY_KUBERNETES_DATA);
0, null, null, validBootId, awsCloudData, 0L, UtilizationConfig.EMPTY_DATA, KubernetesData.EMPTY_KUBERNETES_DATA);
Map<String, Object> map = data.map();
Assert.assertTrue(map.containsKey("hostname"));
Assert.assertFalse(map.containsKey("full_hostname"));
Expand All @@ -87,10 +87,38 @@ public void testUtilizationKeysInValidEntityIdentity() {

@Test
public void testNoData() {
UtilizationData data = new UtilizationData(null, null, null, 0, null, invalidBootId, awsCloudData, 0L, UtilizationConfig.EMPTY_DATA,
UtilizationData data = new UtilizationData(null, null, null, 0, null, null, invalidBootId, awsCloudData, 0L, UtilizationConfig.EMPTY_DATA,
KubernetesData.EMPTY_KUBERNETES_DATA);
Map<String, Object> map = data.map();
Assert.assertNull(map.get("total_ram_mib"));
Assert.assertNull(map.get("logical_processors"));
}

@Test
public void map_producesCorrectJson_withEcsFargateContainerId() {
UtilizationData data = new UtilizationData(null, null, null, 0, null, "ecs1234567890", invalidBootId, awsCloudData, 0L, UtilizationConfig.EMPTY_DATA,
KubernetesData.EMPTY_KUBERNETES_DATA);
Map<String, Object> map = data.map();
Map<String, Object> vendorMap = (Map<String, Object>) map.get("vendors");
Map<String, Object> ecsMap = (Map<String, Object>) vendorMap.get("ecs");
String id = (String) ecsMap.get("ecsDockerId");

Assert.assertEquals("ecs1234567890", id);
}

@Test
public void map_producesCorrectJson_withBothDockerIdandEcsFargateContainerId() {
UtilizationData data = new UtilizationData(null, null, null, 0, "dockerid09876", "ecs1234567890", invalidBootId, awsCloudData, 0L, UtilizationConfig.EMPTY_DATA,
KubernetesData.EMPTY_KUBERNETES_DATA);
Map<String, Object> map = data.map();
Map<String, Object> vendorMap = (Map<String, Object>) map.get("vendors");
Map<String, Object> ecsMap = (Map<String, Object>) vendorMap.get("ecs");
String ecsId = (String) ecsMap.get("ecsDockerId");

Map<String, Object> dockerMap = (Map<String, Object>) vendorMap.get("docker");
String dockerId = (String) dockerMap.get("id");

Assert.assertEquals("ecs1234567890", ecsId);
Assert.assertEquals("dockerid09876", dockerId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public void testDockerCheckDockerConfigEnabled() {

UtilizationService utilizationService = Mockito.spy(new UtilizationService());
DockerData dockerData = mock(DockerData.class);
when(dockerData.getDockerContainerId(false)).thenReturn("f96c541a87e1376f25461f1386cb60208cea35750eac1e24e11566f078715920");
when(dockerData.getDockerContainerIdFromCGroups(false)).thenReturn("f96c541a87e1376f25461f1386cb60208cea35750eac1e24e11566f078715920");
when(utilizationService.getDockerData()).thenReturn(dockerData);
UtilizationData oldData = utilizationService.utilizationData;
UtilizationData newData = utilizationService.updateUtilizationData();
Expand Down
Loading