aboutsummaryrefslogtreecommitdiffstats
path: root/node-admin/src/test/java
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2021-06-30 11:17:20 +0200
committerMartin Polden <mpolden@mpolden.no>2021-06-30 11:37:10 +0200
commit513f5bd3b709a6c524efee7ed86d17e1d0899efe (patch)
treefe19f85dd842c450de7dcf913e465416bc55ad33 /node-admin/src/test/java
parent732a55c8508ce437e7d7fe5c50285b6a082bff44 (diff)
Container cleanup
This PR puts all container-related code, which is independent of the container engine implementation, in `com.yahoo.vespa.hosted.node.admin.container`. * All operations on containers still pass through `ContainerOperations` * `ContainerOperations` is no longer an interface * `ContainerEngine` is the interface for a container engine/runtime (e.g. Podman) * Some code was migrated from internal repo. This is the reason for the large-ish diff * Fixes problematic circular dependencies
Diffstat (limited to 'node-admin/src/test/java')
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerEngineMock.java206
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperationsTest.java64
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollectorTest.java151
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ProcessResultTest.java24
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/image/ContainerImageDownloaderTest.java34
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/image/ContainerImagePrunerTest.java238
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerOperationsMock.java155
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerTester.java11
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollectorTest.java10
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java14
10 files changed, 713 insertions, 194 deletions
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerEngineMock.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerEngineMock.java
new file mode 100644
index 00000000000..1d077449ed6
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerEngineMock.java
@@ -0,0 +1,206 @@
+// Copyright Verizon Media. 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.config.provision.DockerImage;
+import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
+import com.yahoo.vespa.hosted.node.admin.container.image.Image;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.ContainerData;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
+import com.yahoo.vespa.hosted.node.admin.task.util.process.CommandResult;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.stream.Collectors;
+
+/**
+ * @author mpolden
+ */
+public class ContainerEngineMock implements ContainerEngine {
+
+ private final Map<ContainerName, Container> containers = new ConcurrentHashMap<>();
+ private final Map<String, ImageDownload> images = new ConcurrentHashMap<>();
+ private boolean asyncImageDownload = false;
+
+ public ContainerEngineMock asyncImageDownload(boolean enabled) {
+ this.asyncImageDownload = enabled;
+ return this;
+ }
+
+ public ContainerEngineMock completeDownloadOf(DockerImage image) {
+ String imageId = image.asString();
+ ImageDownload download;
+ while ((download = images.get(imageId)) == null);
+ download.complete();
+ return this;
+ }
+
+ public ContainerEngineMock setImages(List<Image> images) {
+ this.images.clear();
+ for (var image : images) {
+ ImageDownload imageDownload = new ImageDownload(image);
+ imageDownload.complete();
+ this.images.put(image.id(), imageDownload);
+ }
+ return this;
+ }
+
+ public ContainerEngineMock addContainers(List<Container> containers) {
+ for (var container : containers) {
+ if (this.containers.containsKey(container.name())) {
+ throw new IllegalArgumentException("Container " + container.name() + " already exists");
+ }
+ this.containers.put(container.name(), container);
+ }
+ return this;
+ }
+
+ public ContainerEngineMock addContainer(Container container) {
+ return addContainers(List.of(container));
+ }
+
+ @Override
+ public void createContainer(NodeAgentContext context, ContainerData containerData, ContainerResources containerResources) {
+ addContainer(createContainer(context, PartialContainer.State.created, containerResources));
+ }
+
+ @Override
+ public void startContainer(NodeAgentContext context) {
+ Container container = requireContainer(context.containerName(), PartialContainer.State.created);
+ Container newContainer = createContainer(context, PartialContainer.State.running, container.resources());
+ containers.put(newContainer.name(), newContainer);
+ }
+
+ @Override
+ public void removeContainer(TaskContext context, PartialContainer container) {
+ requireContainer(container.name());
+ containers.remove(container.name());
+ }
+
+ @Override
+ public void updateContainer(NodeAgentContext context, ContainerId containerId, ContainerResources containerResources) {
+ Container container = requireContainer(context.containerName());
+ containers.put(container.name(), new Container(containerId, container.name(), container.state(),
+ container.imageId(), container.image(),
+ container.labels(), container.pid(),
+ container.conmonPid(), container.hostname(),
+ containerResources, container.networks(),
+ container.managed()));
+ }
+
+ @Override
+ public Optional<Container> getContainer(NodeAgentContext context) {
+ return Optional.ofNullable(containers.get(context.containerName()));
+ }
+
+ @Override
+ public List<PartialContainer> listContainers(TaskContext context) {
+ return List.copyOf(containers.values());
+ }
+
+ @Override
+ public String networkInterface(NodeAgentContext context) {
+ return "eth0";
+ }
+
+ @Override
+ public CommandResult executeAsRoot(NodeAgentContext context, Duration timeout, String... command) {
+ return new CommandResult(null, 0, "");
+ }
+
+ @Override
+ public CommandResult executeInNetworkNamespace(NodeAgentContext context, String... command) {
+ return new CommandResult(null, 0, "");
+ }
+
+ @Override
+ public void pullImage(TaskContext context, DockerImage image, RegistryCredentials registryCredentials) {
+ String imageId = image.asString();
+ ImageDownload imageDownload = images.computeIfAbsent(imageId, (ignored) -> new ImageDownload(new Image(imageId, Optional.empty(), List.of(imageId))));
+ if (!asyncImageDownload) {
+ imageDownload.complete();
+ }
+ imageDownload.awaitCompletion();
+ }
+
+ @Override
+ public boolean hasImage(TaskContext context, DockerImage image) {
+ ImageDownload download = images.get(image.asString());
+ return download != null && download.isComplete();
+ }
+
+ @Override
+ public void removeImage(TaskContext context, String id) {
+ images.remove(id);
+ }
+
+ @Override
+ public List<Image> listImages(TaskContext context) {
+ return images.values().stream()
+ .filter(ImageDownload::isComplete)
+ .map(ImageDownload::image)
+ .collect(Collectors.toUnmodifiableList());
+ }
+
+ private Container requireContainer(ContainerName name) {
+ return requireContainer(name, null);
+ }
+
+ private Container requireContainer(ContainerName name, PartialContainer.State wantedState) {
+ Container container = containers.get(name);
+ if (container == null) throw new IllegalArgumentException("No such container: " + name);
+ if (wantedState != null && container.state() != wantedState) throw new IllegalArgumentException("Container is " + container.state() + ", wanted " + wantedState);
+ return container;
+ }
+
+ public Container createContainer(NodeAgentContext context, PartialContainer.State state, ContainerResources containerResources) {
+ return new Container(new ContainerId("id-of-" + context.containerName()),
+ context.containerName(),
+ state,
+ "image-id",
+ context.node().wantedDockerImage().get(),
+ Map.of(),
+ 41,
+ 42,
+ context.hostname().value(),
+ containerResources,
+ List.of(),
+ true);
+ }
+
+ private static class ImageDownload {
+
+ private final Image image;
+ private final CountDownLatch done = new CountDownLatch(1);
+
+ ImageDownload(Image image) {
+ this.image = Objects.requireNonNull(image);
+ }
+
+ Image image() {
+ return image;
+ }
+
+ boolean isComplete() {
+ return done.getCount() == 0;
+ }
+
+ void complete() {
+ done.countDown();
+ }
+
+ void awaitCompletion() {
+ try {
+ done.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ }
+
+}
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
new file mode 100644
index 00000000000..666c5fb31f9
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperationsTest.java
@@ -0,0 +1,64 @@
+// Copyright Verizon Media. 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.config.provision.DockerImage;
+import com.yahoo.vespa.hosted.node.admin.component.TestTaskContext;
+import com.yahoo.vespa.test.file.TestFileSystem;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author mpolden
+ */
+public class ContainerOperationsTest {
+
+ private final TestTaskContext context = new TestTaskContext();
+ private final ContainerEngineMock containerEngine = new ContainerEngineMock();
+ private final ContainerOperations containerOperations = new ContainerOperations(containerEngine, TestFileSystem.create());
+
+ @Test
+ public void no_managed_containers_running() {
+ Container c1 = createContainer("c1", true);
+ Container c2 = createContainer("c2", false);
+
+ containerEngine.addContainer(c1);
+ assertFalse(containerOperations.noManagedContainersRunning(context));
+
+ containerEngine.removeContainer(context, c1);
+ assertTrue(containerOperations.noManagedContainersRunning(context));
+
+ containerEngine.addContainer(c2);
+ assertTrue(containerOperations.noManagedContainersRunning(context));
+ }
+
+ @Test
+ public void retain_managed_containers() {
+ Container c1 = createContainer("c1", true);
+ Container c2 = createContainer("c2", true);
+ Container c3 = createContainer("c3", false);
+ containerEngine.addContainers(List.of(c1, c2, c3));
+
+ assertEquals(3, containerEngine.listContainers(context).size());
+ containerOperations.retainManagedContainers(context, Set.of(c1.name()));
+
+ assertEquals(List.of(c1.name(), c3.name()), containerEngine.listContainers(context).stream()
+ .map(PartialContainer::name)
+ .sorted()
+ .collect(Collectors.toList()));
+ }
+
+ private Container createContainer(String name, boolean managed) {
+ return new Container(new ContainerId("id-of-" + name), new ContainerName(name), PartialContainer.State.running,
+ "image-id", DockerImage.EMPTY, Map.of(), 42, 43, name,
+ ContainerResources.UNLIMITED, List.of(), managed);
+ }
+
+}
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
new file mode 100644
index 00000000000..0bb7aee8e0a
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollectorTest.java
@@ -0,0 +1,151 @@
+// Copyright Verizon Media. 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.test.file.TestFileSystem;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author mpolden
+ */
+public class ContainerStatsCollectorTest {
+
+ private final FileSystem fileSystem = TestFileSystem.create();
+
+ @Test
+ public void collect() throws Exception {
+ ContainerStatsCollector collector = new ContainerStatsCollector(fileSystem);
+ ContainerId containerId = new ContainerId("id1");
+ int containerPid = 42;
+ assertTrue("No stats found", collector.collect(containerId, containerPid, "eth0").isEmpty());
+
+ mockMemoryStats(containerId);
+ mockCpuStats(containerId);
+ mockNetworkStats(containerPid);
+
+ Optional<ContainerStats> stats = collector.collect(containerId, containerPid, "eth0");
+ assertTrue(stats.isPresent());
+ assertEquals(new ContainerStats.CpuStats(24, 6049374780000000L, 691675615472L,
+ 262190000000L, 3L, 1L, 2L),
+ stats.get().getCpuStats());
+ assertEquals(new ContainerStats.MemoryStats(470790144L, 1228017664L, 2147483648L),
+ stats.get().getMemoryStats());
+ assertEquals(Map.of("eth0", new ContainerStats.NetworkStats(22280813L, 4L, 3L,
+ 19859383L, 6L, 5L)),
+ stats.get().getNetworks());
+ }
+
+ private void mockNetworkStats(int pid) throws IOException {
+ Path dev = fileSystem.getPath("/proc/" + pid + "/net/dev");
+ Files.createDirectories(dev.getParent());
+ Files.writeString(dev, "Inter-| Receive | Transmit\n" +
+ " face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed\n" +
+ " lo: 36289258 149700 0 0 0 0 0 0 36289258 149700 0 0 0 0 0 0\n" +
+ " eth0: 22280813 118083 3 4 0 0 0 0 19859383 115415 5 6 0 0 0 0\n");
+ }
+
+ private void mockMemoryStats(ContainerId containerId) throws IOException {
+ Path root = fileSystem.getPath("/sys/fs/cgroup/memory/machine.slice/libpod-" + containerId + ".scope");
+ Files.createDirectories(root);
+
+ Files.writeString(root.resolve("memory.limit_in_bytes"), "2147483648\n");
+ Files.writeString(root.resolve("memory.usage_in_bytes"), "1228017664\n");
+ Files.writeString(root.resolve("memory.stat"), "cache 470790144\n" +
+ "rss 698699776\n" +
+ "rss_huge 526385152\n" +
+ "shmem 0\n" +
+ "mapped_file 811008\n" +
+ "dirty 405504\n" +
+ "writeback 0\n" +
+ "swap 0\n" +
+ "pgpgin 6938085\n" +
+ "pgpgout 6780573\n" +
+ "pgfault 14343186\n" +
+ "pgmajfault 0\n" +
+ "inactive_anon 0\n" +
+ "active_anon 699289600\n" +
+ "inactive_file 455516160\n" +
+ "active_file 13787136\n" +
+ "unevictable 0\n" +
+ "hierarchical_memory_limit 2147483648\n" +
+ "hierarchical_memsw_limit 4294967296\n" +
+ "total_cache 470790144\n" +
+ "total_rss 698699776\n" +
+ "total_rss_huge 526385152\n" +
+ "total_shmem 0\n" +
+ "total_mapped_file 811008\n" +
+ "total_dirty 405504\n" +
+ "total_writeback 0\n" +
+ "total_swap 0\n" +
+ "total_pgpgin 6938085\n" +
+ "total_pgpgout 6780573\n" +
+ "total_pgfault 14343186\n" +
+ "total_pgmajfault 0\n" +
+ "total_inactive_anon 0\n" +
+ "total_active_anon 699289600\n" +
+ "total_inactive_file 455516160\n" +
+ "total_active_file 13787136\n" +
+ "total_unevictable 0\n");
+ }
+
+ private void mockCpuStats(ContainerId containerId) throws IOException {
+ Path root = fileSystem.getPath("/sys/fs/cgroup/cpuacct/machine.slice/libpod-" + containerId + ".scope");
+ Path proc = fileSystem.getPath("/proc");
+ Files.createDirectories(root);
+ Files.createDirectories(proc);
+ Files.writeString(root.resolve("cpu.stat"), "nr_periods 1\n" +
+ "nr_throttled 2\n" +
+ "throttled_time 3\n");
+ Files.writeString(root.resolve("cpuacct.usage_percpu"), "25801608855 22529436415 25293652376 26212081533 " +
+ "27545883290 25357818592 33464821448 32568003867 " +
+ "28916742231 31771772292 34418037242 38417072233 " +
+ "26069101127 24568838237 23683334366 26824607997 " +
+ "24289870206 22249389818 32683986446 32444831154 " +
+ "30488394217 26840956322 31633747261 30838696584\n");
+ Files.writeString(root.resolve("cpuacct.usage"), "691675615472\n");
+ Files.writeString(root.resolve("cpuacct.stat"), "user 40900\n" +
+ "system 26219\n");
+ Files.writeString(proc.resolve("stat"), "cpu 7991366 978222 2346238 565556517 1935450 25514479 615206 0 0 0\n" +
+ "cpu0 387906 61529 99088 23516506 42258 1063359 29882 0 0 0\n" +
+ "cpu1 271253 49383 86149 23655234 41703 1061416 31885 0 0 0\n" +
+ "cpu2 349420 50987 93560 23571695 59437 1051977 24461 0 0 0\n" +
+ "cpu3 328107 50628 93406 23605135 44378 1048549 30199 0 0 0\n" +
+ "cpu4 267474 50404 99253 23606041 113094 1038572 26494 0 0 0\n" +
+ "cpu5 309584 50677 94284 23550372 132616 1033661 29436 0 0 0\n" +
+ "cpu6 477926 56888 121251 23367023 83121 1074930 28818 0 0 0\n" +
+ "cpu7 335335 29350 106130 23551107 95606 1066394 26156 0 0 0\n" +
+ "cpu8 323678 28629 99171 23586501 82183 1064708 25403 0 0 0\n" +
+ "cpu9 329805 27516 98538 23579458 89235 1061561 25140 0 0 0\n" +
+ "cpu10 291536 26455 93934 23642345 81282 1049736 25228 0 0 0\n" +
+ "cpu11 271103 25302 90630 23663641 85711 1048781 24291 0 0 0\n" +
+ "cpu12 323634 63392 100406 23465340 132684 1089157 28319 0 0 0\n" +
+ "cpu13 348085 49568 100772 23490388 114190 1079474 20948 0 0 0\n" +
+ "cpu14 310712 51208 90461 23547980 101601 1071940 26712 0 0 0\n" +
+ "cpu15 360405 52754 94620 23524878 79851 1062050 26836 0 0 0\n" +
+ "cpu16 367893 52141 98074 23541314 57500 1058968 25242 0 0 0\n" +
+ "cpu17 412756 51486 101592 23515056 47653 1044874 27467 0 0 0\n" +
+ "cpu18 287307 25478 106011 23599505 79848 1089812 23160 0 0 0\n" +
+ "cpu19 275001 24421 98338 23628694 79675 1084074 22083 0 0 0\n" +
+ "cpu20 288038 24805 94432 23629908 74735 1078501 21915 0 0 0\n" +
+ "cpu21 295373 25017 91344 23628585 75282 1071019 22026 0 0 0\n" +
+ "cpu22 326739 25588 90385 23608217 69186 1068494 21108 0 0 0\n" +
+ "cpu23 452284 24602 104397 23481583 72612 1052462 21985 0 0 0\n" +
+ "intr 6645352968 64 0 0 0 1481 0 0 0 1 0 0 0 0 0 0 0 39 0 0 0 0 0 0 37 0 0 0 0 0 0 0 0 4334106 1 6949071 5814662 5415344 6939471 6961483 6358810 5271953 6718644 0 126114 126114 126114 126114 126114 126114 126114 126114 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" +
+ "ctxt 2495530303\n" +
+ "btime 1611928223\n" +
+ "processes 4839481\n" +
+ "procs_running 4\n" +
+ "procs_blocked 0\n" +
+ "softirq 2202631388 4 20504999 46734 54405637 4330276 0 6951 1664780312 10130 458546345\n");
+ }
+
+}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ProcessResultTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ProcessResultTest.java
deleted file mode 100644
index f7b832bd566..00000000000
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ProcessResultTest.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2017 Yahoo Holdings. 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 org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-public class ProcessResultTest {
- @Test
- public void testBasicProperties() {
- ProcessResult processResult = new ProcessResult(0, "foo", "bar");
- assertEquals(0, processResult.getExitStatus());
- assertEquals("foo", processResult.getOutput());
- assertTrue(processResult.isSuccess());
- }
-
- @Test
- public void testSuccessFails() {
- ProcessResult processResult = new ProcessResult(1, "foo", "bar");
- assertFalse(processResult.isSuccess());
- }
-}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/image/ContainerImageDownloaderTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/image/ContainerImageDownloaderTest.java
new file mode 100644
index 00000000000..f50f2b8b053
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/image/ContainerImageDownloaderTest.java
@@ -0,0 +1,34 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.container.image;
+
+import com.yahoo.config.provision.DockerImage;
+import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
+import com.yahoo.vespa.hosted.node.admin.component.TestTaskContext;
+import com.yahoo.vespa.hosted.node.admin.container.ContainerEngineMock;
+import com.yahoo.vespa.hosted.node.admin.container.RegistryCredentials;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author mpolden
+ */
+public class ContainerImageDownloaderTest {
+
+ @Test(timeout = 5_000)
+ public void test_download() {
+ ContainerEngineMock podman = new ContainerEngineMock().asyncImageDownload(true);
+ ContainerImageDownloader downloader = new ContainerImageDownloader(podman);
+ TaskContext context = new TestTaskContext();
+ DockerImage image = DockerImage.fromString("registry.example.com/vespa:7.42");
+
+ assertFalse("Download started", downloader.get(context, image, RegistryCredentials.none));
+ assertFalse("Download pending", downloader.get(context, image, RegistryCredentials.none));
+ podman.completeDownloadOf(image);
+ boolean downloadCompleted;
+ while (!(downloadCompleted = downloader.get(context, image, RegistryCredentials.none)));
+ assertTrue("Download completed", downloadCompleted);
+ }
+
+}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/image/ContainerImagePrunerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/image/ContainerImagePrunerTest.java
new file mode 100644
index 00000000000..f6d941c4299
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/image/ContainerImagePrunerTest.java
@@ -0,0 +1,238 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.container.image;
+
+import com.yahoo.config.provision.DockerImage;
+import com.yahoo.test.ManualClock;
+import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
+import com.yahoo.vespa.hosted.node.admin.component.TestTaskContext;
+import com.yahoo.vespa.hosted.node.admin.container.Container;
+import com.yahoo.vespa.hosted.node.admin.container.ContainerEngineMock;
+import com.yahoo.vespa.hosted.node.admin.container.ContainerId;
+import com.yahoo.vespa.hosted.node.admin.container.ContainerName;
+import com.yahoo.vespa.hosted.node.admin.container.ContainerResources;
+import com.yahoo.vespa.hosted.node.admin.container.image.ContainerImagePruner;
+import com.yahoo.vespa.hosted.node.admin.container.image.Image;
+import org.junit.Test;
+
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author freva
+ * @author mpolden
+ */
+public class ContainerImagePrunerTest {
+
+ private final Tester tester = new Tester();
+
+ @Test
+ public void noImagesMeansNoUnusedImages() {
+ tester.withExistingImages()
+ .expectDeletedImages();
+ }
+
+ @Test
+ public void singleImageWithoutContainersIsUnused() {
+ tester.withExistingImages(image("image-1"))
+ // Even though nothing is using the image, we will keep it for at least 1h
+ .expectDeletedImagesAfterMinutes(0)
+ .expectDeletedImagesAfterMinutes(30)
+ .expectDeletedImagesAfterMinutes(30, "image-1");
+ }
+
+ @Test
+ public void singleImageWithContainerIsUsed() {
+ tester.withExistingImages(image("image-1"))
+ .withExistingContainers(container("container-1", "image-1"))
+ .expectDeletedImages();
+ }
+
+
+ @Test
+ public void multipleUnusedImagesAreIdentified() {
+ tester.withExistingImages(image("image-1"), image("image-2"))
+ .expectDeletedImages("image-1", "image-2");
+ }
+
+
+ @Test
+ public void multipleUnusedLeavesAreIdentified() {
+ tester.withExistingImages(image("parent-image"),
+ image("image-1", "parent-image"),
+ image("image-2", "parent-image"))
+ .expectDeletedImages("image-1", "image-2", "parent-image");
+ }
+
+
+ @Test
+ public void unusedLeafWithUsedSiblingIsIdentified() {
+ tester.withExistingImages(image("parent-image"),
+ image("image-1", "parent-image", "latest"),
+ image("image-2", "parent-image", "1.24"))
+ .withExistingContainers(container("vespa-node-1", "image-1"))
+ .expectDeletedImages("1.24"); // Deleting the only tag will delete the image
+ }
+
+
+ @Test
+ public void unusedImagesWithMultipleTags() {
+ tester.withExistingImages(image("parent-image"),
+ image("image-1", "parent-image", "vespa-6", "vespa-6.28", "vespa:latest"))
+ .expectDeletedImages("vespa-6", "vespa-6.28", "vespa:latest", "parent-image");
+ }
+
+
+ @Test
+ public void unusedImagesWithMultipleUntagged() {
+ tester.withExistingImages(image("image1", null, "<none>:<none>"),
+ image("image2", null, "<none>:<none>"))
+ .expectDeletedImages("image1", "image2");
+ }
+
+
+ @Test
+ public void taggedImageWithNoContainersIsUnused() {
+ tester.withExistingImages(image("image-1", null, "vespa-6"))
+ .expectDeletedImages("vespa-6");
+ }
+
+
+ @Test
+ public void unusedImagesWithSimpleImageGc() {
+ tester.withExistingImages(image("parent-image"))
+ .expectDeletedImagesAfterMinutes(30)
+ .withExistingImages(image("parent-image"),
+ image("image-1", "parent-image"))
+ .expectDeletedImagesAfterMinutes(0)
+ .expectDeletedImagesAfterMinutes(30)
+ // At this point, parent-image has been unused for 1h, but image-1 depends on parent-image and it has
+ // only been unused for 30m, so we cannot delete parent-image yet. 30 mins later both can be removed
+ .expectDeletedImagesAfterMinutes(30, "image-1", "parent-image");
+ }
+
+
+ @Test
+ public void reDownloadingImageIsNotImmediatelyDeleted() {
+ tester.withExistingImages(image("image"))
+ .expectDeletedImages("image") // After 1h we delete image
+ .expectDeletedImagesAfterMinutes(0) // image is immediately re-downloaded, but is not deleted
+ .expectDeletedImagesAfterMinutes(10)
+ .expectDeletedImages("image"); // 1h after re-download it is deleted again
+ }
+
+
+ @Test
+ public void reDownloadingImageIsNotImmediatelyDeletedWhenDeletingByTag() {
+ tester.withExistingImages(image("image", null, "image-1", "my-tag"))
+ .expectDeletedImages("image-1", "my-tag") // After 1h we delete image
+ .expectDeletedImagesAfterMinutes(0) // image is immediately re-downloaded, but is not deleted
+ .expectDeletedImagesAfterMinutes(10)
+ .expectDeletedImages("image-1", "my-tag"); // 1h after re-download it is deleted again
+ }
+
+ /** Same scenario as in {@link #multipleUnusedImagesAreIdentified()} */
+ @Test
+ public void doesNotDeleteExcludedByIdImages() {
+ tester.withExistingImages(image("parent-image"),
+ image("image-1", "parent-image"),
+ image("image-2", "parent-image"))
+ // Normally, image-1 and parent-image should also be deleted, but because we exclude image-1
+ // we cannot delete parent-image, so only image-2 is deleted
+ .expectDeletedImages(List.of("image-1"), "image-2");
+ }
+
+ /** Same as in {@link #doesNotDeleteExcludedByIdImages()} but with tags */
+ @Test
+ public void doesNotDeleteExcludedByTagImages() {
+ tester.withExistingImages(image("parent-image", "rhel-6"),
+ image("image-1", "parent-image", "vespa:6.288.16"),
+ image("image-2", "parent-image", "vespa:6.289.94"))
+ .expectDeletedImages(List.of("vespa:6.288.16"), "vespa:6.289.94");
+ }
+
+ @Test
+ public void exludingNotDownloadedImageIsNoop() {
+ tester.withExistingImages(image("parent-image", "rhel-6"),
+ image("image-1", "parent-image", "vespa:6.288.16"),
+ image("image-2", "parent-image", "vespa:6.289.94"))
+ .expectDeletedImages(List.of("vespa:6.300.1"), "vespa:6.288.16", "vespa:6.289.94", "rhel-6");
+ }
+
+ private static Image image(String id) {
+ return image(id, null);
+ }
+
+ private static Image image(String id, String parentId, String... tags) {
+ return new Image(id, Optional.ofNullable(parentId), List.of(tags));
+ }
+
+ private static Container container(String name, String imageId) {
+ return new Container(new ContainerId("id-of-" + name), new ContainerName(name),
+ Container.State.running, imageId, DockerImage.EMPTY, Map.of(),
+ 42, 43, name + ".example.com", ContainerResources.UNLIMITED,
+ List.of(), true);
+ }
+
+ private static class Tester {
+
+ private final ContainerEngineMock containerEngine = new ContainerEngineMock();
+ private final TaskContext context = new TestTaskContext();
+ private final ManualClock clock = new ManualClock();
+ private final ContainerImagePruner pruner = new ContainerImagePruner(containerEngine, clock);
+ private final Map<String, Integer> removalCountByImageId = new HashMap<>();
+
+ private boolean initialized = false;
+
+ private Tester withExistingImages(Image... images) {
+ containerEngine.setImages(List.of(images));
+ return this;
+ }
+
+ private Tester withExistingContainers(Container... containers) {
+ containerEngine.addContainers(List.of(containers));
+ return this;
+ }
+
+ private Tester expectDeletedImages(String... imageIds) {
+ return expectDeletedImagesAfterMinutes(60, imageIds);
+ }
+
+ private Tester expectDeletedImages(List<String> excludedRefs, String... imageIds) {
+ return expectDeletedImagesAfterMinutes(60, excludedRefs, imageIds);
+ }
+
+ private Tester expectDeletedImagesAfterMinutes(int minutesAfter, String... imageIds) {
+ return expectDeletedImagesAfterMinutes(minutesAfter, Collections.emptyList(), imageIds);
+ }
+
+ private Tester expectDeletedImagesAfterMinutes(int minutesAfter, List<String> excludedRefs, String... imageIds) {
+ if (!initialized) {
+ // Run once with a very long expiry to initialize internal state of existing images
+ pruner.removeUnusedImages(context, List.of(), Duration.ofDays(999));
+ initialized = true;
+ }
+
+ clock.advance(Duration.ofMinutes(minutesAfter));
+
+ pruner.removeUnusedImages(context, excludedRefs, Duration.ofHours(1).minusSeconds(1));
+
+ Arrays.stream(imageIds)
+ .forEach(imageId -> {
+ int newValue = removalCountByImageId.getOrDefault(imageId, 0) + 1;
+ removalCountByImageId.put(imageId, newValue);
+
+ assertTrue("Image " + imageId + " removed",
+ containerEngine.listImages(context).stream().noneMatch(image -> image.id().equals(imageId)));
+ });
+ return this;
+ }
+ }
+
+}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerOperationsMock.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerOperationsMock.java
deleted file mode 100644
index a19e031e41f..00000000000
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integration/ContainerOperationsMock.java
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.integration;
-
-import com.yahoo.config.provision.DockerImage;
-import com.yahoo.vespa.hosted.node.admin.container.Container;
-import com.yahoo.vespa.hosted.node.admin.container.ContainerId;
-import com.yahoo.vespa.hosted.node.admin.container.ContainerName;
-import com.yahoo.vespa.hosted.node.admin.container.ContainerResources;
-import com.yahoo.vespa.hosted.node.admin.container.ContainerStats;
-import com.yahoo.vespa.hosted.node.admin.container.ProcessResult;
-import com.yahoo.vespa.hosted.node.admin.container.RegistryCredentials;
-import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
-import com.yahoo.vespa.hosted.node.admin.container.ContainerOperations;
-import com.yahoo.vespa.hosted.node.admin.nodeagent.ContainerData;
-import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
-import com.yahoo.vespa.hosted.node.admin.task.util.process.CommandResult;
-
-import java.time.Duration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-
-/**
- * Mock with some simple logic
- *
- * @author freva
- */
-public class ContainerOperationsMock implements ContainerOperations {
-
- public static final ContainerId CONTAINER_ID = new ContainerId("af345");
-
- private final Map<ContainerName, Container> containersByContainerName = new HashMap<>();
- private final Object monitor = new Object();
-
- @Override
- public void createContainer(NodeAgentContext context, ContainerData containerData, ContainerResources containerResources) {
- synchronized (monitor) {
- containersByContainerName.put(context.containerName(),
- new Container(CONTAINER_ID,
- context.hostname().value(),
- context.node().wantedDockerImage().get(),
- containerResources,
- context.containerName(),
- Container.State.CREATED,
- 2));
- }
- }
-
- @Override
- public void startContainer(NodeAgentContext context) {
- synchronized (monitor) {
- Optional<Container> container = getContainer(context);
- if (container.isEmpty()) throw new IllegalArgumentException("Cannot start non-existent container " + context.containerName());
- containersByContainerName.put(context.containerName(), new Container(CONTAINER_ID,
- container.get().hostname,
- container.get().image,
- container.get().resources,
- container.get().name,
- Container.State.RUNNING,
- container.get().pid));
- }
- }
-
- @Override
- public void removeContainer(NodeAgentContext context, Container container) {
- synchronized (monitor) {
- containersByContainerName.remove(container.name);
- }
- }
-
- @Override
- public void updateContainer(NodeAgentContext context, ContainerId containerId, ContainerResources containerResources) {
- synchronized (monitor) {
- Container container = containersByContainerName.get(context.containerName());
- containersByContainerName.put(context.containerName(),
- new Container(container.id(), container.hostname, container.image,
- containerResources, container.name, container.state, container.pid));
- }
- }
-
- @Override
- public Optional<Container> getContainer(NodeAgentContext context) {
- synchronized (monitor) {
- return Optional.ofNullable(containersByContainerName.get(context.containerName()));
- }
- }
-
- @Override
- public boolean pullImageAsyncIfNeeded(TaskContext context, DockerImage dockerImage, RegistryCredentials registryCredentials) {
- return false;
- }
-
- @Override
- public ProcessResult executeCommandInContainerAsRoot(NodeAgentContext context, String... command) {
- return null;
- }
-
- @Override
- public ProcessResult executeCommandInContainerAsRoot(NodeAgentContext context, Long timeoutSeconds, String... command) {
- return null;
- }
-
- @Override
- public CommandResult executeCommandInNetworkNamespace(NodeAgentContext context, String... command) {
- return null;
- }
-
- @Override
- public String resumeNode(NodeAgentContext context) {
- return "";
- }
-
- @Override
- public String suspendNode(NodeAgentContext context) {
- return "";
- }
-
- @Override
- public String restartVespa(NodeAgentContext context) {
- return "";
- }
-
- @Override
- public String startServices(NodeAgentContext context) {
- return "";
- }
-
- @Override
- public String stopServices(NodeAgentContext context) {
- return "";
- }
-
- @Override
- public Optional<ContainerStats> getContainerStats(NodeAgentContext context) {
- return Optional.empty();
- }
-
- @Override
- public boolean noManagedContainersRunning(TaskContext context) {
- return false;
- }
-
- @Override
- public boolean retainManagedContainers(TaskContext context, Set<ContainerName> containerNames) {
- return false;
- }
-
- @Override
- public boolean deleteUnusedContainerImages(TaskContext context, List<DockerImage> excludes, Duration minImageAgeToDelete) {
- return false;
- }
-
-}
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 9153afd8e54..4179f53370b 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
@@ -4,11 +4,13 @@ package com.yahoo.vespa.hosted.node.admin.integration;
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.configserver.noderepository.NodeSpec;
+import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator;
+import com.yahoo.vespa.hosted.node.admin.container.ContainerEngineMock;
import com.yahoo.vespa.hosted.node.admin.container.ContainerName;
+import com.yahoo.vespa.hosted.node.admin.container.ContainerOperations;
import com.yahoo.vespa.hosted.node.admin.container.RegistryCredentials;
import com.yahoo.vespa.hosted.node.admin.container.metrics.Metrics;
-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.maintenance.StorageMaintainer;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminImpl;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater;
@@ -23,8 +25,6 @@ import org.mockito.InOrder;
import org.mockito.Mockito;
import java.nio.file.FileSystem;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.time.Clock;
import java.time.Duration;
import java.util.Collections;
@@ -46,12 +46,11 @@ public class ContainerTester implements AutoCloseable {
private static final Logger log = Logger.getLogger(ContainerTester.class.getName());
private static final Duration INTERVAL = Duration.ofMillis(10);
- private static final Path PATH_TO_VESPA_HOME = Paths.get("/opt/vespa");
static final HostName HOST_HOSTNAME = HostName.from("host.test.yahoo.com");
private final Thread loopThread;
- final ContainerOperationsMock containerOperations = spy(new ContainerOperationsMock());
+ final ContainerOperations containerOperations = spy(new ContainerOperations(new ContainerEngineMock(), TestFileSystem.create()));
final NodeRepoMock nodeRepository = spy(new NodeRepoMock());
final Orchestrator orchestrator = mock(Orchestrator.class);
final StorageMaintainer storageMaintainer = mock(StorageMaintainer.class);
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollectorTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollectorTest.java
index 3c22305d006..cfb9aaa4001 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollectorTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollectorTest.java
@@ -1,10 +1,10 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.maintenance.coredump;
-import com.yahoo.vespa.hosted.node.admin.container.ProcessResult;
import com.yahoo.vespa.hosted.node.admin.container.ContainerOperations;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContextImpl;
+import com.yahoo.vespa.hosted.node.admin.task.util.process.CommandResult;
import org.junit.Test;
import java.nio.file.Path;
@@ -12,8 +12,8 @@ import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
-import static com.yahoo.vespa.hosted.node.admin.maintenance.coredump.CoreCollector.GDB_PATH_RHEL7_DT9;
import static com.yahoo.vespa.hosted.node.admin.maintenance.coredump.CoreCollector.GDB_PATH_RHEL7_DT10;
+import static com.yahoo.vespa.hosted.node.admin.maintenance.coredump.CoreCollector.GDB_PATH_RHEL7_DT9;
import static com.yahoo.vespa.hosted.node.admin.maintenance.coredump.CoreCollector.GDB_PATH_RHEL8;
import static com.yahoo.vespa.hosted.node.admin.maintenance.coredump.CoreCollector.JAVA_HEAP_DUMP_METADATA;
import static org.junit.Assert.assertEquals;
@@ -91,7 +91,7 @@ public class CoreCollectorTest {
coreCollector.readBinPathFallback(context, TEST_CORE_PATH);
fail("Expected not to be able to get bin path");
} catch (RuntimeException e) {
- assertEquals("Failed to extract binary path from GDB, result: ProcessResult { exitStatus=1 output= errors=Error 123 }, command: " +
+ assertEquals("Failed to extract binary path from GDB, result: exit status 1, output 'Error 123', command: " +
"[/bin/sh, -c, /opt/rh/devtoolset-10/root/bin/gdb -n -batch -core /tmp/core.1234 | grep '^Core was generated by']", e.getMessage());
}
}
@@ -110,7 +110,7 @@ public class CoreCollectorTest {
coreCollector.readBacktrace(context, TEST_CORE_PATH, TEST_BIN_PATH, false);
fail("Expected not to be able to read backtrace");
} catch (RuntimeException e) {
- assertEquals("Failed to read backtrace ProcessResult { exitStatus=1 output= errors=Failure }, Command: " +
+ assertEquals("Failed to read backtrace exit status 1, output 'Failure', Command: " +
"[" + GDB_PATH_RHEL7_DT9 + ", -n, -ex, bt, -batch, /usr/bin/program, /tmp/core.1234]", e.getMessage());
}
}
@@ -194,6 +194,6 @@ public class CoreCollectorTest {
private void mockExec(NodeAgentContext context, String[] cmd, String output, String error) {
when(docker.executeCommandInContainerAsRoot(context, cmd))
- .thenReturn(new ProcessResult(error.isEmpty() ? 0 : 1, output, error));
+ .thenReturn(new CommandResult(null, error.isEmpty() ? 0 : 1, error.isEmpty() ? output : error));
}
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
index 454374fe3bd..f2b761b614d 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
@@ -33,6 +33,7 @@ import org.mockito.InOrder;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import static org.junit.Assert.assertEquals;
@@ -762,12 +763,17 @@ public class NodeAgentImplTest {
return dockerImage != null ?
Optional.of(new Container(
containerId,
- hostName,
+ ContainerName.fromHostname(hostName),
+ isRunning ? Container.State.running : Container.State.exited,
+ "image-id-1",
dockerImage,
+ Map.of(),
+ 42,
+ 43,
+ hostName,
containerResources,
- ContainerName.fromHostname(hostName),
- isRunning ? Container.State.RUNNING : Container.State.EXITED,
- isRunning ? 1 : 0)) :
+ List.of(),
+ true)) :
Optional.empty();
}).when(containerOperations).getContainer(any());
}