diff --git a/src/integrationtests/java/com/aws/greengrass/integrationtests/lifecyclemanager/GenericExternalServiceIntegTest.java b/src/integrationtests/java/com/aws/greengrass/integrationtests/lifecyclemanager/GenericExternalServiceIntegTest.java index f07204d3ac..7ad911722a 100644 --- a/src/integrationtests/java/com/aws/greengrass/integrationtests/lifecyclemanager/GenericExternalServiceIntegTest.java +++ b/src/integrationtests/java/com/aws/greengrass/integrationtests/lifecyclemanager/GenericExternalServiceIntegTest.java @@ -27,7 +27,6 @@ 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; @@ -667,8 +666,8 @@ void GIVEN_linux_resource_limits_WHEN_it_changes_THEN_component_runs_with_new_re 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"); + LinuxSystemResourceController controllerV2 = (LinuxSystemResourceController) systemResourceController; + Field memoryCgroupField = LinuxSystemResourceController.class.getSuperclass().getDeclaredField("memoryCgroup"); memoryCgroupField.setAccessible(true); Cgroup memoryCgroup = (Cgroup) memoryCgroupField.get(controllerV2); Field subsystem = memoryCgroup.getClass().getDeclaredField("subSystem"); @@ -677,7 +676,7 @@ void GIVEN_linux_resource_limits_WHEN_it_changes_THEN_component_runs_with_new_re Field f = cg.getClass().getInterfaces()[0].getDeclaredField("CGROUP_ROOT"); setFinalStatic(f, Paths.get(ROOT_PATH_STRING)); - Field mountsField = LinuxSystemResourceControllerV2.class.getSuperclass().getDeclaredField("MOUNT_PATH"); + Field mountsField = LinuxSystemResourceController.class.getSuperclass().getDeclaredField("MOUNT_PATH"); mountsField.setAccessible(true); String mountPathFile = rootGGPathString + "/mountPath.txt"; final Path mountPathFilePath = Paths.get(mountPathFile); diff --git a/src/main/java/com/aws/greengrass/util/platforms/unix/linux/CGroupSubSystemPath.java b/src/main/java/com/aws/greengrass/util/platforms/unix/linux/CGroupSubSystemPath.java index beef8b1b01..e99c3130a0 100644 --- a/src/main/java/com/aws/greengrass/util/platforms/unix/linux/CGroupSubSystemPath.java +++ b/src/main/java/com/aws/greengrass/util/platforms/unix/linux/CGroupSubSystemPath.java @@ -5,10 +5,16 @@ package com.aws.greengrass.util.platforms.unix.linux; +import com.aws.greengrass.lifecyclemanager.GreengrassService; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashSet; +import java.util.List; +import java.util.Set; @SuppressFBWarnings(value = "DMI_HARDCODED_ABSOLUTE_FILENAME", justification = "CGroupSubSystemPath virtual filesystem path cannot be relative") @@ -24,6 +30,8 @@ public interface CGroupSubSystemPath { String MEMORY_MAX = "memory.max"; String CGROUP_SUBTREE_CONTROL = "cgroup.subtree_control"; String CGROUP_FREEZE = "cgroup.freeze"; + String MOUNT_PATH = "/proc/self/mounts"; + String UNICODE_SPACE = "\\040"; default Path getRootPath() { return CGROUP_ROOT; @@ -70,4 +78,43 @@ default Path getComponentCpuMaxPath(String componentName) { default Path getCgroupFreezePath(String componentName) { return null; } + + void initializeCgroup(GreengrassService component, LinuxPlatform platform) throws IOException; + + void handleCpuLimits(GreengrassService component, double cpu) throws IOException; + + void pauseComponentProcessesCore(GreengrassService component, List processes) throws IOException; + + void resumeComponentProcesses(GreengrassService component) throws IOException; + + /** + * Get mounted paths. + * + * @return A set of String + * @throws IOException IOException + */ + default Set getMountedPaths() throws IOException { + Set mountedPaths = new HashSet<>(); + + Path procMountsPath = Paths.get(MOUNT_PATH); + List mounts = Files.readAllLines(procMountsPath); + for (String mount : mounts) { + String[] split = mount.split(" "); + // As reported in fstab(5) manpage, struct is: + // 1st field is volume name + // 2nd field is path with spaces escaped as \040 + // 3rd field is fs type + // 4th field is mount options + // 5th field is used by dump(8) (ignored) + // 6th field is fsck order (ignored) + if (split.length < 6) { + continue; + } + + // We only need the path of the mounts to verify whether cgroup is mounted + String path = split[1].replace(UNICODE_SPACE, " "); + mountedPaths.add(path); + } + return mountedPaths; + } } diff --git a/src/main/java/com/aws/greengrass/util/platforms/unix/linux/Cgroup.java b/src/main/java/com/aws/greengrass/util/platforms/unix/linux/Cgroup.java index 43b877f506..d28dece13b 100644 --- a/src/main/java/com/aws/greengrass/util/platforms/unix/linux/Cgroup.java +++ b/src/main/java/com/aws/greengrass/util/platforms/unix/linux/Cgroup.java @@ -5,7 +5,11 @@ package com.aws.greengrass.util.platforms.unix.linux; +import com.aws.greengrass.lifecyclemanager.GreengrassService; + +import java.io.IOException; import java.nio.file.Path; +import java.util.List; /** * Represents Linux cgroup subsystems. @@ -94,4 +98,24 @@ public Path getCgroupFreezePath(String componentName) { return subSystem.getCgroupFreezePath(componentName); } + public void initializeCgroup(GreengrassService component, LinuxPlatform platform) throws IOException { + subSystem.initializeCgroup(component, platform); + } + + public void handleCpuLimits(GreengrassService component, double cpu) throws IOException { + subSystem.handleCpuLimits(component, cpu); + } + + public void pauseComponentProcessesCore(GreengrassService component, List processes) + throws IOException { + subSystem.pauseComponentProcessesCore(component, processes); + } + + public void resumeComponentProcesses(GreengrassService component) throws IOException { + subSystem.resumeComponentProcesses(component); + } + + protected Path freezerCgroupStateFile(String component) { + return getCgroupFreezerStateFilePath(component); + } } diff --git a/src/main/java/com/aws/greengrass/util/platforms/unix/linux/CgroupSubSystem.java b/src/main/java/com/aws/greengrass/util/platforms/unix/linux/CgroupSubSystem.java index 9e3aab7855..3dbaac81f9 100644 --- a/src/main/java/com/aws/greengrass/util/platforms/unix/linux/CgroupSubSystem.java +++ b/src/main/java/com/aws/greengrass/util/platforms/unix/linux/CgroupSubSystem.java @@ -5,9 +5,17 @@ package com.aws.greengrass.util.platforms.unix.linux; +import com.aws.greengrass.lifecyclemanager.GreengrassService; +import com.aws.greengrass.util.Utils; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.Set; @SuppressFBWarnings(value = "DMI_HARDCODED_ABSOLUTE_FILENAME", justification = "CgroupSubSystem virtual filesystem path cannot be relative") @@ -89,4 +97,73 @@ public Path getCgroupProcsPath(String componentName) { public Path getCgroupFreezerStateFilePath(String componentName) { return getSubsystemComponentPath(componentName).resolve(FREEZER_STATE_FILE); } + + @Override + public void initializeCgroup(GreengrassService component, LinuxPlatform platform) throws IOException { + Set mounts = getMountedPaths(); + + if (!mounts.contains(getRootPath().toString())) { + platform.runCmd(rootMountCmd(), o -> { + }, "Failed to mount cgroup root"); + Files.createDirectory(getSubsystemRootPath()); + } + + if (!mounts.contains(getSubsystemRootPath().toString())) { + platform.runCmd(subsystemMountCmd(), o -> { + }, "Failed to mount cgroup subsystem"); + } + if (!Files.exists(getSubsystemGGPath())) { + Files.createDirectory(getSubsystemGGPath()); + } + if (!Files.exists(getSubsystemComponentPath(component.getServiceName()))) { + Files.createDirectory(getSubsystemComponentPath(component.getServiceName())); + } + } + + @Override + public void handleCpuLimits(GreengrassService component, double cpu) throws IOException { + byte[] content = Files.readAllBytes( + getComponentCpuPeriodPath(component.getServiceName())); + int cpuPeriodUs = Integer.parseInt(new String(content, StandardCharsets.UTF_8).trim()); + + int cpuQuotaUs = (int) (cpuPeriodUs * cpu); + String cpuQuotaUsStr = Integer.toString(cpuQuotaUs); + + Files.write(getComponentCpuQuotaPath(component.getServiceName()), + cpuQuotaUsStr.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public void pauseComponentProcessesCore(GreengrassService component, List processes) + throws IOException { + if (LinuxSystemResourceController.CgroupFreezerState.FROZEN.equals( + currentFreezerCgroupState(component.getServiceName()))) { + return; + } + Files.write(getCgroupFreezerStateFilePath(component.getServiceName()), + LinuxSystemResourceController.CgroupFreezerState.FROZEN.toString().getBytes(StandardCharsets.UTF_8), + StandardOpenOption.TRUNCATE_EXISTING); + } + + @Override + public void resumeComponentProcesses(GreengrassService component) throws IOException { + if (LinuxSystemResourceController.CgroupFreezerState.THAWED.equals( + currentFreezerCgroupState(component.getServiceName()))) { + return; + } + + Files.write(getCgroupFreezerStateFilePath(component.getServiceName()), + LinuxSystemResourceController.CgroupFreezerState.THAWED.toString().getBytes(StandardCharsets.UTF_8), + StandardOpenOption.TRUNCATE_EXISTING); + } + + private LinuxSystemResourceController.CgroupFreezerState currentFreezerCgroupState(String component) + throws IOException { + List stateFileContent = + Files.readAllLines(getCgroupFreezerStateFilePath(component)); + if (Utils.isEmpty(stateFileContent) || stateFileContent.size() != 1) { + throw new IOException("Unexpected error reading freezer cgroup state"); + } + return LinuxSystemResourceController.CgroupFreezerState.valueOf(stateFileContent.get(0).trim()); + } } diff --git a/src/main/java/com/aws/greengrass/util/platforms/unix/linux/CgroupSubSystemV2.java b/src/main/java/com/aws/greengrass/util/platforms/unix/linux/CgroupSubSystemV2.java index c064572cc2..3aef0931cb 100644 --- a/src/main/java/com/aws/greengrass/util/platforms/unix/linux/CgroupSubSystemV2.java +++ b/src/main/java/com/aws/greengrass/util/platforms/unix/linux/CgroupSubSystemV2.java @@ -5,14 +5,23 @@ package com.aws.greengrass.util.platforms.unix.linux; +import com.aws.greengrass.lifecyclemanager.GreengrassService; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.commons.lang3.StringUtils; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.Set; @SuppressFBWarnings(value = "DMI_HARDCODED_ABSOLUTE_FILENAME", justification = "CgroupSubSystemV2 virtual filesystem path cannot be relative") public enum CgroupSubSystemV2 implements CGroupSubSystemPath { Memory, CPU, Freezer, Unified; + private static final String CGROUP_SUBTREE_CONTROL_CONTENT = "+cpuset +cpu +io +memory +pids"; @Override public String rootMountCmd() { @@ -68,4 +77,69 @@ public Path getComponentCpuMaxPath(String componentName) { public Path getCgroupFreezePath(String componentName) { return getSubsystemComponentPath(componentName).resolve(CGROUP_FREEZE); } + + @Override + public void initializeCgroup(GreengrassService component, LinuxPlatform platform) throws IOException { + Set mounts = getMountedPaths(); + + if (!mounts.contains(getRootPath().toString())) { + platform.runCmd(rootMountCmd(), o -> { + }, "Failed to mount cgroup root"); + Files.createDirectory(getSubsystemRootPath()); + } + + if (!Files.exists(getSubsystemGGPath())) { + Files.createDirectory(getSubsystemGGPath()); + } + if (!Files.exists(getSubsystemComponentPath(component.getServiceName()))) { + Files.createDirectory(getSubsystemComponentPath(component.getServiceName())); + } + + //Enable controllers for root group + Files.write(getRootSubTreeControlPath(), + CGROUP_SUBTREE_CONTROL_CONTENT.getBytes(StandardCharsets.UTF_8)); + //Enable controllers for gg group + Files.write(getGGSubTreeControlPath(), + CGROUP_SUBTREE_CONTROL_CONTENT.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public void handleCpuLimits(GreengrassService component, double cpu) throws IOException { + byte[] content = Files.readAllBytes( + getComponentCpuMaxPath(component.getServiceName())); + String cpuMaxContent = new String(content, StandardCharsets.UTF_8).trim(); + String[] cpuMaxContentArr = cpuMaxContent.split(" "); + String cpuMaxStr = "max"; + String cpuPeriodStr = "100000"; + + if (cpuMaxContentArr.length >= 2) { + cpuMaxStr = cpuMaxContentArr[0]; + cpuPeriodStr = cpuMaxContentArr[1]; + + if (!StringUtils.isEmpty(cpuPeriodStr)) { + int period = Integer.parseInt(cpuPeriodStr.trim()); + int max = (int) (period * cpu); + cpuMaxStr = Integer.toString(max); + } + } + + String latestCpuMaxContent = String.format("%s %s", cpuMaxStr, cpuPeriodStr); + Files.write(getComponentCpuMaxPath(component.getServiceName()), + latestCpuMaxContent.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public void pauseComponentProcessesCore(GreengrassService component, List processes) + throws IOException { + Files.write(getCgroupFreezerStateFilePath(component.getServiceName()), + String.valueOf(CgroupV2FreezerState.FROZEN.getIndex()).getBytes(StandardCharsets.UTF_8), + StandardOpenOption.TRUNCATE_EXISTING); + } + + @Override + public void resumeComponentProcesses(GreengrassService component) throws IOException { + Files.write(getCgroupFreezerStateFilePath(component.getServiceName()), + String.valueOf(CgroupV2FreezerState.THAWED.getIndex()).getBytes(StandardCharsets.UTF_8), + StandardOpenOption.TRUNCATE_EXISTING); + } } diff --git a/src/main/java/com/aws/greengrass/util/platforms/unix/linux/LinuxPlatform.java b/src/main/java/com/aws/greengrass/util/platforms/unix/linux/LinuxPlatform.java index 13b7c9119e..5084789edf 100644 --- a/src/main/java/com/aws/greengrass/util/platforms/unix/linux/LinuxPlatform.java +++ b/src/main/java/com/aws/greengrass/util/platforms/unix/linux/LinuxPlatform.java @@ -17,18 +17,15 @@ justification = "Cgroup Controller virtual filesystem path cannot be relative") public class LinuxPlatform extends UnixPlatform { private static final Path CGROUP_CONTROLLERS = Paths.get("/sys/fs/cgroup/cgroup.controllers"); - SystemResourceController systemResourceController; @Override public SystemResourceController getSystemResourceController() { //if the path exists, identify it as cgroupv2, otherwise identify it as cgroupv1 if (Files.exists(CGROUP_CONTROLLERS)) { - systemResourceController = new LinuxSystemResourceControllerV2(this); + return new LinuxSystemResourceController(this, false); } else { - systemResourceController = new LinuxSystemResourceController(this); + return new LinuxSystemResourceController(this, true); } - - return systemResourceController; } } diff --git a/src/main/java/com/aws/greengrass/util/platforms/unix/linux/LinuxSystemResourceController.java b/src/main/java/com/aws/greengrass/util/platforms/unix/linux/LinuxSystemResourceController.java index ab150f395f..bf49b53367 100644 --- a/src/main/java/com/aws/greengrass/util/platforms/unix/linux/LinuxSystemResourceController.java +++ b/src/main/java/com/aws/greengrass/util/platforms/unix/linux/LinuxSystemResourceController.java @@ -11,16 +11,13 @@ import com.aws.greengrass.util.Coerce; import com.aws.greengrass.util.Utils; import com.aws.greengrass.util.platforms.SystemResourceController; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.zeroturnaround.process.PidUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; import java.util.Arrays; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -32,37 +29,47 @@ import static org.apache.commons.io.FileUtils.ONE_KB; +@SuppressFBWarnings(value = "DMI_HARDCODED_ABSOLUTE_FILENAME", + justification = "Cgroup Controller virtual filesystem path cannot be relative") public class LinuxSystemResourceController implements SystemResourceController { private static final Logger logger = LogManager.getLogger(LinuxSystemResourceController.class); - - protected static final String COMPONENT_NAME = "componentName"; - protected static final String MEMORY_KEY = "memory"; - protected static final String CPUS_KEY = "cpus"; - private static final String UNICODE_SPACE = "\\040"; - protected static final String MOUNT_PATH = "/proc/self/mounts"; - protected Cgroup memoryCgroup; - protected Cgroup cpuCgroup; - protected Cgroup freezerCgroup; - protected Cgroup unifiedCgroup; - protected List resourceLimitCgroups; + private static final String COMPONENT_NAME = "componentName"; + private static final String MEMORY_KEY = "memory"; + private static final String CPUS_KEY = "cpus"; + private Cgroup memoryCgroup; + private Cgroup cpuCgroup; + private Cgroup freezerCgroup; + private Cgroup unifiedCgroup; + private List resourceLimitCgroups; protected CopyOnWriteArrayList usedCgroups = new CopyOnWriteArrayList<>(); protected LinuxPlatform platform; - protected Set mounts; - - public LinuxSystemResourceController() { + /** + * LinuxSystemResourceController Constructor. + * + * @param platform platform + * @param isV1Used if you use v1 + */ + public LinuxSystemResourceController(LinuxPlatform platform, boolean isV1Used) { + this.platform = platform; + if (isV1Used) { + this.memoryCgroup = new Cgroup(CgroupSubSystem.Memory); + this.cpuCgroup = new Cgroup(CgroupSubSystem.CPU); + this.freezerCgroup = new Cgroup(CgroupSubSystem.Freezer); + resourceLimitCgroups = Arrays.asList( + memoryCgroup, cpuCgroup); + } else { + this.unifiedCgroup = new Cgroup(CgroupSubSystemV2.Unified); + this.memoryCgroup = new Cgroup(CgroupSubSystemV2.Memory); + this.cpuCgroup = new Cgroup(CgroupSubSystemV2.CPU); + this.freezerCgroup = new Cgroup(CgroupSubSystemV2.Freezer); + resourceLimitCgroups = Arrays.asList(unifiedCgroup); + } } - LinuxSystemResourceController(LinuxPlatform platform) { - this.platform = platform; - this.memoryCgroup = new Cgroup(CgroupSubSystem.Memory); - this.cpuCgroup = new Cgroup(CgroupSubSystem.CPU); - this.freezerCgroup = new Cgroup(CgroupSubSystem.Freezer); - resourceLimitCgroups = Arrays.asList( - memoryCgroup, cpuCgroup); - } + @Override @@ -89,15 +96,7 @@ public void updateResourceLimits(GreengrassService component, Map 0) { - byte[] content = Files.readAllBytes( - cpuCgroup.getComponentCpuPeriodPath(component.getServiceName())); - int cpuPeriodUs = Integer.parseInt(new String(content, StandardCharsets.UTF_8).trim()); - - int cpuQuotaUs = (int) (cpuPeriodUs * cpu); - String cpuQuotaUsStr = Integer.toString(cpuQuotaUs); - - Files.write(cpuCgroup.getComponentCpuQuotaPath(component.getServiceName()), - cpuQuotaUsStr.getBytes(StandardCharsets.UTF_8)); + cpuCgroup.handleCpuLimits(component, cpu); } else { logger.atWarn().kv(COMPONENT_NAME, component.getServiceName()).kv(CPUS_KEY, cpu) @@ -170,23 +169,13 @@ public void addComponentProcess(GreengrassService component, Process process) { @Override public void pauseComponentProcesses(GreengrassService component, List processes) throws IOException { prePauseComponentProcesses(component, processes); - if (CgroupFreezerState.FROZEN.equals(currentFreezerCgroupState(component.getServiceName()))) { - return; - } - Files.write(freezerCgroupStateFile(component.getServiceName()), - CgroupFreezerState.FROZEN.toString().getBytes(StandardCharsets.UTF_8), - StandardOpenOption.TRUNCATE_EXISTING); + freezerCgroup.pauseComponentProcessesCore(component, processes); } @Override public void resumeComponentProcesses(GreengrassService component) throws IOException { - if (CgroupFreezerState.THAWED.equals(currentFreezerCgroupState(component.getServiceName()))) { - return; - } - Files.write(freezerCgroupStateFile(component.getServiceName()), - CgroupFreezerState.THAWED.toString().getBytes(StandardCharsets.UTF_8), - StandardOpenOption.TRUNCATE_EXISTING); + freezerCgroup.resumeComponentProcesses(component); } protected void addComponentProcessToCgroup(String component, Process process, Cgroup cg) @@ -238,56 +227,8 @@ private void handleErrorAddingPidToCgroup(IOException e, String component) { } } - /** - * Get mounted path for Cgroup. - * @return mounted path set - * @throws IOException IOException - */ - public Set getMountedPaths() throws IOException { - Set mountedPaths = new HashSet<>(); - - Path procMountsPath = Paths.get(MOUNT_PATH); - List mounts = Files.readAllLines(procMountsPath); - for (String mount : mounts) { - String[] split = mount.split(" "); - // As reported in fstab(5) manpage, struct is: - // 1st field is volume name - // 2nd field is path with spaces escaped as \040 - // 3rd field is fs type - // 4th field is mount options - // 5th field is used by dump(8) (ignored) - // 6th field is fsck order (ignored) - if (split.length < 6) { - continue; - } - - // We only need the path of the mounts to verify whether cgroup is mounted - String path = split[1].replace(UNICODE_SPACE, " "); - mountedPaths.add(path); - } - return mountedPaths; - } - protected void initializeCgroup(GreengrassService component, Cgroup cgroup) throws IOException { - mounts = getMountedPaths(); - - if (!mounts.contains(cgroup.getRootPath().toString())) { - platform.runCmd(cgroup.rootMountCmd(), o -> { - }, "Failed to mount cgroup root"); - Files.createDirectory(cgroup.getSubsystemRootPath()); - } - - if (!mounts.contains(cgroup.getSubsystemRootPath().toString())) { - platform.runCmd(cgroup.subsystemMountCmd(), o -> { - }, "Failed to mount cgroup subsystem"); - } - if (!Files.exists(cgroup.getSubsystemGGPath())) { - Files.createDirectory(cgroup.getSubsystemGGPath()); - } - if (!Files.exists(cgroup.getSubsystemComponentPath(component.getServiceName()))) { - Files.createDirectory(cgroup.getSubsystemComponentPath(component.getServiceName())); - } - + cgroup.initializeCgroup(component, platform); usedCgroups.add(cgroup); } @@ -296,18 +237,6 @@ private Set pidsInComponentCgroup(Cgroup cgroup, String component) thro .stream().map(Integer::parseInt).collect(Collectors.toSet()); } - protected Path freezerCgroupStateFile(String component) { - return freezerCgroup.getCgroupFreezerStateFilePath(component); - } - - private CgroupFreezerState currentFreezerCgroupState(String component) throws IOException { - List stateFileContent = - Files.readAllLines(freezerCgroupStateFile(component)); - if (Utils.isEmpty(stateFileContent) || stateFileContent.size() != 1) { - throw new IOException("Unexpected error reading freezer cgroup state"); - } - return CgroupFreezerState.valueOf(stateFileContent.get(0).trim()); - } protected void prePauseComponentProcesses(GreengrassService component, List processes) throws IOException { initializeCgroup(component, freezerCgroup); diff --git a/src/main/java/com/aws/greengrass/util/platforms/unix/linux/LinuxSystemResourceControllerV2.java b/src/main/java/com/aws/greengrass/util/platforms/unix/linux/LinuxSystemResourceControllerV2.java deleted file mode 100644 index 6e10e34e62..0000000000 --- a/src/main/java/com/aws/greengrass/util/platforms/unix/linux/LinuxSystemResourceControllerV2.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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 com.aws.greengrass.lifecyclemanager.GreengrassService; -import com.aws.greengrass.logging.api.Logger; -import com.aws.greengrass.logging.impl.LogManager; -import com.aws.greengrass.util.Coerce; -import org.apache.commons.lang3.StringUtils; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -/** - * https://www.kernel.org/doc/Documentation/cgroup-v2.txt - */ -public class LinuxSystemResourceControllerV2 extends LinuxSystemResourceController { - private static final Logger logger = LogManager.getLogger(LinuxSystemResourceControllerV2.class); - private static final String CGROUP_SUBTREE_CONTROL_CONTENT = "+cpuset +cpu +io +memory +pids"; - - /** - * Linux system resource controller V2 constrcutors. - * - * @param platform linux platform - */ - public LinuxSystemResourceControllerV2(LinuxPlatform platform) { - super(); - this.platform = platform; - this.unifiedCgroup = new Cgroup(CgroupSubSystemV2.Unified); - this.memoryCgroup = new Cgroup(CgroupSubSystemV2.Memory); - this.cpuCgroup = new Cgroup(CgroupSubSystemV2.CPU); - this.freezerCgroup = new Cgroup(CgroupSubSystemV2.Freezer); - resourceLimitCgroups = Arrays.asList(unifiedCgroup); - } - - - @Override - public void updateResourceLimits(GreengrassService component, Map resourceLimit) { - try { - super.updateMemoryResourceLimits(component, resourceLimit); - - if (resourceLimit.containsKey(CPUS_KEY)) { - double cpu = Coerce.toDouble(resourceLimit.get(CPUS_KEY)); - if (cpu > 0) { - handleCpuLimits(component, cpu); - } else { - logger.atWarn().kv(COMPONENT_NAME, component.getServiceName()).kv(CPUS_KEY, cpu) - .log("The provided cpu limit is invalid"); - } - } - } catch (IOException e) { - logger.atError().setCause(e).kv(COMPONENT_NAME, component.getServiceName()) - .log("Failed to apply resource limits"); - } - } - - @Override - public void pauseComponentProcesses(GreengrassService component, List processes) throws IOException { - prePauseComponentProcesses(component, processes); - - Files.write(freezerCgroupStateFile(component.getServiceName()), - String.valueOf(CgroupV2FreezerState.FROZEN.getIndex()).getBytes(StandardCharsets.UTF_8), - StandardOpenOption.TRUNCATE_EXISTING); - } - - @Override - public void resumeComponentProcesses(GreengrassService component) throws IOException { - Files.write(freezerCgroupStateFile(component.getServiceName()), - String.valueOf(CgroupV2FreezerState.THAWED.getIndex()).getBytes(StandardCharsets.UTF_8), - StandardOpenOption.TRUNCATE_EXISTING); - } - - @Override - protected void initializeCgroup(GreengrassService component, Cgroup cgroup) throws IOException { - mounts = getMountedPaths(); - - if (!mounts.contains(cgroup.getRootPath().toString())) { - platform.runCmd(cgroup.rootMountCmd(), o -> { - }, "Failed to mount cgroup root"); - Files.createDirectory(cgroup.getSubsystemRootPath()); - } - - if (!Files.exists(cgroup.getSubsystemGGPath())) { - Files.createDirectory(cgroup.getSubsystemGGPath()); - } - if (!Files.exists(cgroup.getSubsystemComponentPath(component.getServiceName()))) { - Files.createDirectory(cgroup.getSubsystemComponentPath(component.getServiceName())); - } - - //Enable controllers for root group - Files.write(cgroup.getRootSubTreeControlPath(), - CGROUP_SUBTREE_CONTROL_CONTENT.getBytes(StandardCharsets.UTF_8)); - //Enable controllers for gg group - Files.write(cgroup.getGGSubTreeControlPath(), - CGROUP_SUBTREE_CONTROL_CONTENT.getBytes(StandardCharsets.UTF_8)); - - usedCgroups.add(cgroup); - } - - private void handleCpuLimits(GreengrassService component, double cpu) throws IOException { - byte[] content = Files.readAllBytes( - cpuCgroup.getComponentCpuMaxPath(component.getServiceName())); - String cpuMaxContent = new String(content, StandardCharsets.UTF_8).trim(); - String[] cpuMaxContentArr = cpuMaxContent.split(" "); - String cpuMaxStr = "max"; - String cpuPeriodStr = "100000"; - - if (cpuMaxContentArr.length >= 2) { - cpuMaxStr = cpuMaxContentArr[0]; - cpuPeriodStr = cpuMaxContentArr[1]; - - if (!StringUtils.isEmpty(cpuPeriodStr)) { - int period = Integer.parseInt(cpuPeriodStr.trim()); - int max = (int) (period * cpu); - cpuMaxStr = Integer.toString(max); - } - } - - String latestCpuMaxContent = String.format("%s %s", cpuMaxStr, cpuPeriodStr); - Files.write(cpuCgroup.getComponentCpuMaxPath(component.getServiceName()), - latestCpuMaxContent.getBytes(StandardCharsets.UTF_8)); - } - -} diff --git a/src/test/java/com/aws/greengrass/util/platforms/unix/linux/LinuxSystemResourceControllerV2Test.java b/src/test/java/com/aws/greengrass/util/platforms/unix/linux/LinuxSystemResourceControllerV2Test.java index b6f43377a7..a9e02e016b 100644 --- a/src/test/java/com/aws/greengrass/util/platforms/unix/linux/LinuxSystemResourceControllerV2Test.java +++ b/src/test/java/com/aws/greengrass/util/platforms/unix/linux/LinuxSystemResourceControllerV2Test.java @@ -8,6 +8,7 @@ import com.aws.greengrass.lifecyclemanager.GreengrassService; import com.aws.greengrass.testcommons.testutilities.GGExtension; import com.aws.greengrass.util.Utils; +import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; @@ -28,25 +29,28 @@ import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; + @ExtendWith({MockitoExtension.class, GGExtension.class}) @DisabledOnOs(OS.WINDOWS) class LinuxSystemResourceControllerV2Test { @Mock GreengrassService component; @Mock - Cgroup cpuCgroup; - @Mock - Cgroup memoryCgroup; - @Mock LinuxPlatform platform; + @Spy + Cgroup cpuCgroup = new Cgroup(CgroupSubSystemV2.CPU); + @Spy + Cgroup memoryCgroup = new Cgroup(CgroupSubSystemV2.Memory); @InjectMocks @Spy - LinuxSystemResourceControllerV2 linuxSystemResourceControllerV2 = new LinuxSystemResourceControllerV2(platform); + LinuxSystemResourceController linuxSystemResourceControllerV2 = new LinuxSystemResourceController( + platform, false); + private static final String FILE_PATH = "/cgroupv2test"; private static final String CGROUP_MEMORY_LIMIT_FILE_NAME = "memory.txt"; private static final String CGROUP_CPU_LIMIT_FILE_NAME = "cpu.txt"; @@ -71,13 +75,13 @@ void GIVEN_cgroupv2_WHEN_memory_limit_updated_THEN_memory_limit_file_updated() t when(memoryCgroup.getComponentMemoryLimitPath(COMPONENT_NAME)).thenReturn(path); lenient().when(memoryCgroup.getSubsystemComponentPath(COMPONENT_NAME)).thenReturn(componentNameFolderPath); + when(cpuCgroup.getSubsystemComponentPath(COMPONENT_NAME)).thenReturn(path); + linuxSystemResourceControllerV2.updateResourceLimits(component, resourceLimit); List mounts = Files.readAllLines(path); assertEquals(String.valueOf(MEMORY_IN_KB * 1024), mounts.get(0)); - - Files.deleteIfExists(path); - Files.deleteIfExists(componentNameFolderPath); + FileUtils.deleteDirectory(componentNameFolderPath.toFile()); } @Test @@ -97,10 +101,10 @@ void GIVEN_cgroupv2_WHEN_cpu_limit_updated_THEN_cpu_limit_file_updated() throws Files.write(path, "max 100000".getBytes(StandardCharsets.UTF_8)); - doNothing().when(linuxSystemResourceControllerV2).updateMemoryResourceLimits(component, resourceLimit); - when(cpuCgroup.getComponentCpuMaxPath(COMPONENT_NAME)).thenReturn(path); - lenient().when(cpuCgroup.getSubsystemComponentPath(COMPONENT_NAME)).thenReturn(componentNameFolderPath); - linuxSystemResourceControllerV2.updateResourceLimits(component, resourceLimit); + CGroupSubSystemPath cpuSystemV2 = spy(CgroupSubSystemV2.CPU); + lenient().when(cpuSystemV2.getComponentCpuMaxPath(COMPONENT_NAME)).thenReturn(path); + + cpuSystemV2.handleCpuLimits(component, 0.5); List mounts = Files.readAllLines(path); assertEquals((int) (CPU_TIME * 100000) + " 100000", mounts.get(0));