Skip to content

Commit 86a3562

Browse files
authored
feat: add linux cgroup v2 support (#1733)
1 parent af1a176 commit 86a3562

File tree

10 files changed

+954
-254
lines changed

10 files changed

+954
-254
lines changed

src/integrationtests/java/com/aws/greengrass/integrationtests/deployment/DeploymentServiceIntegrationTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@
3434
import com.aws.greengrass.util.Coerce;
3535
import com.fasterxml.jackson.databind.DeserializationFeature;
3636
import com.fasterxml.jackson.databind.ObjectMapper;
37+
import org.apache.commons.lang3.SystemUtils;
3738
import org.hamcrest.collection.IsMapContaining;
3839
import org.junit.jupiter.api.AfterEach;
3940
import org.junit.jupiter.api.BeforeEach;
40-
import org.junit.jupiter.api.Disabled;
4141
import org.junit.jupiter.api.Test;
4242
import org.junit.jupiter.api.condition.EnabledOnOs;
4343
import org.junit.jupiter.api.condition.OS;
@@ -85,6 +85,7 @@
8585
import static org.hamcrest.Matchers.equalTo;
8686
import static org.junit.jupiter.api.Assertions.assertEquals;
8787
import static org.junit.jupiter.api.Assertions.assertTrue;
88+
import static org.junit.jupiter.api.Assumptions.assumeTrue;
8889
import static org.mockito.ArgumentMatchers.any;
8990
import static org.mockito.Mockito.when;
9091

@@ -325,10 +326,10 @@ void GIVEN_deployment_with_large_config_WHEN_receives_deployment_THEN_deployment
325326
assertThat(resultConfig, IsMapContaining.hasEntry("willBeNullKey", null));
326327
}
327328

328-
@Disabled
329329
@Test
330330
@EnabledOnOs(OS.LINUX)
331331
void GIVEN_deployment_with_system_resource_WHEN_receives_deployment_THEN_deployment_succeeds() throws Exception {
332+
assumeTrue("root".equals(SystemUtils.USER_NAME), "Test requires root access for cgroup management");
332333
CountDownLatch deploymentFinished = new CountDownLatch(1);
333334
Consumer<GreengrassLogMessage> listener = m -> {
334335
if (m.getMessage() != null) {

src/integrationtests/java/com/aws/greengrass/integrationtests/deployment/DeploymentTaskIntegrationTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@
4646
import com.fasterxml.jackson.databind.MapperFeature;
4747
import com.fasterxml.jackson.databind.ObjectMapper;
4848
import com.vdurmont.semver4j.Semver;
49+
import org.apache.commons.lang3.SystemUtils;
4950
import org.hamcrest.Matchers;
5051
import org.hamcrest.collection.IsMapContaining;
5152
import org.hamcrest.collection.IsMapWithSize;
5253
import org.junit.jupiter.api.AfterAll;
5354
import org.junit.jupiter.api.AfterEach;
5455
import org.junit.jupiter.api.BeforeAll;
5556
import org.junit.jupiter.api.BeforeEach;
56-
import org.junit.jupiter.api.Disabled;
5757
import org.junit.jupiter.api.MethodOrderer;
5858
import org.junit.jupiter.api.Order;
5959
import org.junit.jupiter.api.Test;
@@ -126,6 +126,7 @@
126126
import static org.junit.jupiter.api.Assertions.assertThrows;
127127
import static org.junit.jupiter.api.Assertions.assertTrue;
128128
import static org.junit.jupiter.api.Assertions.fail;
129+
import static org.junit.jupiter.api.Assumptions.assumeTrue;
129130

130131
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
131132
@SuppressWarnings("PMD.ExcessiveClassLength")
@@ -871,10 +872,10 @@ void GIVEN_services_running_WHEN_service_added_and_deleted_THEN_add_remove_servi
871872
* Start a service running with a user, then deploy an update to change the user and ensure the correct user stops
872873
* the process and starts the new one.
873874
*/
874-
@Disabled
875875
@Test
876876
@Order(9) // deploy before tests that break services
877877
void GIVEN_a_deployment_with_runwith_config_WHEN_submitted_THEN_runwith_updated() throws Exception {
878+
assumeTrue("root".equals(SystemUtils.USER_NAME), "Test requires root access for cgroup management");
878879
((Map) kernel.getContext().getvIfExists(Kernel.SERVICE_TYPE_TO_CLASS_MAP_KEY).get())
879880
.put("plugin", GreengrassService.class.getName());
880881

src/integrationtests/java/com/aws/greengrass/integrationtests/ipc/IPCHibernateTest.java

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,41 @@
99
import com.aws.greengrass.lifecyclemanager.Kernel;
1010
import com.aws.greengrass.testcommons.testutilities.GGExtension;
1111
import com.aws.greengrass.testcommons.testutilities.TestUtils;
12-
import com.aws.greengrass.util.platforms.unix.linux.Cgroup;
13-
import com.aws.greengrass.util.platforms.unix.linux.LinuxSystemResourceController;
12+
import com.aws.greengrass.util.platforms.unix.linux.CgroupManager;
13+
import com.aws.greengrass.util.platforms.unix.linux.CgroupV1;
14+
import com.aws.greengrass.util.platforms.unix.linux.CgroupV2;
15+
import org.apache.commons.lang3.SystemUtils;
1416
import org.junit.jupiter.api.AfterEach;
1517
import org.junit.jupiter.api.BeforeEach;
16-
import org.junit.jupiter.api.Disabled;
1718
import org.junit.jupiter.api.Test;
1819
import org.junit.jupiter.api.condition.EnabledOnOs;
1920
import org.junit.jupiter.api.condition.OS;
2021
import org.junit.jupiter.api.extension.ExtendWith;
2122
import org.junit.jupiter.api.extension.ExtensionContext;
2223
import org.junit.jupiter.api.io.TempDir;
24+
import org.mockito.junit.jupiter.MockitoExtension;
2325
import software.amazon.awssdk.aws.greengrass.GreengrassCoreIPCClient;
2426
import software.amazon.awssdk.aws.greengrass.model.PauseComponentRequest;
2527
import software.amazon.awssdk.aws.greengrass.model.ResumeComponentRequest;
2628
import software.amazon.awssdk.crt.io.SocketOptions;
2729
import software.amazon.awssdk.eventstreamrpc.EventStreamRPCConnection;
2830

29-
import java.io.IOException;
30-
import java.nio.charset.StandardCharsets;
3131
import java.nio.file.FileSystemException;
3232
import java.nio.file.Files;
3333
import java.nio.file.Path;
34+
import java.nio.file.Paths;
3435
import java.util.Optional;
3536
import java.util.concurrent.TimeUnit;
3637

3738
import static com.aws.greengrass.integrationtests.ipc.IPCTestUtils.prepareKernelFromConfigFile;
3839
import static com.aws.greengrass.testcommons.testutilities.ExceptionLogProtector.ignoreExceptionOfType;
3940
import static com.aws.greengrass.testcommons.testutilities.ExceptionLogProtector.ignoreExceptionUltimateCauseWithMessage;
4041
import static com.aws.greengrass.testcommons.testutilities.ExceptionLogProtector.ignoreExceptionWithMessage;
41-
import static org.hamcrest.MatcherAssert.assertThat;
42-
import static org.hamcrest.Matchers.anyOf;
43-
import static org.hamcrest.Matchers.is;
4442
import static org.junit.jupiter.api.Assertions.assertFalse;
4543
import static org.junit.jupiter.api.Assertions.assertTrue;
44+
import static org.junit.jupiter.api.Assumptions.assumeTrue;
4645

47-
@ExtendWith({GGExtension.class})
46+
@ExtendWith({GGExtension.class, MockitoExtension.class})
4847
class IPCHibernateTest {
4948
private static final String TARGET_COMPONENT_NAME = "HibernateTarget";
5049
private static final String CONTROLLER_COMPONENT_NAME = "HibernateController";
@@ -88,37 +87,35 @@ void beforeEach(ExtensionContext context) throws Exception {
8887
greengrassCoreIPCClient = new GreengrassCoreIPCClient(clientConnection);
8988
}
9089

91-
@Disabled
9290
@SuppressWarnings({"PMD.CloseResource", "PMD.AvoidCatchingGenericException"})
9391
@EnabledOnOs({OS.LINUX})
9492
@Test
9593
void GIVEN_LifeCycleEventStreamClient_WHEN_pause_resume_component_THEN_target_service_paused_and_resumed()
9694
throws Exception {
95+
// Skip test if not running as root (required for cgroup access)
96+
assumeTrue("root".equals(SystemUtils.USER_NAME), "Test requires root access for cgroup management");
97+
9798
GenericExternalService component = (GenericExternalService) kernel.locate(TARGET_COMPONENT_NAME);
9899

99100
PauseComponentRequest pauseRequest = new PauseComponentRequest();
100101
pauseRequest.setComponentName(TARGET_COMPONENT_NAME);
101102
greengrassCoreIPCClient.pauseComponent(pauseRequest, Optional.empty()).getResponse().get(5, TimeUnit.SECONDS);
102103

103104
assertTrue(component.isPaused());
104-
assertThat(getCgroupFreezerState(component.getServiceName()),
105-
anyOf(is(LinuxSystemResourceController.CgroupFreezerState.FROZEN),
106-
is(LinuxSystemResourceController.CgroupFreezerState.FREEZING)));
105+
106+
CgroupManager freezerManager = isCgroupV2Supported() ? CgroupV2.Freezer : CgroupV1.Freezer;
107+
assertTrue(freezerManager.isComponentFrozen(TARGET_COMPONENT_NAME));
107108

108109
ResumeComponentRequest resumeRequest = new ResumeComponentRequest();
109110
resumeRequest.setComponentName(TARGET_COMPONENT_NAME);
110111
greengrassCoreIPCClient.resumeComponent(resumeRequest, Optional.empty()).getResponse().get(5, TimeUnit.SECONDS);
111112

112113
assertFalse(component.isPaused());
113-
assertThat(getCgroupFreezerState(component.getServiceName()),
114-
is(LinuxSystemResourceController.CgroupFreezerState.THAWED));
114+
assertFalse(freezerManager.isComponentFrozen(TARGET_COMPONENT_NAME));
115115
}
116116

117-
private LinuxSystemResourceController.CgroupFreezerState getCgroupFreezerState(String serviceName)
118-
throws IOException {
119-
return LinuxSystemResourceController.CgroupFreezerState.valueOf(
120-
new String(Files.readAllBytes(Cgroup.Freezer.getCgroupFreezerStateFilePath(serviceName)),
121-
StandardCharsets.UTF_8).trim());
117+
private boolean isCgroupV2Supported() {
118+
return Files.exists(Paths.get("/sys/fs/cgroup/cgroup.controllers"));
122119
}
123120
}
124121

src/integrationtests/java/com/aws/greengrass/integrationtests/lifecyclemanager/GenericExternalServiceIntegTest.java

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,31 @@
1818
import com.aws.greengrass.logging.impl.GreengrassLogMessage;
1919
import com.aws.greengrass.logging.impl.Slf4jLogAdapter;
2020
import com.aws.greengrass.status.model.ComponentStatusDetails;
21+
import com.aws.greengrass.testcommons.testutilities.GGExtension;
2122
import com.aws.greengrass.testcommons.testutilities.NoOpPathOwnershipHandler;
2223
import com.aws.greengrass.util.Pair;
23-
import com.aws.greengrass.util.platforms.unix.linux.Cgroup;
24-
import com.aws.greengrass.util.platforms.unix.linux.LinuxSystemResourceController;
24+
import com.aws.greengrass.util.platforms.unix.linux.CgroupManager;
25+
import com.aws.greengrass.util.platforms.unix.linux.CgroupV1;
26+
import com.aws.greengrass.util.platforms.unix.linux.CgroupV2;
2527
import org.apache.commons.lang3.SystemUtils;
2628
import org.junit.jupiter.api.AfterEach;
2729
import org.junit.jupiter.api.BeforeEach;
28-
import org.junit.jupiter.api.Disabled;
2930
import org.junit.jupiter.api.Test;
3031
import org.junit.jupiter.api.condition.EnabledOnOs;
3132
import org.junit.jupiter.api.condition.OS;
33+
import org.junit.jupiter.api.extension.ExtendWith;
3234
import org.junit.jupiter.api.extension.ExtensionContext;
3335
import org.junit.jupiter.params.ParameterizedTest;
3436
import org.junit.jupiter.params.provider.Arguments;
3537
import org.junit.jupiter.params.provider.MethodSource;
38+
import org.mockito.junit.jupiter.MockitoExtension;
3639

3740
import java.io.IOException;
3841
import java.nio.charset.StandardCharsets;
3942
import java.nio.file.FileSystemException;
4043
import java.nio.file.Files;
4144
import java.nio.file.Path;
45+
import java.nio.file.Paths;
4246
import java.util.ArrayList;
4347
import java.util.List;
4448
import java.util.concurrent.CompletableFuture;
@@ -64,7 +68,6 @@
6468
import static com.aws.greengrass.testcommons.testutilities.TestUtils.createCloseableLogListener;
6569
import static com.aws.greengrass.util.platforms.unix.UnixPlatform.STDOUT;
6670
import static org.hamcrest.MatcherAssert.assertThat;
67-
import static org.hamcrest.Matchers.anyOf;
6871
import static org.hamcrest.Matchers.contains;
6972
import static org.hamcrest.Matchers.containsString;
7073
import static org.hamcrest.Matchers.equalTo;
@@ -83,7 +86,7 @@
8386
import static org.mockito.Mockito.times;
8487
import static org.mockito.Mockito.verify;
8588

86-
89+
@ExtendWith({GGExtension.class, MockitoExtension.class})
8790
class GenericExternalServiceIntegTest extends BaseITCase {
8891

8992
private Kernel kernel;
@@ -581,7 +584,6 @@ void GIVEN_bootstrap_command_WHEN_runs_longer_than_5_sec_THEN_timeout_exception_
581584
assertThrows(TimeoutException.class, serviceWithJustBootstrapAndShouldTimeout::bootstrap);
582585
}
583586

584-
@Disabled
585587
@EnabledOnOs({OS.LINUX, OS.MAC})
586588
@ParameterizedTest
587589
@MethodSource("posixTestUserConfig")
@@ -637,10 +639,12 @@ void GIVEN_posix_default_user_WHEN_runs_THEN_runs_with_default_user(String file,
637639
}
638640
}
639641

640-
@Disabled
641642
@EnabledOnOs({OS.LINUX})
642643
@Test
643644
void GIVEN_linux_resource_limits_WHEN_it_changes_THEN_component_runs_with_new_resource_limits() throws Exception {
645+
// Skip test if no proper access available (required for cgroup access)
646+
assumeTrue("root".equals(SystemUtils.USER_NAME), "Test requires root access for cgroup management");
647+
644648
String componentName = "echo_service";
645649
// Run with no resource limit
646650
ConfigPlatformResolver.initKernelWithMultiPlatformConfig(kernel,
@@ -818,20 +822,38 @@ void GIVEN_service_starts_up_WHEN_startup_times_out_THEN_timeout_error_code_pers
818822
}
819823

820824
private void assertResourceLimits(String componentName, long memory, double cpus) throws Exception {
821-
byte[] buf1 = Files.readAllBytes(Cgroup.Memory.getComponentMemoryLimitPath(componentName));
822-
assertThat(memory, equalTo(Long.parseLong(new String(buf1, StandardCharsets.UTF_8).trim())));
823-
824-
byte[] buf2 = Files.readAllBytes(Cgroup.CPU.getComponentCpuQuotaPath(componentName));
825-
byte[] buf3 = Files.readAllBytes(Cgroup.CPU.getComponentCpuPeriodPath(componentName));
826-
827-
int quota = Integer.parseInt(new String(buf2, StandardCharsets.UTF_8).trim());
828-
int period = Integer.parseInt(new String(buf3, StandardCharsets.UTF_8).trim());
825+
int quota;
826+
int period;
827+
828+
if (isCgroupV2Supported()) {
829+
byte[] buf1 = Files.readAllBytes(CgroupV2.Memory.getComponentMemoryLimitPath(componentName));
830+
assertThat(memory, equalTo(Long.parseLong(new String(buf1, StandardCharsets.UTF_8).trim())));
831+
832+
byte[] buf2 = Files.readAllBytes(CgroupV2.CPU.getComponentCpuLimitPath(componentName));
833+
String cpuMax = new String(buf2, StandardCharsets.UTF_8).trim();
834+
String[] parts = cpuMax.split(" ");
835+
quota = Integer.parseInt(parts[0]);
836+
period = Integer.parseInt(parts[1]);
837+
} else {
838+
byte[] buf1 = Files.readAllBytes(CgroupV1.Memory.getComponentMemoryLimitPath(componentName));
839+
assertThat(memory, equalTo(Long.parseLong(new String(buf1, StandardCharsets.UTF_8).trim())));
840+
841+
byte[] buf2 = Files.readAllBytes(CgroupV1.CPU.getComponentCpuQuotaPath(componentName));
842+
byte[] buf3 = Files.readAllBytes(CgroupV1.CPU.getComponentCpuPeriodPath(componentName));
843+
844+
quota = Integer.parseInt(new String(buf2, StandardCharsets.UTF_8).trim());
845+
period = Integer.parseInt(new String(buf3, StandardCharsets.UTF_8).trim());
846+
}
847+
829848
int expectedQuota = (int) (cpus * period);
830849
assertThat(expectedQuota, equalTo(quota));
831850
}
832851

852+
@Test
853+
@EnabledOnOs({OS.LINUX})
833854
void GIVEN_running_service_WHEN_pause_resume_requested_THEN_pause_resume_Service_and_freeze_thaw_cgroup(
834855
ExtensionContext context) throws Exception {
856+
assumeTrue("root".equals(SystemUtils.USER_NAME), "Test requires root access for cgroup management");
835857
ignoreExceptionOfType(context, FileSystemException.class);
836858
ConfigPlatformResolver.initKernelWithMultiPlatformConfig(kernel,
837859
getClass().getResource("long_running_services.yaml"));
@@ -852,21 +874,17 @@ void GIVEN_running_service_WHEN_pause_resume_requested_THEN_pause_resume_Service
852874

853875
component.pause();
854876
assertTrue(component.isPaused());
855-
assertThat(getCgroupFreezerState(component.getServiceName()),
856-
anyOf(is(LinuxSystemResourceController.CgroupFreezerState.FROZEN),
857-
is(LinuxSystemResourceController.CgroupFreezerState.FREEZING)));
877+
878+
// check if component is frozen
879+
CgroupManager freezerManager = isCgroupV2Supported() ? CgroupV2.Freezer : CgroupV1.Freezer;
880+
assertTrue(freezerManager.isComponentFrozen(component.getServiceName()));
858881

859882
component.resume();
860883
assertFalse(component.isPaused());
861-
assertThat(getCgroupFreezerState(component.getServiceName()),
862-
is(LinuxSystemResourceController.CgroupFreezerState.THAWED));
884+
assertFalse(freezerManager.isComponentFrozen(component.getServiceName()));
863885
}
864886

865-
// To be used on linux only
866-
private LinuxSystemResourceController.CgroupFreezerState getCgroupFreezerState(String serviceName)
867-
throws IOException {
868-
return LinuxSystemResourceController.CgroupFreezerState
869-
.valueOf(new String(Files.readAllBytes(Cgroup.Freezer.getCgroupFreezerStateFilePath(serviceName))
870-
, StandardCharsets.UTF_8).trim());
887+
private boolean isCgroupV2Supported() {
888+
return Files.exists(Paths.get("/sys/fs/cgroup/cgroup.controllers"));
871889
}
872890
}

0 commit comments

Comments
 (0)