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

feat: Linux Control Group version 2 API support cgroup v2 #1329

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
58f4ec1
feat: linux control group version 2 API support cgroup v2
ChangxinDong Oct 10, 2022
52800bd
Merge branch 'aws-greengrass:main' into main
ChangxinDong Oct 21, 2022
1abc7cc
feat: linux control group version 2 API support cgroup v2
yiwenTS Oct 25, 2022
5832fb2
feat: linux control group version 2 API support cgroup v2
ChangxinDong Oct 25, 2022
8132b86
Merge branch 'aws-greengrass:main' into main
ChangxinDong Oct 27, 2022
1b95b7d
feat: linux control group version 2 API support cgroup v2
ChangxinDong Nov 3, 2022
b8cd781
feat: linux control group version 2 API support cgroup v2
ChangxinDong Nov 4, 2022
901efe1
Merge branch 'aws-greengrass:main' into main
ChangxinDong Nov 8, 2022
58441a7
feat: linux control group version 2 API support cgroup v2
ChangxinDong Nov 8, 2022
8ef0143
feat: linux control group version 2 API support cgroup v2
ChangxinDong Nov 11, 2022
ec2348b
feat: linux control group version 2 API support cgroup v2
yiwenTS Nov 11, 2022
3db48d7
feat: linux control group version 2 API support cgroup v2
ChangxinDong Nov 11, 2022
996d87c
Merge branch 'aws-greengrass:main' into main
ChangxinDong Nov 18, 2022
c1626ce
feat: linux control group version 2 API support cgroup v2
ChangxinDong Nov 18, 2022
acfe1eb
Merge branch 'aws-greengrass:main' into main
ChangxinDong Nov 18, 2022
6004a64
feat: linux control group version 2 API support cgroup v2
ChangxinDong Nov 18, 2022
936e8eb
Merge branch 'main' of ssh://github.com/ChangxinDong/aws-greengrass-n…
ChangxinDong Nov 18, 2022
1a35f5d
Merge branch 'main' into main
junfuchen99 Aug 21, 2024
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 @@ -10,6 +10,7 @@
import com.aws.greengrass.testcommons.testutilities.GGExtension;
import com.aws.greengrass.testcommons.testutilities.TestUtils;
import com.aws.greengrass.util.platforms.unix.linux.Cgroup;
import com.aws.greengrass.util.platforms.unix.linux.CgroupSubSystem;
import com.aws.greengrass.util.platforms.unix.linux.LinuxSystemResourceController;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -115,7 +116,7 @@ void GIVEN_LifeCycleEventStreamClient_WHEN_pause_resume_component_THEN_target_se
private LinuxSystemResourceController.CgroupFreezerState getCgroupFreezerState(String serviceName)
throws IOException {
return LinuxSystemResourceController.CgroupFreezerState.valueOf(
new String(Files.readAllBytes(Cgroup.Freezer.getCgroupFreezerStateFilePath(serviceName)),
new String(Files.readAllBytes(new Cgroup(CgroupSubSystem.Freezer).getCgroupFreezerStateFilePath(serviceName)),
StandardCharsets.UTF_8).trim());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,39 @@
import com.aws.greengrass.logging.impl.GreengrassLogMessage;
import com.aws.greengrass.logging.impl.Slf4jLogAdapter;
import com.aws.greengrass.status.model.ComponentStatusDetails;
import com.aws.greengrass.testcommons.testutilities.GGExtension;
import com.aws.greengrass.testcommons.testutilities.NoOpPathOwnershipHandler;
import com.aws.greengrass.util.Pair;
import com.aws.greengrass.util.platforms.SystemResourceController;
import com.aws.greengrass.util.platforms.unix.linux.Cgroup;
import com.aws.greengrass.util.platforms.unix.linux.CgroupSubSystem;
import com.aws.greengrass.util.platforms.unix.linux.CgroupSubSystemV2;
import com.aws.greengrass.util.platforms.unix.linux.LinuxPlatform;
import com.aws.greengrass.util.platforms.unix.linux.LinuxSystemResourceController;
import com.aws.greengrass.util.platforms.unix.linux.LinuxSystemResourceControllerV2;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;

import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystemException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
Expand Down Expand Up @@ -79,11 +92,18 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;


@ExtendWith({GGExtension.class, MockitoExtension.class})
class GenericExternalServiceIntegTest extends BaseITCase {

private Kernel kernel;

@Spy
LinuxPlatform linuxPlatform;

private final static String ROOT_PATH_STRING = "/systest21/fs/cgroup";
private final static String GG_PATH_STRING = "greengrass";

SystemResourceController systemResourceController;

static Stream<Arguments> posixTestUserConfig() {
return Stream.of(
arguments("config_run_with_user.yaml", "nobody", "nobody"),
Expand Down Expand Up @@ -620,6 +640,84 @@ void GIVEN_linux_resource_limits_WHEN_it_changes_THEN_component_runs_with_new_re
assertResourceLimits(componentName, 10240l * 1024, 1.5);
}

@EnabledOnOs({OS.LINUX})
@Test
void GIVEN_linux_resource_limits_WHEN_it_changes_THEN_component_runs_with_new_resource_limits_V2() throws Exception {
String echoComponentName = "echo_service";
String mainComponentName = "main";
String rootGGPathString = ROOT_PATH_STRING + "/" + GG_PATH_STRING;
String componentPathString = rootGGPathString + "/" + echoComponentName;
// Run with no resource limit
ConfigPlatformResolver.initKernelWithMultiPlatformConfig(kernel,
getClass().getResource("config_run_with_user.yaml"));
CountDownLatch service = new CountDownLatch(1);
kernel.getContext().addGlobalStateChangeListener((s, oldState, newState) -> {
if (s.getName().equals(echoComponentName) && newState.equals(State.RUNNING)) {
service.countDown();
}
});

linuxPlatform = spy(kernel.getContext().get(LinuxPlatform.class));

createComponentData(echoComponentName);
createComponentData(mainComponentName);

// Due to cgroup v1 is active by default (in test platform), and the directories of cgroup v1 are read-only
// therefore, here create some directories and files as fake cgroup v2 files to support testing
Field controllerField = LinuxPlatform.class.getDeclaredField("CGROUP_CONTROLLERS");
setFinalStatic(controllerField, Paths.get(componentPathString + "/memory.max"));
systemResourceController = linuxPlatform.getSystemResourceController();
LinuxSystemResourceControllerV2 controllerV2 = (LinuxSystemResourceControllerV2) systemResourceController;
Field memoryCgroupField = LinuxSystemResourceControllerV2.class.getSuperclass().getDeclaredField("memoryCgroup");
memoryCgroupField.setAccessible(true);
Cgroup memoryCgroup = (Cgroup) memoryCgroupField.get(controllerV2);
Field subsystem = memoryCgroup.getClass().getDeclaredField("subSystem");
subsystem.setAccessible(true);
CgroupSubSystemV2 cg = (CgroupSubSystemV2) subsystem.get(memoryCgroup);
Field f = cg.getClass().getInterfaces()[0].getDeclaredField("CGROUP_ROOT");
setFinalStatic(f, Paths.get(ROOT_PATH_STRING));
ChangxinDong marked this conversation as resolved.
Show resolved Hide resolved

Field mountsField = LinuxSystemResourceControllerV2.class.getSuperclass().getDeclaredField("MOUNT_PATH");
mountsField.setAccessible(true);
String mountPathFile = rootGGPathString + "/mountPath.txt";
final Path mountPathFilePath = Paths.get(mountPathFile);
if (!Files.exists(mountPathFilePath)) {
Files.createFile(mountPathFilePath);
Files.write(mountPathFilePath, String.format("test1 %s test2 test3 test4 test5", ROOT_PATH_STRING).getBytes(StandardCharsets.UTF_8));

}
setFinalStatic(mountsField, mountPathFile);

kernel.launch();
assertResourceLimits_V2(10240l * 1024, 1.5);

// Run with updated component resource limit
kernel.getConfig().lookup(SERVICES_NAMESPACE_TOPIC, echoComponentName, RUN_WITH_NAMESPACE_TOPIC,
SYSTEM_RESOURCE_LIMITS_TOPICS, "memory").withValue(51200l);
kernel.getConfig().lookup(SERVICES_NAMESPACE_TOPIC, echoComponentName, RUN_WITH_NAMESPACE_TOPIC,
SYSTEM_RESOURCE_LIMITS_TOPICS, "cpus").withValue(0.35);
kernel.getConfig().lookup(SERVICES_NAMESPACE_TOPIC, echoComponentName, VERSION_CONFIG_KEY).withValue("2.0.0");
// Block until events are completed
kernel.getContext().waitForPublishQueueToClear();

assertResourceLimits_V2(51200l * 1024, 0.35);

//Remove component resource limit, should fall back to default
kernel.getConfig().lookupTopics(SERVICES_NAMESPACE_TOPIC, echoComponentName, RUN_WITH_NAMESPACE_TOPIC,
SYSTEM_RESOURCE_LIMITS_TOPICS).remove();
kernel.getContext().waitForPublishQueueToClear();

assertResourceLimits_V2(10240l * 1024, 1.5);

FileUtils.deleteDirectory(Paths.get("/systest21").toFile());
}

private void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
FieldUtils.removeFinalModifier(field, true);
field.set(null, newValue);
}

@Test
void GIVEN_service_starts_up_WHEN_service_breaks_THEN_status_details_persisted_for_errored_and_broken_states()
throws Exception {
Expand Down Expand Up @@ -751,18 +849,50 @@ void GIVEN_service_starts_up_WHEN_startup_times_out_THEN_timeout_error_code_pers
}

private void assertResourceLimits(String componentName, long memory, double cpus) throws Exception {
byte[] buf1 = Files.readAllBytes(Cgroup.Memory.getComponentMemoryLimitPath(componentName));
byte[] buf1 = Files.readAllBytes(new Cgroup(CgroupSubSystem.Memory).getComponentMemoryLimitPath(componentName));
assertThat(memory, equalTo(Long.parseLong(new String(buf1, StandardCharsets.UTF_8).trim())));

byte[] buf2 = Files.readAllBytes(Cgroup.CPU.getComponentCpuQuotaPath(componentName));
byte[] buf3 = Files.readAllBytes(Cgroup.CPU.getComponentCpuPeriodPath(componentName));
byte[] buf2 = Files.readAllBytes(new Cgroup(CgroupSubSystem.CPU).getComponentCpuQuotaPath(componentName));
byte[] buf3 = Files.readAllBytes(new Cgroup(CgroupSubSystem.CPU).getComponentCpuPeriodPath(componentName));

int quota = Integer.parseInt(new String(buf2, StandardCharsets.UTF_8).trim());
int period = Integer.parseInt(new String(buf3, StandardCharsets.UTF_8).trim());
int expectedQuota = (int) (cpus * period);
assertThat(expectedQuota, equalTo(quota));
}

private void createComponentData(String componentName) throws IOException {
Path path = Paths.get(ROOT_PATH_STRING).resolve(GG_PATH_STRING).resolve(componentName);
Files.createDirectories(path);

if (!Files.exists(path.resolve("memory.max"))) {
Files.createFile(path.resolve("memory.max"));
}
if (!Files.exists(path.resolve("cpu.max"))) {
Files.createFile(path.resolve("cpu.max"));
}
Files.write(path.resolve("cpu.max"), "max 100000".getBytes(StandardCharsets.UTF_8));
if (!Files.exists(path.resolve("cgroup.procs"))) {
Files.createFile(path.resolve("cgroup.procs"));
}
}

private void assertResourceLimits_V2(long memory, double cpus) throws Exception {
ChangxinDong marked this conversation as resolved.
Show resolved Hide resolved
byte[] buf1 = Files.readAllBytes(Paths.get(String.format("%s/%s/echo_service/memory.max", ROOT_PATH_STRING, GG_PATH_STRING)));
assertThat(memory, equalTo(Long.parseLong(new String(buf1, StandardCharsets.UTF_8).trim())));

byte[] buf2 = Files.readAllBytes(Paths.get(String.format("%s/%s/echo_service/cpu.max", ROOT_PATH_STRING, GG_PATH_STRING)));

String cpuMaxContent = new String(buf2, StandardCharsets.UTF_8).trim();
String[] cpuMaxContentArr = cpuMaxContent.split(" ");

String cpuMaxStr = cpuMaxContentArr[0];
String cpuPeriodStr = cpuMaxContentArr[1];
int quota = Integer.parseInt(cpuMaxStr);
int expectedQuota = (int) (cpus * Integer.parseInt(cpuPeriodStr));
assertThat(expectedQuota, equalTo(quota));
}

void GIVEN_running_service_WHEN_pause_resume_requested_THEN_pause_resume_Service_and_freeze_thaw_cgroup(
ExtensionContext context) throws Exception {
ignoreExceptionOfType(context, FileSystemException.class);
Expand Down Expand Up @@ -799,7 +929,7 @@ void GIVEN_running_service_WHEN_pause_resume_requested_THEN_pause_resume_Service
private LinuxSystemResourceController.CgroupFreezerState getCgroupFreezerState(String serviceName)
throws IOException {
return LinuxSystemResourceController.CgroupFreezerState
.valueOf(new String(Files.readAllBytes(Cgroup.Freezer.getCgroupFreezerStateFilePath(serviceName))
.valueOf(new String(Files.readAllBytes(new Cgroup(CgroupSubSystem.Freezer).getCgroupFreezerStateFilePath(serviceName))
, StandardCharsets.UTF_8).trim());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package com.aws.greengrass.util.platforms.unix.linux;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import java.nio.file.Path;
import java.nio.file.Paths;

@SuppressFBWarnings(value = "DMI_HARDCODED_ABSOLUTE_FILENAME",
justification = "CGroupSubSystemPath virtual filesystem path cannot be relative")
public interface CGroupSubSystemPath {
ChangxinDong marked this conversation as resolved.
Show resolved Hide resolved
Path CGROUP_ROOT = Paths.get("/sys/fs/cgroup");
String GG_NAMESPACE = "greengrass";
String CGROUP_MEMORY_LIMITS = "memory.limit_in_bytes";
String CPU_CFS_PERIOD_US = "cpu.cfs_period_us";
String CPU_CFS_QUOTA_US = "cpu.cfs_quota_us";
String CGROUP_PROCS = "cgroup.procs";
String FREEZER_STATE_FILE = "freezer.state";
String CPU_MAX = "cpu.max";
String MEMORY_MAX = "memory.max";
String CGROUP_SUBTREE_CONTROL = "cgroup.subtree_control";
String CGROUP_FREEZE = "cgroup.freeze";

default Path getRootPath() {
return CGROUP_ROOT;
}

String rootMountCmd();

default String subsystemMountCmd() {
return null;
}

Path getSubsystemRootPath();

Path getSubsystemGGPath();

Path getSubsystemComponentPath(String componentName);

Path getComponentMemoryLimitPath(String componentName);

default Path getComponentCpuPeriodPath(String componentName) {
return null;
}

default Path getComponentCpuQuotaPath(String componentName) {
return null;
}

Path getCgroupProcsPath(String componentName);

Path getCgroupFreezerStateFilePath(String componentName);

default Path getRootSubTreeControlPath() {
return null;
}

default Path getGGSubTreeControlPath() {
return null;
}

default Path getComponentCpuMaxPath(String componentName) {
return null;
}

default Path getCgroupFreezePath(String componentName) {
return null;
}
}
Loading