summaryrefslogtreecommitdiffstats
path: root/node-admin
diff options
context:
space:
mode:
authorHåkon Hallingstad <hakon@yahooinc.com>2023-04-28 15:29:42 +0200
committerHåkon Hallingstad <hakon@yahooinc.com>2023-04-28 15:29:42 +0200
commit631b2228210c23b2cd3bc97e1a4090547b5576a8 (patch)
treefea67dcd27093c208e8e2df6c15db7e75aa0a4ab /node-admin
parent75e261266c3629e4343f40f1aa26fc2dc02c9aa3 (diff)
Use Cgroup in CgroupLimitsTask, CgroupV2Task, MiscHostMetrics, Podman
Diffstat (limited to 'node-admin')
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/Cgroup.java163
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/CgroupCore.java34
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/ControlGroup.java89
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/CpuController.java45
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/MemoryController.java37
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/cgroup/Size.java8
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java4
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollector.java8
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java19
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/cgroup/CgroupTest.java (renamed from node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/cgroup/ControlGroupTest.java)8
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperationsTest.java4
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollectorTest.java4
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerTester.java4
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);