summaryrefslogtreecommitdiffstats
path: root/node-admin
diff options
context:
space:
mode:
authorHarald Musum <musum@yahoo-inc.com>2017-01-27 06:45:30 +0100
committerGitHub <noreply@github.com>2017-01-27 06:45:30 +0100
commit860f5460ae09345cf74609aabe395b7e52460d81 (patch)
treefcf52b3e9b73343be553d7a4bcfd394f483eb4f4 /node-admin
parentaee77527a2667a56cc9e692ca86429c630239e7f (diff)
parent2b8b98a2ca273d1bf27e9f775dc8ec4517bb3438 (diff)
Merge pull request #1624 from yahoo/freva/move-node-admin-maintenenace-to-own-module
Freva/move node admin maintenenace to own module
Diffstat (limited to 'node-admin')
-rw-r--r--node-admin/pom.xml44
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperations.java3
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java49
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java240
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/ComponentsProviderImpl.java9
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/CoreCollector.java144
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/CoredumpHandler.java137
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/DeleteOldAppData.java160
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/Maintainer.java256
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImplTest.java60
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerFailTest.java6
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerMock.java8
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerTester.java2
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/MultiDockerTest.java12
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeStateTest.java9
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RestartTest.java2
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/StorageMaintainerMock.java7
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java16
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/maintenance/CoreCollectorTest.java227
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/maintenance/CoredumpHandlerTest.java202
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/maintenance/DeleteOldAppDataTest.java287
22 files changed, 292 insertions, 1590 deletions
diff --git a/node-admin/pom.xml b/node-admin/pom.xml
index 159124f6657..6216381bd7c 100644
--- a/node-admin/pom.xml
+++ b/node-admin/pom.xml
@@ -35,12 +35,6 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>org.hamcrest</groupId>
- <artifactId>hamcrest-junit</artifactId>
- <version>2.0.0.0</version>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>com.yahoo.vespa</groupId>
<artifactId>container-dev</artifactId>
<version>${project.version}</version>
@@ -79,6 +73,13 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
+
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-junit</artifactId>
+ <version>2.0.0.0</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
@@ -95,18 +96,6 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>io.airlift</groupId>
- <artifactId>airline</artifactId>
- <version>0.7</version>
- </dependency>
-
- <!-- JSON parser for Maintenance JVM -->
- <dependency>
- <groupId>com.google.code.gson</groupId>
- <artifactId>gson</artifactId>
- <version>2.6.2</version>
- </dependency>
</dependencies>
<build>
@@ -129,25 +118,6 @@
</compilerArgs>
</configuration>
</plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-assembly-plugin</artifactId>
- <configuration>
- <finalName>node-admin-maintenance</finalName>
- <descriptorRefs>
- <descriptorRef>jar-with-dependencies</descriptorRef>
- </descriptorRefs>
- </configuration>
- <executions>
- <execution>
- <id>make-assembly</id>
- <phase>package</phase>
- <goals>
- <goal>single</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
</plugins>
</build>
</project>
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperations.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperations.java
index 336e20253b6..0785e292a79 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperations.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperations.java
@@ -5,6 +5,7 @@ import com.yahoo.vespa.hosted.dockerapi.Container;
import com.yahoo.vespa.hosted.dockerapi.ContainerName;
import com.yahoo.vespa.hosted.dockerapi.Docker;
import com.yahoo.vespa.hosted.dockerapi.DockerImage;
+import com.yahoo.vespa.hosted.dockerapi.ProcessResult;
import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec;
import java.util.List;
@@ -25,7 +26,7 @@ public interface DockerOperations {
void removeContainer(ContainerNodeSpec nodeSpec, Container existingContainer);
- void executeCommandInContainer(ContainerName containerName, String[] command);
+ ProcessResult executeCommandInContainerAsRoot(ContainerName containerName, String[] command);
String executeCommandInNetworkNamespace(ContainerName containerName, String[] command);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java
index f405ade4081..810d4536d65 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImpl.java
@@ -142,24 +142,6 @@ public class DockerOperationsImpl implements DockerOperations {
}
/**
- * Executes a program and returns its result, or if it doesn't exist, return a result
- * as-if the program executed with exit status 0 and no output.
- */
- Optional<ProcessResult> executeOptionalProgramInContainer(ContainerName containerName, String... args) {
- assert args.length > 0;
- String[] nodeProgramExistsCommand = programExistsCommand(args[0]);
- if (!docker.executeInContainer(containerName, nodeProgramExistsCommand).isSuccess()) {
- return Optional.empty();
- }
-
- return Optional.of(docker.executeInContainer(containerName, args));
- }
-
- String[] programExistsCommand(String programPath) {
- return new String[]{ "/usr/bin/env", "test", "-x", programPath };
- }
-
- /**
* Try to suspend node. Suspending a node means the node should be taken offline,
* such that maintenance can be done of the node (upgrading, rebooting, etc),
* and such that we will start serving again as soon as possible afterwards.
@@ -168,23 +150,15 @@ public class DockerOperationsImpl implements DockerOperations {
*/
@Override
public void trySuspendNode(ContainerName containerName) {
- PrefixLogger logger = PrefixLogger.getNodeAgentLogger(DockerOperationsImpl.class, containerName);
- Optional<ProcessResult> result;
-
try {
// TODO: Change to waiting w/o timeout (need separate thread that we can stop).
- result = executeOptionalProgramInContainer(containerName, SUSPEND_NODE_COMMAND);
+ executeCommandInContainer(containerName, SUSPEND_NODE_COMMAND);
} catch (RuntimeException e) {
+ PrefixLogger logger = PrefixLogger.getNodeAgentLogger(DockerOperationsImpl.class, containerName);
// It's bad to continue as-if nothing happened, but on the other hand if we do not proceed to
// remove container, we will not be able to upgrade to fix any problems in the suspend logic!
logger.warning("Failed trying to suspend container " + containerName.asString() + " with "
+ Arrays.toString(SUSPEND_NODE_COMMAND), e);
- return;
- }
-
- if (result.isPresent() && !result.get().isSuccess()) {
- logger.warning("The suspend program " + Arrays.toString(SUSPEND_NODE_COMMAND)
- + " failed: " + result.get().getOutput() + " for container " + containerName.asString());
}
}
@@ -242,7 +216,7 @@ public class DockerOperationsImpl implements DockerOperations {
}
DIRECTORIES_TO_MOUNT.entrySet().stream().filter(Map.Entry::getValue).forEach(entry ->
- docker.executeInContainer(nodeSpec.containerName, "sudo", "chmod", "-R", "a+w", entry.getKey()));
+ docker.executeInContainerAsRoot(nodeSpec.containerName, "chmod", "-R", "a+w", entry.getKey()));
} catch (IOException e) {
throw new RuntimeException("Failed to create container " + nodeSpec.containerName.asString(), e);
}
@@ -288,14 +262,19 @@ public class DockerOperationsImpl implements DockerOperations {
numberOfRunningContainersGauge.sample(getAllManagedContainers().size());
}
- @Override
- public void executeCommandInContainer(ContainerName containerName, String[] command) {
- Optional<ProcessResult> result = executeOptionalProgramInContainer(containerName, command);
+ ProcessResult executeCommandInContainer(ContainerName containerName, String[] command) {
+ ProcessResult result = docker.executeInContainerAsRoot(containerName, command);
- if (result.isPresent() && !result.get().isSuccess()) {
- throw new RuntimeException("Container " + containerName.asString()
- + ": command " + Arrays.toString(command) + " failed: " + result.get());
+ if (! result.isSuccess()) {
+ throw new RuntimeException("Container " + containerName.asString() +
+ ": command " + Arrays.toString(command) + " failed: " + result);
}
+ return result;
+ }
+
+ @Override
+ public ProcessResult executeCommandInContainerAsRoot(ContainerName containerName, String[] command) {
+ return docker.executeInContainerAsRoot(containerName, command);
}
@Override
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java
index a9048b06cf5..92d33c49f74 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java
@@ -1,39 +1,65 @@
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.maintenance;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yahoo.collections.Pair;
import com.yahoo.io.IOUtils;
+import com.yahoo.net.HostName;
+import com.yahoo.system.ProcessExecuter;
import com.yahoo.vespa.hosted.dockerapi.ContainerName;
+import com.yahoo.vespa.hosted.dockerapi.Docker;
+import com.yahoo.vespa.hosted.dockerapi.ProcessResult;
+import com.yahoo.vespa.hosted.dockerapi.metrics.CounterWrapper;
+import com.yahoo.vespa.hosted.dockerapi.metrics.Dimensions;
+import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec;
import com.yahoo.vespa.hosted.node.admin.util.Environment;
import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger;
-import com.yahoo.vespa.hosted.node.maintenance.DeleteOldAppData;
-import com.yahoo.vespa.hosted.node.maintenance.Maintainer;
-import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
/**
* @author freva
*/
public class StorageMaintainer {
- private static final PrefixLogger NODE_ADMIN_LOGGER = PrefixLogger.getNodeAdminLogger(StorageMaintainer.class);
+ private static final ContainerName NODE_ADMIN = new ContainerName("node-admin");
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+ private static Optional<String> kernelVersion = Optional.empty();
+
private static final long intervalSec = 1000;
private final Object monitor = new Object();
+ private final CounterWrapper numberOfNodeAdminMaintenanceFails;
+ private final Docker docker;
private final Environment environment;
private Map<ContainerName, MetricsCache> metricsCacheByContainerName = new ConcurrentHashMap<>();
- public StorageMaintainer(Environment environment) {
+ public StorageMaintainer(Docker docker, MetricReceiverWrapper metricReceiver, Environment environment) {
+ this.docker = docker;
this.environment = environment;
+
+ Dimensions dimensions = new Dimensions.Builder()
+ .add("host", HostName.getLocalhost())
+ .add("role", "docker").build();
+
+ numberOfNodeAdminMaintenanceFails = metricReceiver.declareCounter(dimensions, "nodes.maintenance.fails");
}
public Map<String, Number> updateIfNeededAndGetDiskMetricsFor(ContainerName containerName) {
@@ -48,7 +74,7 @@ public class StorageMaintainer {
// Throttle to one disk usage calculation at a time.
synchronized (monitor) {
PrefixLogger logger = PrefixLogger.getNodeAgentLogger(StorageMaintainer.class, containerName);
- File containerDir = environment.pathInNodeAdminFromPathInNode(containerName, "/home/").toFile();
+ Path containerDir = environment.pathInNodeAdminFromPathInNode(containerName, "/home/");
try {
long used = getDiscUsedInBytes(containerDir);
metricsCache.metrics.put("node.disk.used", used);
@@ -64,7 +90,7 @@ public class StorageMaintainer {
}
// Public for testing
- long getDiscUsedInBytes(File path) throws IOException, InterruptedException {
+ long getDiscUsedInBytes(Path path) throws IOException, InterruptedException {
final String[] command = {"du", "-xsk", path.toString()};
Process duCommand = new ProcessBuilder().command(command).start();
@@ -83,51 +109,207 @@ public class StorageMaintainer {
if (results.length != 2) {
throw new RuntimeException("Result from disk usage command not as expected: " + output);
}
- long diskUsageKB = Long.valueOf(results[0]);
+ long diskUsageKB = Long.valueOf(results[0]);
return diskUsageKB * 1024;
}
+
+ /**
+ * Deletes old log files for vespa, nginx, logstash, etc.
+ */
public void removeOldFilesFromNode(ContainerName containerName) {
+ MaintainerExecutor maintainerExecutor = new MaintainerExecutor();
String[] pathsToClean = {"/home/y/logs/elasticsearch2", "/home/y/logs/logstash2",
"/home/y/logs/daemontools_y", "/home/y/logs/nginx", "/home/y/logs/vespa"};
+
for (String pathToClean : pathsToClean) {
- File path = environment.pathInNodeAdminFromPathInNode(containerName, pathToClean).toFile();
- if (path.exists()) {
- DeleteOldAppData.deleteFiles(path.getAbsolutePath(), Duration.ofDays(3).getSeconds(), ".*\\.log\\..+", false);
- DeleteOldAppData.deleteFiles(path.getAbsolutePath(), Duration.ofDays(3).getSeconds(), ".*QueryAccessLog.*", false);
+ Path path = environment.pathInNodeAdminFromPathInNode(containerName, pathToClean);
+ if (Files.exists(path)) {
+ maintainerExecutor.addJob("delete-files")
+ .withArgument("basePath", path)
+ .withArgument("maxAgeSeconds", Duration.ofDays(3).getSeconds())
+ .withArgument("fileNameRegex", ".*\\.log\\..+")
+ .withArgument("recursive", false);
+
+ maintainerExecutor.addJob("delete-files")
+ .withArgument("basePath", path)
+ .withArgument("maxAgeSeconds", Duration.ofDays(3).getSeconds())
+ .withArgument("fileNameRegex", ".*QueryAccessLog.*")
+ .withArgument("recursive", false);
}
}
- File logArchiveDir = environment.pathInNodeAdminFromPathInNode(containerName, "/home/y/logs/vespa/logarchive").toFile();
- if (logArchiveDir.exists()) {
- DeleteOldAppData.deleteFiles(logArchiveDir.getAbsolutePath(), Duration.ofDays(31).getSeconds(), null, false);
- }
+ Path logArchiveDir = environment.pathInNodeAdminFromPathInNode(containerName, "/home/y/logs/vespa/logarchive");
+ maintainerExecutor.addJob("delete-files")
+ .withArgument("basePath", logArchiveDir)
+ .withArgument("maxAgeSeconds", Duration.ofDays(31).getSeconds())
+ .withArgument("recursive", false);
- File fileDistrDir = environment.pathInNodeAdminFromPathInNode(containerName, "/home/y/var/db/vespa/filedistribution").toFile();
- if (fileDistrDir.exists()) {
- DeleteOldAppData.deleteFiles(fileDistrDir.getAbsolutePath(), Duration.ofDays(31).getSeconds(), null, false);
- }
+ Path fileDistrDir = environment.pathInNodeAdminFromPathInNode(containerName, "/home/y/var/db/vespa/filedistribution");
+ maintainerExecutor.addJob("delete-files")
+ .withArgument("basePath", fileDistrDir)
+ .withArgument("maxAgeSeconds", Duration.ofDays(31).getSeconds())
+ .withArgument("recursive", false);
+
+ maintainerExecutor.execute();
}
+ /**
+ * Checks if container has any new coredumps, reports and archives them if so
+ */
public void handleCoreDumpsForContainer(ContainerNodeSpec nodeSpec, Environment environment) {
- PrefixLogger logger = PrefixLogger.getNodeAgentLogger(StorageMaintainer.class, nodeSpec.containerName);
+ Map<String, Object> attributes = new HashMap<>();
+ attributes.put("hostname", nodeSpec.hostname);
+ attributes.put("parent_hostname", HostName.getLocalhost());
+ attributes.put("region", environment.getRegion());
+ attributes.put("environment", environment.getEnvironment());
+ attributes.put("flavor", nodeSpec.nodeFlavor);
+ try {
+ attributes.put("kernel_version", getKernelVersion());
+ } catch (Throwable ignored) {
+ attributes.put("kernel_version", "unknown");
+ }
- Maintainer.handleCoreDumpsForContainer(logger, nodeSpec, environment);
+ nodeSpec.wantedDockerImage.ifPresent(image -> attributes.put("docker_image", image.asString()));
+ nodeSpec.vespaVersion.ifPresent(version -> attributes.put("vespa_version", version));
+ nodeSpec.owner.ifPresent(owner -> {
+ attributes.put("tenant", owner.tenant);
+ attributes.put("application", owner.application);
+ attributes.put("instance", owner.instance);
+ });
+
+ MaintainerExecutor maintainerExecutor = new MaintainerExecutor(true);
+ maintainerExecutor.addJob("handle-core-dumps")
+ .withArgument("doneCoredumpsPath", environment.pathInNodeAdminToDoneCoredumps())
+ .withArgument("containerCoredumpsPath", environment.pathInNodeAdminFromPathInNode(nodeSpec.containerName, "/home/y/var/crash"))
+ .withArgument("attributes", attributes);
+ maintainerExecutor.execute();
}
+ /**
+ * Deletes old
+ * * archived app data
+ * * archived and reported coredumps
+ * * JDisc logs
+ */
public void cleanNodeAdmin() {
- Maintainer.deleteOldAppData(NODE_ADMIN_LOGGER);
- Maintainer.cleanCoreDumps(NODE_ADMIN_LOGGER);
+ MaintainerExecutor maintainerExecutor = new MaintainerExecutor(true);
+ maintainerExecutor.addJob("delete-directories")
+ .withArgument("basePath", environment.getPathResolver().getApplicationStoragePathForNodeAdmin())
+ .withArgument("maxAgeSeconds", Duration.ofDays(7).getSeconds())
+ .withArgument("dirNameRegex", "^" + Pattern.quote(Environment.APPLICATION_STORAGE_CLEANUP_PATH_PREFIX));
+
+ maintainerExecutor.addJob("delete-directories")
+ .withArgument("basePath", environment.pathInNodeAdminToDoneCoredumps())
+ .withArgument("maxAgeSeconds", Duration.ofDays(10).getSeconds());
- File nodeAdminJDiskLogsPath = environment.pathInNodeAdminFromPathInNode(new ContainerName("node-admin"),
- "/home/y/logs/jdisc_core/").toFile();
- DeleteOldAppData.deleteFiles(nodeAdminJDiskLogsPath.getAbsolutePath(), Duration.ofDays(31).getSeconds(), null, false);
+ Path nodeAdminJDiskLogsPath = environment.pathInNodeAdminFromPathInNode(NODE_ADMIN, "/home/y/logs/jdisc_core/");
+ maintainerExecutor.addJob("delete-files")
+ .withArgument("basePath", nodeAdminJDiskLogsPath)
+ .withArgument("maxAgeSeconds", Duration.ofDays(31).getSeconds())
+ .withArgument("recursive", false);
+ maintainerExecutor.execute();
}
+ /**
+ * Archives container data, runs when container enters state "dirty"
+ */
public void archiveNodeData(ContainerName containerName) {
- PrefixLogger logger = PrefixLogger.getNodeAgentLogger(StorageMaintainer.class, containerName);
- Maintainer.archiveAppData(logger, containerName);
+ MaintainerExecutor maintainerExecutor = new MaintainerExecutor(true);
+ maintainerExecutor.addJob("recursive-delete")
+ .withArgument("path", environment.pathInNodeAdminFromPathInNode(containerName, "/home/y/var"));
+
+ maintainerExecutor.addJob("move-files")
+ .withArgument("from", environment.pathInNodeAdminFromPathInNode(containerName, "/"))
+ .withArgument("to", environment.pathInNodeAdminToNodeCleanup(containerName));
+
+ maintainerExecutor.execute();
+ }
+
+
+
+ private String getKernelVersion() throws IOException, InterruptedException {
+ if (! kernelVersion.isPresent()) {
+ Pair<Integer, String> result = new ProcessExecuter().exec(new String[]{"uname", "-r"});
+ if (result.getFirst() == 0) {
+ kernelVersion = Optional.of(result.getSecond().trim());
+ } else {
+ throw new RuntimeException("Failed to get kernel version\n" + result);
+ }
+ }
+
+ return kernelVersion.orElse("unknown");
+ }
+
+ /**
+ * Wrapper for node-admin-maintenance, queues up maintenances jobs and sends a single request to maintenance JVM
+ */
+ private class MaintainerExecutor {
+ private final List<MaintainerExecutorJob> jobs = new ArrayList<>();
+ private final ContainerName executeIn;
+ private final boolean runAsRoot;
+
+ MaintainerExecutor(ContainerName executeIn, boolean runAsRoot) {
+ this.executeIn = executeIn;
+ this.runAsRoot = runAsRoot;
+ }
+
+ MaintainerExecutor(boolean runAsRoot) {
+ this(NODE_ADMIN, runAsRoot);
+ }
+
+ MaintainerExecutor() {
+ this(false);
+ }
+
+ MaintainerExecutorJob addJob(String jobName) {
+ MaintainerExecutorJob job = new MaintainerExecutorJob(jobName);
+ jobs.add(job);
+ return job;
+ }
+
+ ProcessResult execute() {
+ String classPath = String.join(":",
+ "/home/y/lib/jars/node-admin-maintenance-jar-with-dependencies.jar",
+ "/home/y/lib/jars/vespajlib.jar");
+
+ String args;
+ try {
+ args = objectMapper.writeValueAsString(jobs);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException("Failed trasform list of maintenance jobs to JSON");
+ }
+
+ String[] command = {"java", "-cp", classPath, "com.yahoo.vespa.hosted.node.maintenance.Maintainer", args};
+ ProcessResult result = runAsRoot ? docker.executeInContainerAsRoot(executeIn, command) :
+ docker.executeInContainer(executeIn, command);
+
+ if (! result.isSuccess()) {
+ PrefixLogger logger = PrefixLogger.getNodeAgentLogger(StorageMaintainer.class, executeIn);
+ logger.warning("Failed to run maintenance jobs: " + args + result);
+ numberOfNodeAdminMaintenanceFails.add();
+ }
+ return result;
+ }
+ }
+
+ private class MaintainerExecutorJob {
+ @JsonProperty(value="jobName")
+ private final String jobName;
+
+ @JsonProperty(value="arguments")
+ private final Map<String, Object> arguments = new HashMap<>();
+
+ MaintainerExecutorJob(String jobName) {
+ this.jobName = jobName;
+ }
+
+ MaintainerExecutorJob withArgument(String argument, Object value) {
+ // Transform Path to String, otherwise ObjectMapper wont encode/decode it properly on the other end
+ arguments.put(argument, (value instanceof Path) ? value.toString() : value);
+ return this;
+ }
}
private static class MetricsCache {
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
index 7f1ce37c1d2..0c3f0f4a139 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
@@ -579,7 +579,7 @@ public class NodeAgentImpl implements NodeAgent {
try {
scheduleMaker.writeTo(yamasAgentFolder);
final String[] restartYamasAgent = new String[] {"service" , "yamas-agent", "restart"};
- dockerOperations.executeCommandInContainer(nodeSpec.containerName, restartYamasAgent);
+ dockerOperations.executeCommandInContainerAsRoot(nodeSpec.containerName, restartYamasAgent);
} catch (IOException e) {
throw new RuntimeException("Failed to write secret-agent schedules for " + nodeSpec.containerName, e);
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/ComponentsProviderImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/ComponentsProviderImpl.java
index 67d4b28472d..1f64c02e6a4 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/ComponentsProviderImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/ComponentsProviderImpl.java
@@ -77,7 +77,8 @@ public class ComponentsProviderImpl implements ComponentsProvider {
docker,
metricReceiver,
new Environment(),
- config.isRunningLocally() ? Optional.empty() : Optional.of(new StorageMaintainer(new Environment())));
+ config.isRunningLocally() ? Optional.empty() :
+ Optional.of(new StorageMaintainer(docker, metricReceiver, new Environment())));
if (! config.isRunningLocally()) {
setCorePattern(docker);
@@ -98,12 +99,12 @@ public class ComponentsProviderImpl implements ComponentsProvider {
private void setCorePattern(Docker docker) {
final String[] sysctlCorePattern = {"sysctl", "-w", "kernel.core_pattern=/home/y/var/crash/%e.core.%p"};
- docker.executeInContainer(NODE_ADMIN_CONTAINER_NAME, sysctlCorePattern);
+ docker.executeInContainerAsRoot(NODE_ADMIN_CONTAINER_NAME, sysctlCorePattern);
}
private void initializeNodeAgentSecretAgent(Docker docker) {
final Path yamasAgentFolder = Paths.get("/etc/yamas-agent/");
- docker.executeInContainer(NODE_ADMIN_CONTAINER_NAME, "sudo", "chmod", "a+w", yamasAgentFolder.toString());
+ docker.executeInContainerAsRoot(NODE_ADMIN_CONTAINER_NAME, "chmod", "a+w", yamasAgentFolder.toString());
Path nodeAdminCheckPath = Paths.get("/usr/bin/curl");
SecretAgentScheduleMaker scheduleMaker = new SecretAgentScheduleMaker("node-admin", 60, nodeAdminCheckPath,
@@ -111,7 +112,7 @@ public class ComponentsProviderImpl implements ComponentsProvider {
try {
scheduleMaker.writeTo(yamasAgentFolder);
- docker.executeInContainer(NODE_ADMIN_CONTAINER_NAME, "service", "yamas-agent", "restart");
+ docker.executeInContainerAsRoot(NODE_ADMIN_CONTAINER_NAME, "service", "yamas-agent", "restart");
} catch (IOException e) {
throw new RuntimeException("Failed to write secret-agent schedules for node-admin", e);
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/CoreCollector.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/CoreCollector.java
deleted file mode 100644
index 3f0f504bf65..00000000000
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/CoreCollector.java
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.maintenance;
-
-import com.yahoo.vespa.hosted.dockerapi.ProcessResult;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Takes in a compressed (lz4) or uncompressed core dump and collects relevant metadata.
- *
- * @author freva
- */
-public class CoreCollector {
- private static final String GDB_PATH = "/home/y/bin64/gdb";
- private static final Pattern CORE_GENERATOR_PATH_PATTERN = Pattern.compile("^Core was generated by `(?<path>.*?)'.$");
- private static final Pattern EXECFN_PATH_PATTERN = Pattern.compile("^.* execfn: '(?<path>.*?)'");
- private static final Pattern FROM_PATH_PATTERN = Pattern.compile("^.* from '(?<path>.*?)'");
- private static final Pattern TOTAL_MEMORY_PATTERN = Pattern.compile("^MemTotal:\\s*(?<totalMem>\\d+) kB$", Pattern.MULTILINE);
-
- private static final Logger logger = Logger.getLogger(CoreCollector.class.getName());
-
- private final Maintainer maintainer;
-
- public CoreCollector(Maintainer maintainer) {
- this.maintainer = maintainer;
- }
-
- Path readBinPathFallback(Path coredumpPath) throws IOException, InterruptedException {
- String command = GDB_PATH + " -n -batch -core " + coredumpPath + " | grep \'^Core was generated by\'";
- ProcessResult result = maintainer.exec("sh", "-c", "\"" + command + "\"");
-
- Matcher matcher = CORE_GENERATOR_PATH_PATTERN.matcher(result.getOutput());
- if (! matcher.find()) {
- throw new RuntimeException("Failed to extract binary path from " + result);
- }
- return Paths.get(matcher.group("path").split(" ")[0]);
- }
-
- Path readBinPath(Path coredumpPath) throws IOException, InterruptedException {
- try {
- ProcessResult result = maintainer.exec("file", coredumpPath.toString());
-
- Matcher execfnMatcher = EXECFN_PATH_PATTERN.matcher(result.getOutput());
- if (execfnMatcher.find()) {
- return Paths.get(execfnMatcher.group("path").split(" ")[0]);
- }
-
- Matcher fromMatcher = FROM_PATH_PATTERN.matcher(result.getOutput());
- if (fromMatcher.find()) {
- return Paths.get(fromMatcher.group("path").split(" ")[0]);
- }
- } catch (Throwable e) {
- logger.log(Level.WARNING, "Failed getting bin path, trying fallback instead", e);
- }
-
- return readBinPathFallback(coredumpPath);
- }
-
- List<String> readBacktrace(Path coredumpPath, Path binPath, boolean allThreads) throws IOException, InterruptedException {
- String threads = allThreads ? "thread apply all bt" : "bt";
- ProcessResult result = maintainer.exec(GDB_PATH, "-n", "-ex", threads, "-batch",
- binPath.toString(), coredumpPath.toString());
- if (! result.isSuccess()) {
- throw new RuntimeException("Failed to read backtrace " + result);
- }
- return Arrays.asList(result.getOutput().split("\n"));
- }
-
- public Map<String, Object> collect(Path coredumpPath) {
- Map<String, Object> data = new LinkedHashMap<>();
- try {
- coredumpPath = compressCoredump(coredumpPath);
- Path binPath = readBinPath(coredumpPath);
-
- data.put("bin_path", binPath.toString()); // Gson can't deal with Path
- data.put("backtrace", readBacktrace(coredumpPath, binPath, false));
- data.put("backtrace_all_threads", readBacktrace(coredumpPath, binPath, true));
-
- deleteDecompressedCoredump(coredumpPath);
- } catch (Throwable e) {
- logger.log(Level.WARNING, "Failed to collect core dump data", e);
- }
- return data;
- }
-
-
- /**
- * This method will either compress or decompress the core dump if the input path is to a decompressed or
- * compressed core dump, respectively.
- *
- * @return Path to the decompressed core dump
- */
- private Path compressCoredump(Path coredumpPath) throws IOException, InterruptedException {
- if (! coredumpPath.toString().endsWith(".lz4")) {
- maintainer.exec("/home/y/bin64/lz4", coredumpPath.toString(), coredumpPath.toString() + ".lz4");
- return coredumpPath;
-
- } else {
- if (!diskSpaceAvailable(coredumpPath)) {
- throw new RuntimeException("Not decompressing " + coredumpPath + " due to not enough disk space available");
- }
-
- Path decompressedPath = Paths.get(coredumpPath.toString().replaceFirst("\\.lz4$", ""));
- ProcessResult result = maintainer.exec("/home/y/bin64/lz4", "-d", coredumpPath.toString(), decompressedPath.toString());
- if (!result.isSuccess()) {
- throw new RuntimeException("Failed to decompress file " + coredumpPath + ": " + result);
- }
- return decompressedPath;
- }
- }
-
- /**
- * Delete the core dump unless:
- * - The file is compressed
- * - There is no compressed file (i.e. it was not decompressed in the first place)
- */
- void deleteDecompressedCoredump(Path coredumpPath) throws IOException {
- if (! coredumpPath.toString().endsWith(".lz4") && Paths.get(coredumpPath.toString() + ".lz4").toFile().exists()) {
- Files.delete(coredumpPath);
- }
- }
-
- private boolean diskSpaceAvailable(Path path) throws IOException {
- String memInfo = new String(Files.readAllBytes(Paths.get("/proc/meminfo")));
- return path.toFile().getFreeSpace() > parseTotalMemorySize(memInfo);
- }
-
- int parseTotalMemorySize(String memInfo) {
- Matcher matcher = TOTAL_MEMORY_PATTERN.matcher(memInfo);
- if (!matcher.find()) throw new RuntimeException("Could not parse meminfo: " + memInfo);
- return Integer.valueOf(matcher.group("totalMem"));
- }
-}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/CoredumpHandler.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/CoredumpHandler.java
deleted file mode 100644
index 2004a126d81..00000000000
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/CoredumpHandler.java
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.maintenance;
-
-import com.google.gson.Gson;
-import org.apache.http.HttpHeaders;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.StringEntity;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.time.Duration;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-
-/**
- * Finds coredumps, collects metadata and reports them
- *
- * @author freva
- */
-public class CoredumpHandler {
- public static final String FEED_ENDPOINT = "http://panic.vespa.us-west-1.prod.vespa.yahooapis.com:4080/document/v1/panic/core_dump/docid";
- public static final String PROCESSING_DIRECTORY_NAME = "processing";
- public static final String METADATA_FILE_NAME = "metadata.json";
-
- private final Logger logger = Logger.getLogger(CoredumpHandler.class.getName());
- private final Gson gson = new Gson();
-
- private final HttpClient httpClient;
- private final CoreCollector coreCollector;
-
- public CoredumpHandler(HttpClient httpClient, CoreCollector coreCollector) {
- this.httpClient = httpClient;
- this.coreCollector = coreCollector;
- }
-
- public void processAndReportCoredumps(Path coredumpsPath, Path doneCoredumpPath, Map<String, Object> nodeAttributes) throws IOException {
- Path processingCoredumps = processCoredumps(coredumpsPath, nodeAttributes);
- reportCoredumps(processingCoredumps, doneCoredumpPath);
- }
-
- public void removeJavaCoredumps(Path javaCoredumpsPath) {
- if (! javaCoredumpsPath.toFile().isDirectory()) return;
- DeleteOldAppData.deleteFiles(javaCoredumpsPath.toString(), 0, "^java_pid.*\\.hprof$", false);
- }
-
- Path processCoredumps(Path coredumpsPath, Map<String, Object> nodeAttributes) throws IOException {
- Path processingCoredumpsPath = coredumpsPath.resolve(PROCESSING_DIRECTORY_NAME);
- processingCoredumpsPath.toFile().mkdirs();
-
- Files.list(coredumpsPath)
- .filter(path -> path.toFile().isFile() && ! path.getFileName().toString().startsWith("."))
- .forEach(coredumpPath -> {
- try {
- coredumpPath.toFile().setReadable(true, false);
- coredumpPath = startProcessing(coredumpPath, processingCoredumpsPath);
-
- Path metadataPath = coredumpPath.getParent().resolve(METADATA_FILE_NAME);
- Map<String, Object> metadata = collectMetadata(coredumpPath, nodeAttributes);
- writeMetadata(metadataPath, metadata);
- } catch (Throwable e) {
- logger.log(Level.WARNING, "Failed to process coredump " + coredumpPath, e);
- }
- });
-
- return processingCoredumpsPath;
- }
-
- void reportCoredumps(Path processingCoredumpsPath, Path doneCoredumpsPath) throws IOException {
- doneCoredumpsPath.toFile().mkdirs();
-
- Files.list(processingCoredumpsPath)
- .filter(path -> path.toFile().isDirectory())
- .forEach(coredumpDirectory -> {
- try {
- report(coredumpDirectory);
- finishProcessing(coredumpDirectory, doneCoredumpsPath);
- } catch (Throwable e) {
- logger.log(Level.WARNING, "Failed to report coredump " + coredumpDirectory, e);
- }
- });
- }
-
- public void removeOldCoredumps(Path doneCoredumpsPath) {
- DeleteOldAppData.deleteDirectories(doneCoredumpsPath.toString(), Duration.ofDays(10).getSeconds(), null);
- }
-
- Path startProcessing(Path coredumpPath, Path processingCoredumpsPath) throws IOException {
- Path folder = processingCoredumpsPath.resolve(UUID.randomUUID().toString());
- folder.toFile().mkdirs();
- return Files.move(coredumpPath, folder.resolve(coredumpPath.getFileName()));
- }
-
- private Map<String, Object> collectMetadata(Path coredumpPath, Map<String, Object> nodeAttributes) {
- Map<String, Object> metadata = coreCollector.collect(coredumpPath);
- metadata.putAll(nodeAttributes);
-
- Map<String, Object> fields = new HashMap<>();
- fields.put("fields", metadata);
- return fields;
- }
-
- private void writeMetadata(Path metadataPath, Map<String, Object> metadata) throws IOException {
- Files.write(metadataPath, gson.toJson(metadata).getBytes());
- }
-
- void report(Path coredumpDirectory) throws IOException {
- // Use core dump UUID as document ID
- String documentId = coredumpDirectory.getFileName().toString();
- String metadata = new String(Files.readAllBytes(coredumpDirectory.resolve(METADATA_FILE_NAME)));
-
- HttpPost post = new HttpPost(FEED_ENDPOINT + "/" + documentId);
- post.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
- post.setEntity(new StringEntity(metadata));
-
- HttpResponse response = httpClient.execute(post);
- if (response.getStatusLine().getStatusCode() / 100 != 2) {
- String result = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))
- .lines().collect(Collectors.joining("\n"));
- throw new RuntimeException("POST to " + post.getURI() + " failed with HTTP: " +
- response.getStatusLine().getStatusCode() + " [" + result + "]");
- }
- logger.info("Successfully reported coredump " + documentId);
- }
-
- void finishProcessing(Path coredumpDirectory, Path doneCoredumpsPath) throws IOException {
- Files.move(coredumpDirectory, doneCoredumpsPath.resolve(coredumpDirectory.getFileName()));
- }
-}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/DeleteOldAppData.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/DeleteOldAppData.java
deleted file mode 100644
index ed3f3653fee..00000000000
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/DeleteOldAppData.java
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.maintenance;
-
-import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.time.Duration;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-/**
- * @author freva
- */
-
-public class DeleteOldAppData {
- private static final PrefixLogger logger = PrefixLogger.getNodeAdminLogger(DeleteOldAppData.class);
-
- /**
- * (Recursively) deletes files if they match all the criteria, also deletes empty directories.
- *
- * @param basePath Base path from where to start the search
- * @param maxAgeSeconds Delete files older (last modified date) than maxAgeSeconds
- * @param fileNameRegex Delete files where filename matches fileNameRegex
- * @param recursive Delete files in sub-directories (with the same criteria)
- */
- public static void deleteFiles(String basePath, long maxAgeSeconds, String fileNameRegex, boolean recursive) {
- Pattern fileNamePattern = fileNameRegex != null ? Pattern.compile(fileNameRegex) : null;
- File[] filesInDeleteDirectory = getContentsOfDirectory(basePath);
-
- for (File file : filesInDeleteDirectory) {
- if (file.isDirectory()) {
- if (recursive) {
- deleteFiles(file.getAbsolutePath(), maxAgeSeconds, fileNameRegex, true);
- if (file.list().length == 0 && !file.delete()) {
- logger.warning("Could not delete directory: " + file.getAbsolutePath());
- }
- }
- } else if (isPatternMatchingFilename(fileNamePattern, file) &&
- isTimeSinceLastModifiedMoreThan(file, Duration.ofSeconds(maxAgeSeconds))) {
- if (!file.delete()) {
- logger.warning("Could not delete file: " + file.getAbsolutePath());
- }
- }
- }
- }
-
- /**
- * Deletes all files in target directory except the n most recent (by modified date)
- *
- * @param basePath Base path to delete from
- * @param nMostRecentToKeep Number of most recent files to keep
- */
- public static void deleteFilesExceptNMostRecent(String basePath, int nMostRecentToKeep) {
- File[] deleteDirContents = getContentsOfDirectory(basePath);
-
- if (nMostRecentToKeep < 1) {
- throw new IllegalArgumentException("Number of files to keep must be a positive number");
- }
-
- List<File> filesInDeleteDir = Arrays.stream(deleteDirContents).filter(File::isFile).collect(Collectors.toList());
- if (filesInDeleteDir.size() <= nMostRecentToKeep) return;
-
- Collections.sort(filesInDeleteDir, (f1, f2) -> Long.signum(f1.lastModified() - f2.lastModified()));
-
- for (int i = nMostRecentToKeep; i < filesInDeleteDir.size(); i++) {
- if (!filesInDeleteDir.get(i).delete()) {
- logger.warning("Could not delete file: " + filesInDeleteDir.get(i).getAbsolutePath());
- }
- }
- }
-
- public static void deleteFilesLargerThan(File baseDirectory, long sizeInBytes) {
- File[] filesInBaseDirectory = getContentsOfDirectory(baseDirectory.getAbsolutePath());
-
- for (File file : filesInBaseDirectory) {
- if (file.isDirectory()) {
- deleteFilesLargerThan(file, sizeInBytes);
- } else {
- if (file.length() > sizeInBytes && !file.delete()) {
- logger.warning("Could not delete file: " + file.getAbsolutePath());
- }
- }
- }
- }
-
- /**
- * Deletes directories and their contents if they match all the criteria
- *
- * @param basePath Base path to delete the directories from
- * @param maxAgeSeconds Delete directories older (last modified date) than maxAgeSeconds
- * @param dirNameRegex Delete directories where directory name matches dirNameRegex
- */
- public static void deleteDirectories(String basePath, long maxAgeSeconds, String dirNameRegex) {
- Pattern dirNamePattern = dirNameRegex != null ? Pattern.compile(dirNameRegex) : null;
- File[] filesInDeleteDirectory = getContentsOfDirectory(basePath);
-
- for (File file : filesInDeleteDirectory) {
- if (file.isDirectory() &&
- isPatternMatchingFilename(dirNamePattern, file) &&
- isTimeSinceLastModifiedMoreThan(getMostRecentlyModifiedFileIn(file), Duration.ofSeconds(maxAgeSeconds))) {
- deleteFiles(file.getPath(), 0, null, true);
- if (file.list().length == 0 && !file.delete()) {
- logger.warning("Could not delete directory: " + file.getAbsolutePath());
- }
- }
- }
- }
-
- /**
- * Similar to rm -rf file:
- * - It's not an error if file doesn't exist
- * - If file is a directory, it and all content is removed
- * - For symlinks: Only the symlink is removed, not what the symlink points to
- */
- public static void recursiveDelete(File file) throws IOException {
- if (file.isDirectory()) {
- for (File childFile : file.listFiles()) {
- recursiveDelete(childFile);
- }
- }
-
- Files.deleteIfExists(file.toPath());
- }
-
- static File[] getContentsOfDirectory(String directoryPath) {
- File directory = new File(directoryPath);
- File[] directoryContents = directory.listFiles();
-
- return directoryContents == null ? new File[0] : directoryContents;
- }
-
- private static File getMostRecentlyModifiedFileIn(File baseFile) {
- File mostRecent = baseFile;
- File[] filesInDirectory = getContentsOfDirectory(baseFile.getAbsolutePath());
-
- for (File file : filesInDirectory) {
- if (file.isDirectory()) {
- file = getMostRecentlyModifiedFileIn(file);
- }
-
- if (file.lastModified() > mostRecent.lastModified()) {
- mostRecent = file;
- }
- }
- return mostRecent;
- }
-
- private static boolean isTimeSinceLastModifiedMoreThan(File file, Duration duration) {
- return System.currentTimeMillis() - file.lastModified() > duration.toMillis();
- }
-
- private static boolean isPatternMatchingFilename(Pattern pattern, File file) {
- return pattern == null || pattern.matcher(file.getName()).find();
- }
-}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/Maintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/Maintainer.java
deleted file mode 100644
index c36aafd54cb..00000000000
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/maintenance/Maintainer.java
+++ /dev/null
@@ -1,256 +0,0 @@
-// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.maintenance;
-
-import com.google.gson.Gson;
-import com.yahoo.io.IOUtils;
-import com.yahoo.log.LogSetup;
-import com.yahoo.net.HostName;
-import com.yahoo.vespa.hosted.dockerapi.ContainerName;
-import com.yahoo.vespa.hosted.dockerapi.ProcessResult;
-import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec;
-import com.yahoo.vespa.hosted.node.admin.util.Environment;
-import com.yahoo.vespa.hosted.node.admin.util.PathResolver;
-import com.yahoo.vespa.hosted.node.admin.util.PrefixLogger;
-import io.airlift.airline.Arguments;
-import io.airlift.airline.Cli;
-import io.airlift.airline.Command;
-import io.airlift.airline.Help;
-import io.airlift.airline.Option;
-import io.airlift.airline.ParseArgumentsUnexpectedException;
-import io.airlift.airline.ParseOptionMissingException;
-import org.apache.http.impl.client.HttpClientBuilder;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.time.Duration;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.regex.Pattern;
-
-/**
- * @author freva
- */
-public class Maintainer {
- private static final Environment environment = new Environment.Builder().pathResolver(new PathResolver()).build();
- private static final Maintainer maintainer = new Maintainer();
- private static final CoredumpHandler COREDUMP_HANDLER =
- new CoredumpHandler(HttpClientBuilder.create().build(), new CoreCollector(maintainer));
- private static final Gson gson = new Gson();
-
- private static final String JOB_DELETE_OLD_APP_DATA = "delete-old-app-data";
- private static final String JOB_ARCHIVE_APP_DATA = "archive-app-data";
- private static final String JOB_CLEAN_CORE_DUMPS = "clean-core-dumps";
- private static final String JOB_HANDLE_CORE_DUMPS = "handle-core-dumps";
-
- private static Optional<String> kernelVersion = Optional.empty();
-
- @SuppressWarnings("unchecked")
- public static void main(String[] args) {
- LogSetup.initVespaLogging(Maintainer.class.getSimpleName().toLowerCase());
-
- Cli.CliBuilder<Runnable> builder = Cli.<Runnable>builder("maintainer.jar")
- .withDescription("This tool makes it easy to delete old log files and other node-admin app data.")
- .withDefaultCommand(Help.class)
- .withCommands(Help.class,
- DeleteOldAppDataArguments.class,
- CleanCoreDumpsArguments.class,
- ArchiveApplicationData.class,
- HandleCoreDumpsForContainer.class);
-
- Cli<Runnable> gitParser = builder.build();
- try {
- gitParser.parse(args).run();
- } catch (ParseArgumentsUnexpectedException | ParseOptionMissingException e) {
- System.err.println(e.getMessage());
- gitParser.parse("help").run();
- }
- }
-
- public static void cleanCoreDumps(PrefixLogger logger) {
- executeMaintainer(logger, JOB_CLEAN_CORE_DUMPS);
- }
-
- public static void deleteOldAppData(PrefixLogger logger) {
- executeMaintainer(logger, JOB_DELETE_OLD_APP_DATA);
- }
-
- public static void archiveAppData(PrefixLogger logger, ContainerName containerName) {
- executeMaintainer(logger, JOB_ARCHIVE_APP_DATA, containerName.asString());
- }
-
- public static void handleCoreDumpsForContainer(PrefixLogger logger, ContainerNodeSpec nodeSpec, Environment environment) {
- Map<String, Object> attributes = new HashMap<>();
- attributes.put("hostname", nodeSpec.hostname);
- attributes.put("parent_hostname", HostName.getLocalhost());
- attributes.put("region", environment.getRegion());
- attributes.put("environment", environment.getEnvironment());
- attributes.put("flavor", nodeSpec.nodeFlavor);
- try {
- attributes.put("kernel_version", getKernelVersion());
- } catch (Throwable ignored) {
- attributes.put("kernel_version", "unknown");
- }
-
- nodeSpec.wantedDockerImage.ifPresent(image -> attributes.put("docker_image", image.asString()));
- nodeSpec.vespaVersion.ifPresent(version -> attributes.put("vespa_version", version));
- nodeSpec.owner.ifPresent(owner -> {
- attributes.put("tenant", owner.tenant);
- attributes.put("application", owner.application);
- attributes.put("instance", owner.instance);
- });
-
- executeMaintainer(logger, JOB_HANDLE_CORE_DUMPS,
- "--container", nodeSpec.containerName.asString(),
- "--attributes", gson.toJson(attributes));
- }
-
- private static void executeMaintainer(PrefixLogger logger, String... params) {
- String[] baseArguments = {"sudo", "/home/y/libexec/vespa/node-admin/maintenance.sh"};
- String[] args = concatenateArrays(baseArguments, params);
- ProcessBuilder processBuilder = new ProcessBuilder(args);
- Map<String, String> env = processBuilder.environment();
- env.put("VESPA_SERVICE_NAME", "maintainer");
-
- try {
- ProcessResult result = maintainer.exec(args);
-
- if (! result.getOutput().isEmpty()) logger.info(result.getOutput());
- if (! result.getErrors().isEmpty()) logger.error(result.getErrors());
- } catch (IOException | InterruptedException e) {
- logger.warning("Failed to execute command " + Arrays.toString(args), e);
- }
- }
-
- public ProcessResult exec(String... args) throws IOException, InterruptedException {
- ProcessBuilder processBuilder = new ProcessBuilder(args);
- Process process = processBuilder.start();
- String output = IOUtils.readAll(new InputStreamReader(process.getInputStream()));
- String errors = IOUtils.readAll(new InputStreamReader(process.getErrorStream()));
-
- return new ProcessResult(process.waitFor(), output, errors);
- }
-
- private static String[] concatenateArrays(String[] ar1, String... ar2) {
- String[] concatenated = new String[ar1.length + ar2.length];
- System.arraycopy(ar1, 0, concatenated, 0, ar1.length);
- System.arraycopy(ar2, 0, concatenated, ar1.length, ar2.length);
- return concatenated;
- }
-
- @Command(name = JOB_DELETE_OLD_APP_DATA, description = "Deletes old app data")
- public static class DeleteOldAppDataArguments implements Runnable {
- @Override
- public void run() {
- String path = environment.getPathResolver().getApplicationStoragePathForNodeAdmin().toString();
- String regex = "^" + Pattern.quote(Environment.APPLICATION_STORAGE_CLEANUP_PATH_PREFIX);
-
- DeleteOldAppData.deleteDirectories(path, Duration.ofDays(7).getSeconds(), regex);
- }
- }
-
- @Command(name = JOB_CLEAN_CORE_DUMPS, description = "Clean core dumps")
- public static class CleanCoreDumpsArguments implements Runnable {
- @Override
- public void run() {
- Path doneCoredumps = environment.pathInNodeAdminToDoneCoredumps();
-
- if (doneCoredumps.toFile().exists()) {
- COREDUMP_HANDLER.removeOldCoredumps(doneCoredumps);
- }
- }
- }
-
- @Command(name = JOB_ARCHIVE_APP_DATA, description = "Move container's container-storage to cleanup")
- public static class ArchiveApplicationData implements Runnable {
- @Arguments(description = "Name of container to archive (required)")
- public String container;
-
- @Override
- public void run() {
- if (container == null) {
- throw new IllegalArgumentException("<container> is required");
- }
- // Note that ContainerName verifies the name, so it cannot
- // contain / or be equal to "." or "..".
- ContainerName containerName = new ContainerName(container);
-
- Logger logger = Logger.getLogger(ArchiveApplicationData.class.getName());
- File yVarDir = environment.pathInNodeAdminFromPathInNode(containerName, "/home/y/var").toFile();
- if (yVarDir.exists()) {
- logger.info("Recursively deleting " + yVarDir);
- try {
- DeleteOldAppData.recursiveDelete(yVarDir);
- } catch (IOException e) {
- throw new RuntimeException("Failed to delete " + yVarDir, e);
- }
- }
-
- Path from = environment.pathInNodeAdminFromPathInNode(containerName, "/");
- if (!Files.exists(from)) {
- logger.info("The container storage at " + from + " doesn't exist");
- return;
- }
-
- Path to = environment.pathInNodeAdminToNodeCleanup(containerName);
- logger.info("Moving container storage from " + from + " to " + to);
- try {
- Files.move(from, to);
- } catch (IOException e) {
- throw new RuntimeException("Failed to move " + from + " to " + to, e);
- }
- }
- }
-
- @SuppressWarnings("unchecked")
- @Command(name = JOB_HANDLE_CORE_DUMPS, description = "Finds container's coredumps, collects metadata and reports them")
- public static class HandleCoreDumpsForContainer implements Runnable {
- @Option(name = "--container", description = "Name of the container")
- public String container;
-
- @Option(name = "--attributes", description = "Comma separated key=value pairs")
- public String attributes;
-
- @Override
- public void run() {
- Logger logger = Logger.getLogger(HandleCoreDumpsForContainer.class.getName());
-
- if (container == null) {
- throw new IllegalArgumentException("<container> is required");
- }
-
- try {
- Map<String, Object> attributesMap = (Map<String, Object>) gson.fromJson(attributes, Map.class);
-
- Path path = environment.pathInNodeAdminFromPathInNode(new ContainerName(container), "/home/y/var/crash");
- Path doneCoredumps = environment.pathInNodeAdminToDoneCoredumps();
-
- COREDUMP_HANDLER.removeJavaCoredumps(path);
- COREDUMP_HANDLER.processAndReportCoredumps(path, doneCoredumps, attributesMap);
- } catch (Throwable e) {
- logger.log(Level.WARNING, "Could not process coredumps", e);
- }
- }
- }
-
-
-
- public static String getKernelVersion() throws IOException, InterruptedException {
- if (! kernelVersion.isPresent()) {
- ProcessResult result = maintainer.exec("uname", "-r");
- if (result.isSuccess()) {
- kernelVersion = Optional.of(result.getOutput().trim());
- } else {
- throw new RuntimeException("Failed to get kernel version\n" + result);
- }
- }
-
- return kernelVersion.get();
- }
-}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImplTest.java
index ed6808e1eac..519f2c49374 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImplTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/docker/DockerOperationsImplTest.java
@@ -25,7 +25,6 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class DockerOperationsImplTest {
@@ -35,64 +34,37 @@ public class DockerOperationsImplTest {
new MetricReceiverWrapper(MetricReceiver.nullImplementation));
@Test
- public void absenceOfNodeProgramIsSuccess() throws Exception {
+ public void processResultFromNodeProgramWhenSuccess() throws Exception {
final ContainerName containerName = new ContainerName("container-name");
+ final ProcessResult actualResult = new ProcessResult(0, "output", "errors");
final String programPath = "/bin/command";
+ final String[] command = new String[] {programPath, "arg"};
- when(docker.executeInContainer(any(), anyVararg())).thenReturn(new ProcessResult(3, "output", "errors"));
-
- Optional<ProcessResult> result = dockerOperations.executeOptionalProgramInContainer(
- containerName,
- programPath,
- "arg1",
- "arg2");
+ when(docker.executeInContainerAsRoot(any(), anyVararg()))
+ .thenReturn(actualResult); // output from node program
- String[] nodeProgramExistsCommand = dockerOperations.programExistsCommand(programPath);
- assertThat(nodeProgramExistsCommand.length, is(4));
+ ProcessResult result = dockerOperations.executeCommandInContainer(containerName, command);
- verify(docker, times(1)).executeInContainer(
+ final InOrder inOrder = inOrder(docker);
+ inOrder.verify(docker, times(1)).executeInContainerAsRoot(
eq(containerName),
- // Mockito fails if we put the array here instead...
- eq(nodeProgramExistsCommand[0]),
- eq(nodeProgramExistsCommand[1]),
- eq(nodeProgramExistsCommand[2]),
- eq(nodeProgramExistsCommand[3]));
- assertThat(result.isPresent(), is(false));
+ eq(command[0]),
+ eq(command[1]));
+
+ assertThat(result, is(actualResult));
}
- @Test
- public void processResultFromNodeProgramWhenPresent() throws Exception {
+ @Test(expected=RuntimeException.class)
+ public void processResultFromNodeProgramWhenNonZeroExitCode() throws Exception {
final ContainerName containerName = new ContainerName("container-name");
final ProcessResult actualResult = new ProcessResult(3, "output", "errors");
final String programPath = "/bin/command";
final String[] command = new String[] {programPath, "arg"};
- when(docker.executeInContainer(any(), anyVararg()))
- .thenReturn(new ProcessResult(0, "", "")) // node program exists
+ when(docker.executeInContainerAsRoot(any(), anyVararg()))
.thenReturn(actualResult); // output from node program
- Optional<ProcessResult> result = dockerOperations.executeOptionalProgramInContainer(
- containerName,
- command);
-
- String[] nodeProgramExistsCommand = dockerOperations.programExistsCommand(programPath);
- assertThat(nodeProgramExistsCommand.length, is(4));
-
- final InOrder inOrder = inOrder(docker);
- inOrder.verify(docker, times(1)).executeInContainer(
- eq(containerName),
- // Mockito fails if we put the array here instead...
- eq(nodeProgramExistsCommand[0]),
- eq(nodeProgramExistsCommand[1]),
- eq(nodeProgramExistsCommand[2]),
- eq(nodeProgramExistsCommand[3]));
- inOrder.verify(docker, times(1)).executeInContainer(
- eq(containerName),
- eq(command[0]),
- eq(command[1]));
-
- assertThat(result.isPresent(), is(true));
- assertThat(result.get(), is(actualResult));
+ dockerOperations.executeCommandInContainer(containerName, command);
}
@Test
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerFailTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerFailTest.java
index 9ad61baf585..06f8079172d 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerFailTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerFailTest.java
@@ -36,16 +36,14 @@ public class DockerFailTest {
CallOrderVerifier callOrderVerifier = dockerTester.getCallOrderVerifier();
callOrderVerifier.assertInOrder(
"createContainerCommand with DockerImage { imageId=dockerImage }, HostName: hostName, ContainerName { name=container }",
- "executeInContainer with ContainerName { name=container }, args: [/usr/bin/env, test, -x, " + DockerOperationsImpl.NODE_PROGRAM + "]",
- "executeInContainer with ContainerName { name=container }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]");
+ "executeInContainerAsRoot with ContainerName { name=container }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]");
dockerTester.deleteContainer(containerNodeSpec.containerName);
callOrderVerifier.assertInOrder(
"deleteContainer with ContainerName { name=container }",
"createContainerCommand with DockerImage { imageId=dockerImage }, HostName: hostName, ContainerName { name=container }",
- "executeInContainer with ContainerName { name=container }, args: [/usr/bin/env, test, -x, " + DockerOperationsImpl.NODE_PROGRAM + "]",
- "executeInContainer with ContainerName { name=container }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]");
+ "executeInContainerAsRoot with ContainerName { name=container }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]");
}
}
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerMock.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerMock.java
index fa4b235066b..2be286eca7b 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerMock.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerMock.java
@@ -156,6 +156,14 @@ public class DockerMock implements Docker {
return new ProcessResult(0, null, "");
}
+ @Override
+ public ProcessResult executeInContainerAsRoot(ContainerName containerName, String... args) {
+ synchronized (monitor) {
+ callOrderVerifier.add("executeInContainerAsRoot with " + containerName + ", args: " + Arrays.toString(args));
+ }
+ return new ProcessResult(0, null, "");
+ }
+
public static class StartContainerCommandMock implements CreateContainerCommand {
@Override
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerTester.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerTester.java
index 0912ccff814..aef03327645 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerTester.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerTester.java
@@ -49,10 +49,10 @@ public class DockerTester implements AutoCloseable {
Environment environment = new Environment.Builder().inetAddressResolver(inetAddressResolver).build();
callOrderVerifier = new CallOrderVerifier();
- StorageMaintainerMock storageMaintainer = new StorageMaintainerMock(environment, callOrderVerifier);
orchestratorMock = new OrchestratorMock(callOrderVerifier);
nodeRepositoryMock = new NodeRepoMock(callOrderVerifier);
dockerMock = new DockerMock(callOrderVerifier);
+ StorageMaintainerMock storageMaintainer = new StorageMaintainerMock(dockerMock, environment, callOrderVerifier);
MetricReceiverWrapper mr = new MetricReceiverWrapper(MetricReceiver.nullImplementation);
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/MultiDockerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/MultiDockerTest.java
index 2f88efd8620..582df7d22f6 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/MultiDockerTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/MultiDockerTest.java
@@ -38,19 +38,16 @@ public class MultiDockerTest {
CallOrderVerifier callOrderVerifier = dockerTester.getCallOrderVerifier();
callOrderVerifier.assertInOrder(
"createContainerCommand with DockerImage { imageId=image1 }, HostName: host1, ContainerName { name=container1 }",
- "executeInContainer with ContainerName { name=container1 }, args: [/usr/bin/env, test, -x, " + DockerOperationsImpl.NODE_PROGRAM + "]",
- "executeInContainer with ContainerName { name=container1 }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]",
+ "executeInContainerAsRoot with ContainerName { name=container1 }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]",
"createContainerCommand with DockerImage { imageId=image2 }, HostName: host2, ContainerName { name=container2 }",
- "executeInContainer with ContainerName { name=container2 }, args: [/usr/bin/env, test, -x, " + DockerOperationsImpl.NODE_PROGRAM + "]",
- "executeInContainer with ContainerName { name=container2 }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]",
+ "executeInContainerAsRoot with ContainerName { name=container2 }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]",
"stopContainer with ContainerName { name=container2 }",
"deleteContainer with ContainerName { name=container2 }",
"createContainerCommand with DockerImage { imageId=image1 }, HostName: host3, ContainerName { name=container3 }",
- "executeInContainer with ContainerName { name=container3 }, args: [/usr/bin/env, test, -x, " + DockerOperationsImpl.NODE_PROGRAM + "]",
- "executeInContainer with ContainerName { name=container3 }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]");
+ "executeInContainerAsRoot with ContainerName { name=container3 }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]");
callOrderVerifier.assertInOrderWithAssertMessage("Maintainer did not receive call to delete application storage",
"deleteContainer with ContainerName { name=container2 }",
@@ -85,8 +82,7 @@ public class MultiDockerTest {
tester.getCallOrderVerifier().assertInOrder(
"createContainerCommand with " + dockerImage + ", HostName: " + hostName + ", " + containerName,
- "executeInContainer with " + containerName + ", args: [/usr/bin/env, test, -x, " + DockerOperationsImpl.NODE_PROGRAM + "]",
- "executeInContainer with " + containerName + ", args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]");
+ "executeInContainerAsRoot with " + containerName + ", args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]");
return containerNodeSpec;
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeStateTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeStateTest.java
index 53d3422e8d5..222d46658eb 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeStateTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/NodeStateTest.java
@@ -37,8 +37,7 @@ public class NodeStateTest {
tester.getCallOrderVerifier().assertInOrder(
"createContainerCommand with DockerImage { imageId=dockerImage }, HostName: host1, ContainerName { name=container }",
- "executeInContainer with ContainerName { name=container }, args: [/usr/bin/env, test, -x, " + DockerOperationsImpl.NODE_PROGRAM + "]",
- "executeInContainer with ContainerName { name=container }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]");
+ "executeInContainerAsRoot with ContainerName { name=container }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]");
}
@@ -58,8 +57,7 @@ public class NodeStateTest {
}
dockerTester.getCallOrderVerifier()
- .assertInOrder("executeInContainer with ContainerName { name=container }, args: [/usr/bin/env, test, -x, " + DockerOperationsImpl.NODE_PROGRAM + "]",
- "executeInContainer with ContainerName { name=container }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", stop]",
+ .assertInOrder("executeInContainerAsRoot with ContainerName { name=container }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", stop]",
"stopContainer with ContainerName { name=container }",
"deleteContainer with ContainerName { name=container }");
}
@@ -95,8 +93,7 @@ public class NodeStateTest {
callOrderVerifier.assertInOrderWithAssertMessage("Node not started again after being put to active state",
"deleteContainer with ContainerName { name=container }",
"createContainerCommand with DockerImage { imageId=newDockerImage }, HostName: host1, ContainerName { name=container }",
- "executeInContainer with ContainerName { name=container }, args: [/usr/bin/env, test, -x, " + DockerOperationsImpl.NODE_PROGRAM + "]",
- "executeInContainer with ContainerName { name=container }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]");
+ "executeInContainerAsRoot with ContainerName { name=container }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", resume]");
}
}
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RestartTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RestartTest.java
index adeac210325..9c2adca3bb7 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RestartTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RestartTest.java
@@ -40,7 +40,7 @@ public class RestartTest {
dockerTester.updateContainerNodeSpec(createContainerNodeSpec(wantedRestartGeneration, currentRestartGeneration));
callOrderVerifier.assertInOrder("Suspend for host1",
- "executeInContainer with ContainerName { name=container }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", restart]");
+ "executeInContainerAsRoot with ContainerName { name=container }, args: [" + DockerOperationsImpl.NODE_PROGRAM + ", restart]");
}
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/StorageMaintainerMock.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/StorageMaintainerMock.java
index a2b3d655b6c..6107ca74752 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/StorageMaintainerMock.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/StorageMaintainerMock.java
@@ -1,7 +1,10 @@
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.integrationTests;
+import com.yahoo.metrics.simple.MetricReceiver;
import com.yahoo.vespa.hosted.dockerapi.ContainerName;
+import com.yahoo.vespa.hosted.dockerapi.Docker;
+import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec;
import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer;
import com.yahoo.vespa.hosted.node.admin.util.Environment;
@@ -15,8 +18,8 @@ import java.util.Map;
public class StorageMaintainerMock extends StorageMaintainer {
private final CallOrderVerifier callOrderVerifier;
- public StorageMaintainerMock(Environment environment, CallOrderVerifier callOrderVerifier) {
- super(environment);
+ public StorageMaintainerMock(Docker docker, Environment environment, CallOrderVerifier callOrderVerifier) {
+ super(docker, new MetricReceiverWrapper(MetricReceiver.nullImplementation), environment);
this.callOrderVerifier = callOrderVerifier;
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java
index 886536a5555..25d267eef28 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java
@@ -1,12 +1,15 @@
package com.yahoo.vespa.hosted.node.admin.maintenance;
+import com.yahoo.metrics.simple.MetricReceiver;
+import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
import com.yahoo.vespa.hosted.node.admin.util.Environment;
-import com.yahoo.vespa.hosted.node.maintenance.DeleteOldAppDataTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
import static org.junit.Assert.*;
@@ -21,12 +24,17 @@ public class StorageMaintainerTest {
@Test
public void testDiskUsed() throws IOException, InterruptedException {
int writeSize = 10000;
- DeleteOldAppDataTest.writeNBytesToFile(folder.newFile(), writeSize);
+ writeNBytesToFile(folder.newFile(), writeSize);
Environment environment = new Environment.Builder().build();
- StorageMaintainer storageMaintainer = new StorageMaintainer(environment);
- long usedBytes = storageMaintainer.getDiscUsedInBytes(folder.getRoot());
+ StorageMaintainer storageMaintainer = new StorageMaintainer(null,
+ new MetricReceiverWrapper(MetricReceiver.nullImplementation), environment);
+ long usedBytes = storageMaintainer.getDiscUsedInBytes(folder.getRoot().toPath());
if (usedBytes * 4 < writeSize || usedBytes > writeSize * 4)
fail("Used bytes is " + usedBytes + ", but wrote " + writeSize + " bytes, not even close.");
}
+
+ private static void writeNBytesToFile(File file, int nBytes) throws IOException {
+ Files.write(file.toPath(), new byte[nBytes]);
+ }
} \ No newline at end of file
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/maintenance/CoreCollectorTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/maintenance/CoreCollectorTest.java
deleted file mode 100644
index 1f9b480b0c1..00000000000
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/maintenance/CoreCollectorTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.maintenance;
-
-import com.yahoo.vespa.hosted.dockerapi.ProcessResult;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-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.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-/**
- * @author freva
- */
-public class CoreCollectorTest {
- private final Maintainer maintainer = mock(Maintainer.class);
- private final CoreCollector coreCollector = new CoreCollector(maintainer);
-
- private final Path TEST_CORE_PATH = Paths.get("/tmp/core.1234");
- private final Path TEST_BIN_PATH = Paths.get("/usr/bin/program");
- private final List<String> GDB_BACKTRACE = Arrays.asList("[New Thread 2703]",
- "Core was generated by `/usr/bin/program\'.", "Program terminated with signal 11, Segmentation fault.",
- "#0 0x00000000004004d8 in main (argv=0x1) at main.c:4", "4\t printf(argv[3]);",
- "#0 0x00000000004004d8 in main (argv=0x1) at main.c:4");
-
- @Rule
- public TemporaryFolder folder= new TemporaryFolder();
-
- private void mockExec(String[] cmd, String output) throws IOException, InterruptedException {
- mockExec(cmd, output, "");
- }
-
- private void mockExec(String[] cmd, String output, String error) throws IOException, InterruptedException {
- when(maintainer.exec(cmd)).thenReturn(new ProcessResult(error.isEmpty() ? 0 : 1, output, error));
- }
-
- @Test
- public void extractsBinaryPathTest() throws IOException, InterruptedException {
- final String[] cmd = {"file", TEST_CORE_PATH.toString()};
-
- mockExec(cmd,
- "/tmp/core.1234: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from " +
- "'/usr/bin/program'");
- assertEquals(TEST_BIN_PATH, coreCollector.readBinPath(TEST_CORE_PATH));
-
- mockExec(cmd,
- "/tmp/core.1234: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from " +
- "'/usr/bin/program --foo --bar baz'");
- assertEquals(TEST_BIN_PATH, coreCollector.readBinPath(TEST_CORE_PATH));
-
- mockExec(cmd,
- "/tmp/core.1234: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from " +
- "'/usr/bin//program'");
- assertEquals(TEST_BIN_PATH, coreCollector.readBinPath(TEST_CORE_PATH));
-
- mockExec(cmd,
- "/tmp/core.1234: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, " +
- "from 'program', real uid: 0, effective uid: 0, real gid: 0, effective gid: 0, " +
- "execfn: '/usr/bin/program', platform: 'x86_64");
- assertEquals(TEST_BIN_PATH, coreCollector.readBinPath(TEST_CORE_PATH));
-
-
- Path fallbackResponse = Paths.get("/response/from/fallback");
- mockExec(new String[]{"sh", "-c", "\"/home/y/bin64/gdb -n -batch -core /tmp/core.1234 | grep '^Core was generated by'\""},
- "Core was generated by `/response/from/fallback'.");
- mockExec(cmd,
- "/tmp/core.1234: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style");
- assertEquals(fallbackResponse, coreCollector.readBinPath(TEST_CORE_PATH));
-
- mockExec(cmd, "", "Error code 1234");
- assertEquals(fallbackResponse, coreCollector.readBinPath(TEST_CORE_PATH));
- }
-
- @Test
- public void extractsBinaryPathUsingGdbTest() throws IOException, InterruptedException {
- final String[] cmd = new String[]{"sh", "-c",
- "\"/home/y/bin64/gdb -n -batch -core /tmp/core.1234 | grep '^Core was generated by'\""};
-
- mockExec(cmd, "Core was generated by `/usr/bin/program-from-gdb --identity foo/search/cluster.content_'.");
- assertEquals(Paths.get("/usr/bin/program-from-gdb"), coreCollector.readBinPathFallback(TEST_CORE_PATH));
-
- mockExec(cmd, "", "Error 123");
- try {
- coreCollector.readBinPathFallback(TEST_CORE_PATH);
- fail("Expected not to be able to get bin path");
- } catch (RuntimeException e) {
- assertEquals(e.getMessage(), "Failed to extract binary path from ProcessResult { exitStatus=1 output= errors=Error 123 }");
- }
- }
-
- @Test
- public void extractsBacktraceUsingGdb() throws IOException, InterruptedException {
- mockExec(new String[]{"/home/y/bin64/gdb", "-n", "-ex", "bt", "-batch", "/usr/bin/program", "/tmp/core.1234"},
- String.join("\n", GDB_BACKTRACE));
- assertEquals(GDB_BACKTRACE, coreCollector.readBacktrace(TEST_CORE_PATH, TEST_BIN_PATH, false));
-
- mockExec(new String[]{"/home/y/bin64/gdb", "-n", "-ex", "bt", "-batch", "/usr/bin/program", "/tmp/core.1234"},
- "", "Failure");
- try {
- coreCollector.readBacktrace(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 }", e.getMessage());
- }
- }
-
- @Test
- public void extractsBacktraceFromAllThreadsUsingGdb() throws IOException, InterruptedException {
- mockExec(new String[]{"/home/y/bin64/gdb", "-n", "-ex", "thread apply all bt", "-batch",
- "/usr/bin/program", "/tmp/core.1234"},
- String.join("\n", GDB_BACKTRACE));
- assertEquals(GDB_BACKTRACE, coreCollector.readBacktrace(TEST_CORE_PATH, TEST_BIN_PATH, true));
- }
-
- @Test
- public void collectsDataTest() throws IOException, InterruptedException {
- mockExec(new String[]{"file", TEST_CORE_PATH.toString()},
- "/tmp/core.1234: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from " +
- "'/usr/bin/program'");
- mockExec(new String[]{"/home/y/bin64/gdb", "-n", "-ex", "bt", "-batch", "/usr/bin/program", "/tmp/core.1234"},
- String.join("\n", GDB_BACKTRACE));
- mockExec(new String[]{"/home/y/bin64/gdb", "-n", "-ex", "thread apply all bt", "-batch",
- "/usr/bin/program", "/tmp/core.1234"},
- String.join("\n", GDB_BACKTRACE));
-
- Map<String, Object> expectedData = new HashMap<>();
- expectedData.put("bin_path", TEST_BIN_PATH.toString());
- expectedData.put("backtrace", new ArrayList<>(GDB_BACKTRACE));
- expectedData.put("backtrace_all_threads", new ArrayList<>(GDB_BACKTRACE));
- assertEquals(expectedData, coreCollector.collect(TEST_CORE_PATH));
- }
-
- @Test
- public void collectsPartialIfUnableToDetermineDumpingProgramTest() {
- Map<String, Object> expectedData = new HashMap<>();
- assertEquals(expectedData, coreCollector.collect(TEST_CORE_PATH));
- }
-
- @Test
- public void collectsPartialIfBacktraceFailsTest() throws IOException, InterruptedException {
- mockExec(new String[]{"file", TEST_CORE_PATH.toString()},
- "/tmp/core.1234: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from " +
- "'/usr/bin/program'");
- mockExec(new String[]{"/home/y/bin64/gdb -n -ex bt -batch /usr/bin/program /tmp/core.1234"},
- "", "Failure");
-
- Map<String, Object> expectedData = new HashMap<>();
- expectedData.put("bin_path", TEST_BIN_PATH.toString());
- assertEquals(expectedData, coreCollector.collect(TEST_CORE_PATH));
- }
-
- @Test
- public void parseTotalMemoryTestTest() throws IOException {
- String memInfo = "MemTotal: 100000000 kB\nMemUsed: 1000000 kB\n";
- assertEquals(100000000, coreCollector.parseTotalMemorySize(memInfo));
-
- String badMemInfo = "This string has no memTotal value";
- try {
- coreCollector.parseTotalMemorySize(badMemInfo);
- fail("Expected to fail on parsing");
- } catch (RuntimeException e) {
- assertEquals("Could not parse meminfo: " + badMemInfo, e.getMessage());
- }
- }
-
- @Test
- public void testDeleteUncompressedFiles() throws IOException {
- final String documentId = "UIDD-ABCD-EFGH";
- final String coreDumpFilename = "core.dump";
-
- Path coredumpPath = folder.newFolder("crash").toPath()
- .resolve(CoredumpHandler.PROCESSING_DIRECTORY_NAME)
- .resolve(documentId);
- coredumpPath.toFile().mkdirs();
- coredumpPath.resolve(coreDumpFilename).toFile().createNewFile();
-
- Set<Path> expectedContentsOfCoredump = new HashSet<>(Arrays.asList(
- coredumpPath.resolve(CoredumpHandler.METADATA_FILE_NAME),
- coredumpPath.resolve(coreDumpFilename + ".lz4")));
- expectedContentsOfCoredump.forEach(path -> {
- try {
- path.toFile().createNewFile();
- } catch (IOException ignored) { ignored.printStackTrace();}
- });
- coreCollector.deleteDecompressedCoredump(coredumpPath.resolve(coreDumpFilename));
-
- assertEquals(expectedContentsOfCoredump, Files.list(coredumpPath).collect(Collectors.toSet()));
- }
-
- @Test
- public void testDeleteUncompressedFilesWithoutLz4() throws IOException {
- final String documentId = "UIDD-ABCD-EFGH";
- final String coreDumpFilename = "core.dump";
-
- Path coredumpPath = folder.newFolder("crash").toPath()
- .resolve(CoredumpHandler.PROCESSING_DIRECTORY_NAME)
- .resolve(documentId);
- coredumpPath.toFile().mkdirs();
-
- Set<Path> expectedContentsOfCoredump = new HashSet<>(Arrays.asList(
- coredumpPath.resolve(CoredumpHandler.METADATA_FILE_NAME),
- coredumpPath.resolve(coreDumpFilename)));
- expectedContentsOfCoredump.forEach(path -> {
- try {
- path.toFile().createNewFile();
- } catch (IOException ignored) { ignored.printStackTrace();}
- });
- coreCollector.deleteDecompressedCoredump(coredumpPath.resolve(coreDumpFilename));
-
- assertEquals(expectedContentsOfCoredump, Files.list(coredumpPath).collect(Collectors.toSet()));
- }
-}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/maintenance/CoredumpHandlerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/maintenance/CoredumpHandlerTest.java
deleted file mode 100644
index 1dfc6abeb6b..00000000000
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/maintenance/CoredumpHandlerTest.java
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.maintenance;
-
-import org.apache.http.HttpHeaders;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpVersion;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.DefaultHttpResponseFactory;
-import org.apache.http.message.BasicStatusLine;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.mockito.ArgumentCaptor;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-/**
- * @author freva
- */
-public class CoredumpHandlerTest {
- private final HttpClient httpClient = mock(HttpClient.class);
- private final CoreCollector coreCollector = mock(CoreCollector.class);
- private static final Map<String, Object> attributes = new LinkedHashMap<>();
- private static final Map<String, Object> metadata = new LinkedHashMap<>();
- private static final String expectedMetadataFileContents = "{\"fields\":{" +
- "\"bin_path\":\"/bin/bash\"," +
- "\"backtrace\":[\"call 1\",\"function 2\",\"something something\"]," +
- "\"hostname\":\"host123.yahoo.com\"," +
- "\"vespa_version\":\"6.48.4\"," +
- "\"kernel_version\":\"2.6.32-573.22.1.el6.YAHOO.20160401.10.x86_64\"," +
- "\"docker_image\":\"vespa/ci:6.48.4\"}}";
-
- static {
- attributes.put("hostname", "host123.yahoo.com");
- attributes.put("vespa_version", "6.48.4");
- attributes.put("kernel_version", "2.6.32-573.22.1.el6.YAHOO.20160401.10.x86_64");
- attributes.put("docker_image", "vespa/ci:6.48.4");
-
- metadata.put("bin_path", "/bin/bash");
- metadata.put("backtrace", Arrays.asList("call 1", "function 2", "something something"));
- }
-
- private final CoredumpHandler coredumpHandler = new CoredumpHandler(httpClient, coreCollector);
-
-
- @Rule
- public TemporaryFolder folder= new TemporaryFolder();
-
- @Test
- public void ignoresIncompleteCoredumps() throws IOException {
- Path coredumpPath = createCoredump(".core.dump");
- Path crashPath = coredumpPath.getParent();
- Path processingPath = coredumpHandler.processCoredumps(crashPath, attributes);
-
- // The 'processing' directory should be empty
- assertFolderContents(processingPath);
-
- // The 'crash' directory should have 'processing' and the incomplete core dump in it
- assertFolderContents(crashPath, processingPath.getFileName().toString(), coredumpPath.getFileName().toString());
- }
-
- @Test
- public void startProcessingTest() throws IOException {
- Path coredumpPath = createCoredump("core.dump");
- Path crashPath = coredumpPath.getParent();
- Path processingPath = crashPath.resolve("processing_dir");
- coredumpHandler.startProcessing(coredumpPath, crashPath.resolve("processing_dir"));
-
- // Contents of 'crash' should be only the 'processing' directory
- assertFolderContents(crashPath, processingPath.getFileName().toString());
-
- // The 'processing' directory should have 1 directory inside for the core.dump we just created
- List<Path> processedCoredumps = Files.list(processingPath).collect(Collectors.toList());
- assertEquals(processedCoredumps.size(), 1);
-
- // Inside the coredump directory, there should be 1 file: core.dump
- assertFolderContents(processedCoredumps.get(0), coredumpPath.getFileName().toString());
- }
-
- @Test
- public void coredumpMetadataCollectAndWriteTest() throws IOException, InterruptedException {
- when(coreCollector.collect(any())).thenReturn(metadata);
- Path coredumpPath = createCoredump("core.dump");
- Path crashPath = coredumpPath.getParent();
- Path processingPath = coredumpHandler.processCoredumps(crashPath, attributes);
-
- // Inside 'processing' directory, there should be a new directory containing 'metadata.json' file
- List<Path> processedCoredumps = Files.list(processingPath).collect(Collectors.toList());
- String metadataFileContents = new String(Files.readAllBytes(
- processedCoredumps.get(0).resolve(CoredumpHandler.METADATA_FILE_NAME)));
- assertEquals(expectedMetadataFileContents, metadataFileContents);
- }
-
- @Test
- public void reportSuccessCoredumpTest() throws IOException, URISyntaxException, InterruptedException {
- final String documentId = "UIDD-ABCD-EFGH";
- Path coredumpPath = createProcessedCoredump(documentId);
-
- setNextHttpResponse(200, Optional.empty());
- coredumpHandler.report(coredumpPath.getParent());
- validateNextHttpPost(documentId, expectedMetadataFileContents);
- }
-
- @Test
- public void reportFailCoredumpTest() throws IOException, URISyntaxException {
- final String documentId = "UIDD-ABCD-EFGH";
-
- Path metadataPath = createProcessedCoredump(documentId);
- Path crashPath = metadataPath.getParent().getParent().getParent();
- Path donePath = folder.newFolder("done").toPath();
-
- setNextHttpResponse(500, Optional.of("Internal server error"));
- coredumpHandler.reportCoredumps(crashPath.resolve(CoredumpHandler.PROCESSING_DIRECTORY_NAME), donePath);
- validateNextHttpPost(documentId, expectedMetadataFileContents);
-
- // The coredump should not have been moved out of 'processing' and into 'done' as the report failed
- assertFolderContents(donePath);
- assertFolderContents(metadataPath.getParent(), CoredumpHandler.METADATA_FILE_NAME);
- }
-
- @Test
- public void finishProcessingTest() throws IOException {
- final String documentId = "UIDD-ABCD-EFGH";
-
- Path coredumpPath = createProcessedCoredump(documentId);
- Path crashPath = coredumpPath.getParent().getParent().getParent();
- Path donePath = folder.newFolder("done").toPath();
-
- coredumpHandler.finishProcessing(coredumpPath.getParent(), donePath);
-
- // The coredump should've been moved out of 'processing' and into 'done'
- assertFolderContents(crashPath.resolve(CoredumpHandler.PROCESSING_DIRECTORY_NAME));
- assertFolderContents(donePath.resolve(documentId), CoredumpHandler.METADATA_FILE_NAME);
- }
-
-
- private static void assertFolderContents(Path pathToFolder, String... filenames) throws IOException {
- Set<Path> expectedContentsOfFolder = Arrays.stream(filenames)
- .map(pathToFolder::resolve)
- .collect(Collectors.toSet());
- Set<Path> actualContentsOfFolder = Files.list(pathToFolder).collect(Collectors.toSet());
- assertEquals(expectedContentsOfFolder, actualContentsOfFolder);
- }
-
- private Path createCoredump(String coredumpName) throws IOException {
- Path crashPath = folder.newFolder("crash").toPath();
- Path coredumpPath = crashPath.resolve(coredumpName);
- coredumpPath.toFile().createNewFile();
- return coredumpPath;
- }
-
- private Path createProcessedCoredump(String documentId) throws IOException {
- Path crashPath = folder.newFolder("crash").toPath();
- Path coredumpPath = crashPath
- .resolve(CoredumpHandler.PROCESSING_DIRECTORY_NAME)
- .resolve(documentId)
- .resolve(CoredumpHandler.METADATA_FILE_NAME);
- coredumpPath.getParent().toFile().mkdirs();
- return Files.write(coredumpPath, expectedMetadataFileContents.getBytes());
- }
-
- private void setNextHttpResponse(int code, Optional<String> message) throws IOException {
- DefaultHttpResponseFactory responseFactory = new DefaultHttpResponseFactory();
- HttpResponse httpResponse = responseFactory.newHttpResponse(
- new BasicStatusLine(HttpVersion.HTTP_1_1, code, null), null);
- if (message.isPresent()) httpResponse.setEntity(new StringEntity(message.get()));
-
- when(httpClient.execute(any())).thenReturn(httpResponse);
- }
-
- private void validateNextHttpPost(String documentId, String expectedBody) throws IOException, URISyntaxException {
- ArgumentCaptor<HttpPost> capturedPost = ArgumentCaptor.forClass(HttpPost.class);
- verify(httpClient).execute(capturedPost.capture());
-
- URI expectedURI = new URI(CoredumpHandler.FEED_ENDPOINT + "/" + documentId);
- assertEquals(expectedURI, capturedPost.getValue().getURI());
- assertEquals("application/json", capturedPost.getValue().getHeaders(HttpHeaders.CONTENT_TYPE)[0].getValue());
- assertEquals(expectedBody,
- new BufferedReader(new InputStreamReader(capturedPost.getValue().getEntity().getContent())).readLine());
- }
-}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/maintenance/DeleteOldAppDataTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/maintenance/DeleteOldAppDataTest.java
deleted file mode 100644
index 41a42095b1d..00000000000
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/maintenance/DeleteOldAppDataTest.java
+++ /dev/null
@@ -1,287 +0,0 @@
-// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.maintenance;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.time.Duration;
-import java.util.Arrays;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertTrue;
-
-/**
- * @author freva
- */
-public class DeleteOldAppDataTest {
- @Rule
- public TemporaryFolder folder = new TemporaryFolder();
-
- @Before
- public void initFiles() throws IOException {
- for (int i=0; i<10; i++) {
- File temp = folder.newFile("test_" + i + ".json");
- temp.setLastModified(System.currentTimeMillis() - i*Duration.ofSeconds(130).toMillis());
- }
-
- for (int i=0; i<7; i++) {
- File temp = folder.newFile("test_" + i + "_file.test");
- temp.setLastModified(System.currentTimeMillis() - i*Duration.ofSeconds(250).toMillis());
- }
-
- for (int i=0; i<5; i++) {
- File temp = folder.newFile(i + "-abc" + ".json");
- temp.setLastModified(System.currentTimeMillis() - i*Duration.ofSeconds(80).toMillis());
- }
-
- File temp = folder.newFile("week_old_file.json");
- temp.setLastModified(System.currentTimeMillis() - Duration.ofDays(8).toMillis());
- }
-
- @Test
- public void testDeleteAll() {
- DeleteOldAppData.deleteFiles(folder.getRoot().getAbsolutePath(), 0, null, false);
-
- assertThat(folder.getRoot().listFiles().length, is(0));
- }
-
- @Test
- public void testDeletePrefix() {
- DeleteOldAppData.deleteFiles(folder.getRoot().getAbsolutePath(), 0, "^test_", false);
-
- assertThat(folder.getRoot().listFiles().length, is(6)); // 5 abc files + 1 week_old_file
- }
-
- @Test
- public void testDeleteSuffix() {
- DeleteOldAppData.deleteFiles(folder.getRoot().getAbsolutePath(), 0, ".json$", false);
-
- assertThat(folder.getRoot().listFiles().length, is(7));
- }
-
- @Test
- public void testDeletePrefixAndSuffix() {
- DeleteOldAppData.deleteFiles(folder.getRoot().getAbsolutePath(), 0, "^test_.*\\.json$", false);
-
- assertThat(folder.getRoot().listFiles().length, is(13)); // 5 abc files + 7 test_*_file.test files + week_old_file
- }
-
- @Test
- public void testDeleteOld() {
- DeleteOldAppData.deleteFiles(folder.getRoot().getAbsolutePath(), 600, null, false);
-
- assertThat(folder.getRoot().listFiles().length, is(13)); // All 23 - 6 (from test_*_.json) - 3 (from test_*_file.test) - 1 week old file
- }
-
- @Test
- public void testDeleteWithAllParameters() {
- DeleteOldAppData.deleteFiles(folder.getRoot().getAbsolutePath(), 200, "^test_.*\\.json$", false);
-
- assertThat(folder.getRoot().listFiles().length, is(15)); // All 23 - 8 (from test_*_.json)
- }
-
- @Test
- public void testDeleteWithSubDirectoriesNoRecursive() throws IOException {
- initSubDirectories();
- DeleteOldAppData.deleteFiles(folder.getRoot().getAbsolutePath(), 0, "^test_.*\\.json$", false);
-
- // 6 test_*.json from test_folder1/
- // + 9 test_*.json and 4 abc_*.json from test_folder2/
- // + 13 test_*.json from test_folder2/subSubFolder2/
- // + 7 test_*_file.test and 5 *-abc.json and 1 week_old_file from root
- // + test_folder1/ and test_folder2/ and test_folder2/subSubFolder2/ themselves
- assertThat(getNumberOfFilesAndDirectoriesIn(folder.getRoot()), is(48));
- }
-
- @Test
- public void testDeleteWithSubDirectoriesRecursive() throws IOException {
- initSubDirectories();
- DeleteOldAppData.deleteFiles(folder.getRoot().getAbsolutePath(), 0, "^test_.*\\.json$", true);
-
- // 4 abc_*.json from test_folder2/
- // + 7 test_*_file.test and 5 *-abc.json and 1 week_old_file from root
- // + test_folder2/ itself
- assertThat(getNumberOfFilesAndDirectoriesIn(folder.getRoot()), is(18));
- }
-
- @Test
- public void testDeleteFilesWhereFilenameRegexAlsoMatchesDirectories() throws IOException {
- initSubDirectories();
-
- DeleteOldAppData.deleteFiles(folder.getRoot().getAbsolutePath(), 0, "^test_", false);
-
- assertThat(folder.getRoot().listFiles().length, is(8)); // 5 abc files + 1 week_old_file + 2 directories
- }
-
- @Test
- public void testGetContentsOfNonExistingDirectory() throws IOException {
- assertArrayEquals(new File[0], DeleteOldAppData.getContentsOfDirectory("/some/made/up/dir/"));
- }
-
- @Test(expected=IllegalArgumentException.class)
- public void testDeleteFilesExceptNMostRecentWithNegativeN() {
- DeleteOldAppData.deleteFilesExceptNMostRecent(folder.getRoot().getAbsolutePath(), -5);
- }
-
- @Test
- public void testDeleteFilesExceptFiveMostRecent() {
- DeleteOldAppData.deleteFilesExceptNMostRecent(folder.getRoot().getAbsolutePath(), 5);
-
- assertThat(folder.getRoot().listFiles().length, is(5));
-
- String[] oldestFiles = {"test_5_file.test", "test_6_file.test", "test_8.json", "test_9.json", "week_old_file.json"};
- String[] remainingFiles = folder.getRoot().list();
- Arrays.sort(remainingFiles);
-
- assertArrayEquals(oldestFiles, remainingFiles);
- }
-
- @Test
- public void testDeleteFilesExceptNMostRecentWithLargeN() {
- String[] filesPreDelete = folder.getRoot().list();
-
- DeleteOldAppData.deleteFilesExceptNMostRecent(folder.getRoot().getAbsolutePath(), 50);
-
- assertArrayEquals(filesPreDelete, folder.getRoot().list());
- }
-
- @Test
- public void testDeleteFilesLargerThan10B() throws IOException {
- initSubDirectories();
-
- File temp1 = new File(folder.getRoot(), "small_file");
- writeNBytesToFile(temp1, 50);
-
- File temp2 = new File(folder.getRoot(), "some_file");
- writeNBytesToFile(temp2, 20);
-
- File temp3 = new File(folder.getRoot(), "test_folder1/some_other_file");
- writeNBytesToFile(temp3, 75);
-
- DeleteOldAppData.deleteFilesLargerThan(folder.getRoot(), 10);
-
- assertThat(getNumberOfFilesAndDirectoriesIn(folder.getRoot()), is(58));
- assertThat(temp1.exists() || temp2.exists() || temp3.exists(), is(false));
- }
-
- @Test
- public void testDeleteDirectories() throws IOException {
- initSubDirectories();
-
- DeleteOldAppData.deleteDirectories(folder.getRoot().getAbsolutePath(), 0, ".*folder2");
-
- //23 files in root
- // + 6 in test_folder1 + test_folder1 itself
- assertThat(getNumberOfFilesAndDirectoriesIn(folder.getRoot()), is(30));
- }
-
- @Test
- public void testDeleteDirectoriesBasedOnAge() throws IOException {
- initSubDirectories();
-
- DeleteOldAppData.deleteDirectories(folder.getRoot().getAbsolutePath(), 50, ".*folder.*");
-
- //23 files in root
- // + 13 in test_folder2
- // + 13 in subSubFolder2
- // + test_folder2 + subSubFolder2 itself
- assertThat(getNumberOfFilesAndDirectoriesIn(folder.getRoot()), is(51));
- }
-
- @Test
- public void testRecursivelyDeleteDirectory() throws IOException {
- initSubDirectories();
- DeleteOldAppData.recursiveDelete(folder.getRoot());
- assertTrue(!folder.getRoot().exists());
- }
-
- @Test
- public void testRecursivelyDeleteRegularFile() throws IOException {
- File file = folder.newFile();
- assertTrue(file.exists());
- assertTrue(file.isFile());
- DeleteOldAppData.recursiveDelete(file);
- assertTrue(!file.exists());
- }
-
- @Test
- public void testRecursivelyDeleteNonExistingFile() throws IOException {
- File file = folder.getRoot().toPath().resolve("non-existing-file.json").toFile();
- assertTrue(!file.exists());
- DeleteOldAppData.recursiveDelete(file);
- assertTrue(!file.exists());
- }
-
- @Test
- public void testInitSubDirectories() throws IOException {
- initSubDirectories();
- assertTrue(folder.getRoot().exists());
- assertTrue(folder.getRoot().isDirectory());
-
- Path test_folder1 = folder.getRoot().toPath().resolve("test_folder1");
- assertTrue(test_folder1.toFile().exists());
- assertTrue(test_folder1.toFile().isDirectory());
-
- Path test_folder2 = folder.getRoot().toPath().resolve("test_folder2");
- assertTrue(test_folder2.toFile().exists());
- assertTrue(test_folder2.toFile().isDirectory());
-
- Path subSubFolder2 = test_folder2.resolve("subSubFolder2");
- assertTrue(subSubFolder2.toFile().exists());
- assertTrue(subSubFolder2.toFile().isDirectory());
- }
-
- private void initSubDirectories() throws IOException {
- File subFolder1 = folder.newFolder("test_folder1");
- File subFolder2 = folder.newFolder("test_folder2");
- File subSubFolder2 = folder.newFolder("test_folder2/subSubFolder2");
-
-
- for (int j=0; j<6; j++) {
- File temp = File.createTempFile("test_", ".json", subFolder1);
- temp.setLastModified(System.currentTimeMillis() - (j+1)*Duration.ofSeconds(60).toMillis());
- }
-
- for (int j=0; j<9; j++) {
- File.createTempFile("test_", ".json", subFolder2);
- }
-
- for (int j=0; j<4; j++) {
- File.createTempFile("abc_", ".txt", subFolder2);
- }
-
- for (int j=0; j<13; j++) {
- File temp = File.createTempFile("test_", ".json", subSubFolder2);
- temp.setLastModified(System.currentTimeMillis() - (j+1)*Duration.ofSeconds(40).toMillis());
- }
-
- //Must be after all the files have been created
- subFolder1.setLastModified(System.currentTimeMillis() - Duration.ofHours(2).toMillis());
- subFolder2.setLastModified(System.currentTimeMillis() - Duration.ofHours(1).toMillis());
- subSubFolder2.setLastModified(System.currentTimeMillis() - Duration.ofHours(3).toMillis());
- }
-
- private static int getNumberOfFilesAndDirectoriesIn(File folder) {
- int total = 0;
- for (File file : folder.listFiles()) {
- if (file.isDirectory()) {
- total += getNumberOfFilesAndDirectoriesIn(file);
- }
- total++;
- }
-
- return total;
- }
-
- public static void writeNBytesToFile(File file, int nBytes) throws IOException {
- Files.write(file.toPath(), new byte[nBytes]);
- }
-}