diff options
author | Håkon Hallingstad <hakon@yahooinc.com> | 2023-04-28 15:29:42 +0200 |
---|---|---|
committer | Håkon Hallingstad <hakon@yahooinc.com> | 2023-04-28 15:29:42 +0200 |
commit | 631b2228210c23b2cd3bc97e1a4090547b5576a8 (patch) | |
tree | fea67dcd27093c208e8e2df6c15db7e75aa0a4ab /node-admin | |
parent | 75e261266c3629e4343f40f1aa26fc2dc02c9aa3 (diff) |
Use Cgroup in CgroupLimitsTask, CgroupV2Task, MiscHostMetrics, Podman
Diffstat (limited to 'node-admin')
13 files changed, 270 insertions, 157 deletions
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/Cgroup.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/Cgroup.java new file mode 100644 index 00000000000..2fcbcf3c738 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/Cgroup.java @@ -0,0 +1,163 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.node.admin.cgroup; + +import com.yahoo.vespa.defaults.Defaults; +import com.yahoo.vespa.hosted.node.admin.component.TaskContext; +import com.yahoo.vespa.hosted.node.admin.container.ContainerId; +import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath; + +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.logging.Logger; + +/** + * Represents a cgroup directory in the control group v2 hierarchy, see + * <a href="https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html">Control Group v2</a>. + * + * @author hakonhall + */ +public class Cgroup { + private static final Logger logger = Logger.getLogger(Cgroup.class.getName()); + + private static Map<String, Consumer<UnixPath>> cgroupDirectoryCallbacks = new HashMap<>(); + + private final Path root; + private final Path relativePath; + + public static Cgroup root(FileSystem fileSystem) { + return new Cgroup(fileSystem.getPath("/sys/fs/cgroup"), fileSystem.getPath("")); + } + + private Cgroup(Path root, Path relativePath) { + this.root = root.normalize(); + this.relativePath = this.root.relativize(this.root.resolve(relativePath).normalize()); + if (this.relativePath.toString().equals("..") || this.relativePath.toString().startsWith("../")) { + throw new IllegalArgumentException("Invalid cgroup relative path: " + relativePath); + } + } + + /** Whether this cgroup actually exists in the kernel / on the file system. */ + public boolean exists() { return unixPath().resolve("cgroup.controllers").exists(); } + + /** Creates this cgroup if it does not already exist, and return this. */ + public Cgroup create() { + if (unixPath().createDirectory()) { + // cgroup automatically creates various files in a newly created cgroup directory. A unit test may simulate + // this by registering consumers before the test is run. + Consumer<UnixPath> callback = cgroupDirectoryCallbacks.get(relativePath.toString()); + if (callback != null) + callback.accept(unixPath()); + } + return this; + } + + /** Whether v2 cgroup is enabled on this host. */ + public boolean v2CgroupIsEnabled() { return resolveRoot().exists(); } + + /** + * Resolve the given path against the path of this cgroup, and return the resulting cgroup. + * If the given path is absolute, it is resolved against the root of the cgroup hierarchy. + */ + public Cgroup resolve(String path) { + Path effectivePath = fileSystem().getPath(path); + if (effectivePath.isAbsolute()) { + return new Cgroup(root, fileSystem().getPath("/").relativize(effectivePath)); + } else { + return new Cgroup(root, relativePath.resolve(path)); + } + } + + /** Returns the root cgroup, possibly this. */ + public Cgroup resolveRoot() { return isRoot() ? this : new Cgroup(root, fileSystem().getPath("")); } + + /** Returns the cgroup of a system service assuming this is the root, e.g. vespa-host-admin -> system.slice/vespa-host-admin.service. */ + public Cgroup resolveSystemService(String name) { return resolve("system.slice").resolve(serviceNameOf(name)); } + + /** Returns the root cgroup of the given Podman container. */ + public Cgroup resolveContainer(ContainerId containerId) { return resolve("/machine.slice/libpod-" + containerId + ".scope/container"); } + + /** Returns the root cgroup of the container, or otherwise the root cgroup. */ + public Cgroup resolveRoot(Optional<ContainerId> containerId) { return containerId.map(this::resolveContainer).orElseGet(this::resolveRoot); } + + /** Returns the absolute path to this cgroup. */ + public Path path() { return root.resolve(relativePath); } + + /** Returns the absolute UnixPath to this cgroup. */ + public UnixPath unixPath() { return new UnixPath(path()); } + + public String read(String filename) { + return unixPath().resolve(filename).readUtf8File(); + } + + public Optional<String> readIfExists(String filename) { + return unixPath().resolve(filename).readUtf8FileIfExists().map(String::strip); + } + + public List<String> readLines(String filename) { + return unixPath().resolve(filename).readUtf8File().lines().toList(); + } + + public Optional<Integer> readIntIfExists(String filename) { + return unixPath().resolve(filename).readUtf8FileIfExists().map(String::strip).map(Integer::parseInt); + } + + public Size readSize(String filename) { return Size.from(read(filename).stripTrailing()); } + + public boolean convergeFileContent(TaskContext context, String filename, String content, boolean apply) { + UnixPath path = unixPath().resolve(filename); + String currentContent = path.readUtf8File(); + if (ensureSuffixNewline(currentContent).equals(ensureSuffixNewline(content))) return false; + + if (apply) { + context.recordSystemModification(logger, "Updating " + path + " from '" + currentContent.stripTrailing() + + "' to '" + content.stripTrailing() + "'"); + path.writeUtf8File(content); + } + return true; + } + + /** The kernel appears to append a newline if none exist, when writing to files in cgroupfs. */ + private static String ensureSuffixNewline(String content) { + return content.endsWith("\n") ? content : content + "\n"; + } + + /** Returns an instance representing core interface files (cgroup.* files). */ + public CgroupCore core() { return new CgroupCore(this); } + + /** Returns the CPU controller of this Cgroup (cpu.* files). */ + public CpuController cpu() { return new CpuController(this); } + + /** Returns the memory controller of this Cgroup (memory.* files). */ + public MemoryController memory() { return new MemoryController(this); } + + /** + * Wraps {@code command} to ensure it is executed in this cgroup. + * + * <p>WARNING: This method must be called only after vespa-cgexec has been installed.</p> + */ + public String[] wrapCommandForExecutionInCgroup(String... command) { + String[] fullCommand = new String[3 + command.length]; + fullCommand[0] = Defaults.getDefaults().vespaHome() + "/bin/vespa-cgexec"; + fullCommand[1] = "-g"; + fullCommand[2] = relativePath.toString(); + System.arraycopy(command, 0, fullCommand, 3, command.length); + return fullCommand; + } + + public static void unitTesting_atCgroupCreation(String relativePath, Consumer<UnixPath> callback) { + cgroupDirectoryCallbacks.put(relativePath, callback); + } + + private boolean isRoot() { return relativePath.toString().isEmpty(); } + + private static String serviceNameOf(String name) { + return name.indexOf('.') == -1 ? name + ".service" : name; + } + + private FileSystem fileSystem() { return root.getFileSystem(); } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/CgroupCore.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/CgroupCore.java new file mode 100644 index 00000000000..68f27549e1b --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/CgroupCore.java @@ -0,0 +1,34 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.node.admin.cgroup; + +import java.util.List; + +/** + * Utility methods for accessing the cgroup core interface files, i.e. all cgroup.* files. + * + * @author hakonhall + */ +public class CgroupCore { + private final Cgroup cgroup; + + CgroupCore(Cgroup cgroup) { this.cgroup = cgroup; } + + public List<Integer> getPidsInCgroup() { + return cgroup.readLines("cgroup.procs") + .stream() + .map(Integer::parseInt) + .toList(); + } + + /** Whether the given PID is a member of this cgroup. */ + public boolean isMember(int pid) { + return getPidsInCgroup().contains(pid); + } + + /** Move the given PID to this cgroup, but return false if it was already a member. */ + public boolean addMember(int pid) { + if (isMember(pid)) return false; + cgroup.unixPath().resolve("cgroup.procs").writeUtf8File(Integer.toString(pid)); + return true; + } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/ControlGroup.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/ControlGroup.java deleted file mode 100644 index 2fa055f2151..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/ControlGroup.java +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.node.admin.cgroup; - -import com.yahoo.vespa.defaults.Defaults; -import com.yahoo.vespa.hosted.node.admin.container.ContainerId; -import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath; - -import java.nio.file.FileSystem; -import java.nio.file.Path; - -/** - * Represents a cgroup directory in the control group v2 hierarchy, see - * <a href="https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html">Control Group v2</a>. - * - * @author hakonhall - */ -public class ControlGroup { - private final Path root; - private final Path relativePath; - - public static ControlGroup root(FileSystem fileSystem) { - return new ControlGroup(fileSystem.getPath("/sys/fs/cgroup"), fileSystem.getPath("")); - } - - private ControlGroup(Path root, Path relativePath) { - this.root = root.normalize(); - this.relativePath = this.root.relativize(this.root.resolve(relativePath).normalize()); - if (this.relativePath.toString().equals("..") || this.relativePath.toString().startsWith("../")) { - throw new IllegalArgumentException("Invalid cgroup relative path: " + relativePath); - } - } - - /** - * Resolve the given path against the path of this cgroup, and return the resulting cgroup. - * If the given path is absolute, it is resolved against the root of the cgroup hierarchy. - */ - public ControlGroup resolve(String path) { - Path effectivePath = fileSystem().getPath(path); - if (effectivePath.isAbsolute()) { - return new ControlGroup(root, fileSystem().getPath("/").relativize(effectivePath)); - } else { - return new ControlGroup(root, relativePath.resolve(path)); - } - } - - /** Returns the parent ControlGroup. */ - public ControlGroup resolveParent() { return new ControlGroup(root, relativePath.getParent()); } - - /** Returns the ControlGroup of a system service, e.g. vespa-host-admin. */ - public ControlGroup resolveSystemService(String name) { return resolve("/system.slice").resolve(serviceNameOf(name)); } - - /** Returns the root ControlGroup of the given Podman container. */ - public ControlGroup resolveContainer(ContainerId containerId) { return resolve("/machine.slice/libpod-" + containerId + ".scope/container"); } - - /** Returns the ControlGroup of a system service in the given Podman container. */ - public ControlGroup resolveContainerSystemService(ContainerId containerId, String name) { return resolveContainer(containerId).resolve("system.slice").resolve(serviceNameOf(name)); } - - /** Returns the absolute path to this cgroup. */ - public Path path() { return root.resolve(relativePath); } - - /** Returns the absolute UnixPath to this cgroup. */ - public UnixPath unixPath() { return new UnixPath(path()); } - - /** Returns the CPU controller of this ControlGroup. */ - public CpuController cpu() { return new CpuController(this); } - - /** Returns the memory controller of this ControlGroup. */ - public MemoryController memory() { return new MemoryController(this); } - - /** - * Wraps {@code command} to ensure it is executed in this cgroup. - * - * <p>WARNING: This method must be called only after vespa-cgexec has been installed.</p> - */ - public String[] wrapCommandForExecutionInCgroup(String... command) { - String[] fullCommand = new String[3 + command.length]; - fullCommand[0] = Defaults.getDefaults().vespaHome() + "/bin/vespa-cgexec"; - fullCommand[1] = "-g"; - fullCommand[2] = relativePath.toString(); - System.arraycopy(command, 0, fullCommand, 3, command.length); - return fullCommand; - } - - private static String serviceNameOf(String name) { - return name.indexOf('.') == -1 ? name + ".service" : name; - } - - private FileSystem fileSystem() { return root.getFileSystem(); } -} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/CpuController.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/CpuController.java index 9b6f0942c2a..c273a6237b4 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/CpuController.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/CpuController.java @@ -2,28 +2,24 @@ package com.yahoo.vespa.hosted.node.admin.cgroup; import com.yahoo.collections.Pair; -import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext; -import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath; +import com.yahoo.vespa.hosted.node.admin.component.TaskContext; import java.util.Arrays; import java.util.Map; import java.util.Optional; -import java.util.logging.Logger; import java.util.stream.Collectors; import static java.lang.Integer.parseInt; /** - * Represents a cgroup v2 CPU controller, i.e. all files named cpu.* + * Represents a cgroup v2 CPU controller, i.e. all cpu.* files. * * @author hakonhall */ public class CpuController { - private static final Logger logger = Logger.getLogger(CpuController.class.getName()); + private final Cgroup cgroup; - private final ControlGroup cgroup; - - CpuController(ControlGroup cgroup) { + CpuController(Cgroup cgroup) { this.cgroup = cgroup; } @@ -32,7 +28,7 @@ public class CpuController { * up to QUOTA in each PERIOD duration. A quota of "max" indicates no limit. */ public record Max(Size quota, int period) { - public String toFileContent() { return quota + " " + period; } + public String toFileContent() { return quota + " " + period + '\n'; } } /** @@ -41,9 +37,7 @@ public class CpuController { * @see Max */ public Optional<Max> readMax() { - return cgroup.unixPath() - .resolve("cpu.max") - .readUtf8FileIfExists() + return cgroup.readIfExists("cpu.max") .map(content -> { String[] parts = content.strip().split(" "); return new Max(Size.from(parts[0]), parseInt(parts[1])); @@ -56,17 +50,14 @@ public class CpuController { * @see #readMax() * @see Max */ - public boolean updateMax(NodeAgentContext context, int quota, int period) { + public boolean updateMax(TaskContext context, int quota, int period) { Max max = new Max(quota < 0 ? Size.max() : Size.from(quota), period); - return convergeFileContent(context, "cpu.max", max.toFileContent()); + return cgroup.convergeFileContent(context, "cpu.max", max.toFileContent(), true); } /** @return The weight in the range [1, 10000], or empty if not found. */ private Optional<Integer> readWeight() { - return cgroup.unixPath() - .resolve("cpu.weight") - .readUtf8FileIfExists() - .map(content -> parseInt(content.strip())); + return cgroup.readIntIfExists("cpu.weight"); } /** @return The number of shares allocated to this cgroup for purposes of CPU time scheduling, or empty if not found. */ @@ -74,8 +65,8 @@ public class CpuController { return readWeight().map(CpuController::weightToShares); } - public boolean updateShares(NodeAgentContext context, int shares) { - return convergeFileContent(context, "cpu.weight", Integer.toString(sharesToWeight(shares))); + public boolean updateShares(TaskContext context, int shares) { + return cgroup.convergeFileContent(context, "cpu.weight", sharesToWeight(shares) + "\n", true); } // Must be same as in crun: https://github.com/containers/crun/blob/72c6e60ade0e4716fe2d8353f0d97d72cc8d1510/src/libcrun/cgroup.c#L3061 @@ -83,16 +74,6 @@ public class CpuController { public static int sharesToWeight(int shares) { return (int) (1 + ((shares - 2L) * 9999) / 262142); } public static int weightToShares(int weight) { return (int) (2 + ((weight - 1L) * 262142) / 9999); } - private boolean convergeFileContent(NodeAgentContext context, String filename, String content) { - UnixPath path = cgroup.unixPath().resolve(filename); - String currentContent = path.readUtf8File().strip(); - if (currentContent.equals(content)) return false; - - context.recordSystemModification(logger, "Updating " + path + " from " + currentContent + " to " + content); - path.writeUtf8File(content); - return true; - } - public enum StatField { TOTAL_USAGE_USEC("usage_usec"), USER_USAGE_USEC("user_usec"), @@ -119,9 +100,7 @@ public class CpuController { } public Map<StatField, Long> readStats() { - return cgroup.unixPath() - .resolve("cpu.stat") - .readAllLines() + return cgroup.readLines("cpu.stat") .stream() .map(line -> line.split("\\s+")) .filter(parts -> parts.length == 2) diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/MemoryController.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/MemoryController.java index a1df9f20471..840cd025917 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/MemoryController.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/MemoryController.java @@ -1,43 +1,48 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.node.admin.cgroup; -import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath; +import java.util.List; +import java.util.Optional; /** - * Represents a cgroup v2 memory controller, i.e. all files named memory.* + * Represents a cgroup v2 memory controller, i.e. all memory.* files. * * @author hakonhall */ public class MemoryController { - private final ControlGroup cgroup; + private final Cgroup cgroup; - MemoryController(ControlGroup cgroup) { + MemoryController(Cgroup cgroup) { this.cgroup = cgroup; } /** @return Maximum amount of memory that can be used by the cgroup and its descendants. */ public Size readMax() { - return Size.from(cgroup.unixPath().resolve("memory.max").readUtf8File().strip()); + return cgroup.readSize("memory.max"); } /** @return The total amount of memory currently being used by the cgroup and its descendants, in bytes. */ public Size readCurrent() { - return Size.from(cgroup.unixPath().resolve("memory.current").readUtf8File().strip()); + return cgroup.readSize("memory.current"); + } + + /** @return The total amount of memory currently being used by the cgroup and its descendants, in bytes. */ + public Optional<Size> readCurrentIfExists() { + return cgroup.readIfExists("memory.current").map(Size::from); } /** @return Number of bytes used to cache filesystem data, including tmpfs and shared memory. */ public Size readFileSystemCache() { - return Size.from(readField(cgroup.unixPath().resolve("memory.stat"), "file")); + return Size.from(readField(cgroup.readLines("memory.stat"), "file")); } - private static String readField(UnixPath path, String fieldName) { - return path.readAllLines() - .stream() - .map(line -> line.split("\\s+")) - .filter(fields -> fields.length == 2) - .filter(fields -> fieldName.equals(fields[0])) - .map(fields -> fields[1]) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("No such field: " + fieldName)); + private static String readField(List<String> lines, String fieldName) { + return lines.stream() + .map(line -> line.split("\\s+")) + .filter(fields -> fields.length == 2) + .filter(fields -> fieldName.equals(fields[0])) + .map(fields -> fields[1]) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No such field: " + fieldName)); } } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/Size.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/Size.java index c03b84fe579..5e6ca7de8bd 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/Size.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/Size.java @@ -41,9 +41,17 @@ public class Size { return value; } + public String toFileContent() { return toString() + '\n'; } + @Override public String toString() { return max ? MAX : Long.toString(value); } + public boolean isGreaterThan(Size that) { + if (that.max) return false; + if (this.max) return true; + return this.value > that.value; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java index 101b90203f6..b9e7ce56c53 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java @@ -2,7 +2,7 @@ package com.yahoo.vespa.hosted.node.admin.container; import com.yahoo.config.provision.DockerImage; -import com.yahoo.vespa.hosted.node.admin.cgroup.ControlGroup; +import com.yahoo.vespa.hosted.node.admin.cgroup.Cgroup; import com.yahoo.vespa.hosted.node.admin.component.TaskContext; import com.yahoo.vespa.hosted.node.admin.container.image.ContainerImageDownloader; import com.yahoo.vespa.hosted.node.admin.container.image.ContainerImagePruner; @@ -34,7 +34,7 @@ public class ContainerOperations { private final ContainerImagePruner imagePruner; private final ContainerStatsCollector containerStatsCollector; - public ContainerOperations(ContainerEngine containerEngine, ControlGroup cgroup, FileSystem fileSystem) { + public ContainerOperations(ContainerEngine containerEngine, Cgroup cgroup, FileSystem fileSystem) { this.containerEngine = Objects.requireNonNull(containerEngine); this.imageDownloader = new ContainerImageDownloader(containerEngine); this.imagePruner = new ContainerImagePruner(containerEngine, Clock.systemUTC()); diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollector.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollector.java index adbfc52d397..8244666f9e0 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollector.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollector.java @@ -1,7 +1,7 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.node.admin.container; -import com.yahoo.vespa.hosted.node.admin.cgroup.ControlGroup; +import com.yahoo.vespa.hosted.node.admin.cgroup.Cgroup; import com.yahoo.vespa.hosted.node.admin.cgroup.CpuController; import com.yahoo.vespa.hosted.node.admin.cgroup.Size; import com.yahoo.vespa.hosted.node.admin.cgroup.MemoryController; @@ -32,14 +32,14 @@ class ContainerStatsCollector { private final ContainerEngine containerEngine; private final FileSystem fileSystem; - private final ControlGroup rootCgroup; + private final Cgroup rootCgroup; private final int onlineCpus; - ContainerStatsCollector(ContainerEngine containerEngine, ControlGroup rootCgroup, FileSystem fileSystem) { + ContainerStatsCollector(ContainerEngine containerEngine, Cgroup rootCgroup, FileSystem fileSystem) { this(containerEngine, rootCgroup, fileSystem, Runtime.getRuntime().availableProcessors()); } - ContainerStatsCollector(ContainerEngine containerEngine, ControlGroup rootCgroup, FileSystem fileSystem, int onlineCpus) { + ContainerStatsCollector(ContainerEngine containerEngine, Cgroup rootCgroup, FileSystem fileSystem, int onlineCpus) { this.containerEngine = Objects.requireNonNull(containerEngine); this.fileSystem = Objects.requireNonNull(fileSystem); this.rootCgroup = Objects.requireNonNull(rootCgroup); diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java index e6786b37b93..f3d0e5d0000 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java @@ -104,6 +104,18 @@ public class UnixPath { return uncheck(() -> Files.readAllLines(path)); } + /** Create an empty file and return true, or false if the file already exists (the file may not be regular). */ + public boolean touch() { + try { + Files.createFile(path); + return true; + } catch (FileAlreadyExistsException ignored) { + return false; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + public UnixPath writeUtf8File(String content, OpenOption... options) { return writeBytes(content.getBytes(StandardCharsets.UTF_8), options); } @@ -214,15 +226,16 @@ public class UnixPath { return this; } - /** Create directory with given permissions, unless it already exists, and return this. */ - public UnixPath createDirectory(String... permissions) { + /** Create directory with given permissions and return true, or false if it already exists. */ + public boolean createDirectory(String... permissions) { try { Files.createDirectory(path, permissionsAsFileAttributes(permissions)); } catch (FileAlreadyExistsException ignore) { + return false; } catch (IOException e) { throw new UncheckedIOException(e); } - return this; + return true; } public UnixPath createDirectories(String... permissions) { diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/cgroup/ControlGroupTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/cgroup/CgroupTest.java index 7c040f4ed06..27580082020 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/cgroup/ControlGroupTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/cgroup/CgroupTest.java @@ -30,12 +30,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author freva */ -public class ControlGroupTest { +public class CgroupTest { private static final ContainerId containerId = new ContainerId("4aec78cc"); private final FileSystem fileSystem = TestFileSystem.create(); - private final ControlGroup containerCgroup = ControlGroup.root(fileSystem).resolveContainer(containerId); + private final Cgroup containerCgroup = Cgroup.root(fileSystem).resolveContainer(containerId); private final CpuController containerCpu = containerCgroup.cpu(); private final NodeAgentContext context = NodeAgentContextImpl.builder("node123.yahoo.com").fileSystem(fileSystem).build(); private final UnixPath cgroupRoot = new UnixPath(fileSystem.getPath("/sys/fs/cgroup/machine.slice/libpod-4aec78cc.scope/container")).createDirectories(); @@ -54,11 +54,11 @@ public class ControlGroupTest { assertTrue(containerCgroup.cpu().updateMax(context, 654, 123456)); assertEquals(Optional.of(new CpuController.Max(Size.from(654), 123456)), containerCpu.readMax()); - assertEquals("654 123456", cgroupRoot.resolve("cpu.max").readUtf8File()); + assertEquals("654 123456\n", cgroupRoot.resolve("cpu.max").readUtf8File()); assertTrue(containerCgroup.cpu().updateMax(context, -1, 123456)); assertEquals(Optional.of(new CpuController.Max(Size.max(), 123456)), containerCpu.readMax()); - assertEquals("max 123456", cgroupRoot.resolve("cpu.max").readUtf8File()); + assertEquals("max 123456\n", cgroupRoot.resolve("cpu.max").readUtf8File()); } @Test diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperationsTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperationsTest.java index 5dbc2128051..09542c9c10a 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperationsTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperationsTest.java @@ -2,7 +2,7 @@ package com.yahoo.vespa.hosted.node.admin.container; import com.yahoo.config.provision.DockerImage; -import com.yahoo.vespa.hosted.node.admin.cgroup.ControlGroup; +import com.yahoo.vespa.hosted.node.admin.cgroup.Cgroup; import com.yahoo.vespa.hosted.node.admin.component.TestTaskContext; import com.yahoo.vespa.test.file.TestFileSystem; import org.junit.jupiter.api.Test; @@ -26,7 +26,7 @@ public class ContainerOperationsTest { private final TestTaskContext context = new TestTaskContext(); private final ContainerEngineMock containerEngine = new ContainerEngineMock(); private final FileSystem fileSystem = TestFileSystem.create(); - private final ContainerOperations containerOperations = new ContainerOperations(containerEngine, mock(ControlGroup.class), fileSystem); + private final ContainerOperations containerOperations = new ContainerOperations(containerEngine, mock(Cgroup.class), fileSystem); @Test void no_managed_containers_running() { diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollectorTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollectorTest.java index 59110ef4bd2..d4598c8923f 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollectorTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollectorTest.java @@ -1,7 +1,7 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.node.admin.container; -import com.yahoo.vespa.hosted.node.admin.cgroup.ControlGroup; +import com.yahoo.vespa.hosted.node.admin.cgroup.Cgroup; import com.yahoo.vespa.hosted.node.admin.cgroup.Size; import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec; import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext; @@ -40,7 +40,7 @@ public class ContainerStatsCollectorTest { private final TestTerminal testTerminal = new TestTerminal(); private final ContainerEngineMock containerEngine = new ContainerEngineMock(testTerminal); private final FileSystem fileSystem = TestFileSystem.create(); - private final ControlGroup cgroup = mock(ControlGroup.class, Answers.RETURNS_DEEP_STUBS); + private final Cgroup cgroup = mock(Cgroup.class, Answers.RETURNS_DEEP_STUBS); private final NodeAgentContext context = NodeAgentContextImpl.builder(NodeSpec.Builder.testSpec("c1").build()) .fileSystem(TestFileSystem.create()) .build(); diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerTester.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerTester.java index 990b8c5997b..1fe6081f1b7 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerTester.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerTester.java @@ -5,7 +5,7 @@ import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.flags.InMemoryFlagSource; -import com.yahoo.vespa.hosted.node.admin.cgroup.ControlGroup; +import com.yahoo.vespa.hosted.node.admin.cgroup.Cgroup; import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec; import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator; import com.yahoo.vespa.hosted.node.admin.container.ContainerEngineMock; @@ -60,7 +60,7 @@ public class ContainerTester implements AutoCloseable { private final ContainerEngineMock containerEngine = new ContainerEngineMock(); private final FileSystem fileSystem = TestFileSystem.create(); - final ContainerOperations containerOperations = spy(new ContainerOperations(containerEngine, mock(ControlGroup.class), fileSystem)); + final ContainerOperations containerOperations = spy(new ContainerOperations(containerEngine, mock(Cgroup.class), fileSystem)); final NodeRepoMock nodeRepository = spy(new NodeRepoMock()); final Orchestrator orchestrator = mock(Orchestrator.class); final StorageMaintainer storageMaintainer = mock(StorageMaintainer.class); |