diff options
16 files changed, 137 insertions, 45 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java index 937cf4dfe7f..58b7d0ec5bb 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java @@ -874,12 +874,8 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye // We make no validation that the hostname is actually allocated to the given application since // most applications under hosted-vespa are not known to the model and it's OK for a user to get // logs for any host if they are authorized for the hosted-vespa tenant. - if (hostname.isPresent()) { - if (HOSTED_VESPA_TENANT.equals(applicationId.tenant())) - return "http://" + hostname.get() + ":8080/logs"; - else - throw new IllegalArgumentException("Using hostname parameter when getting logs is not supported for application " - + applicationId); + if (hostname.isPresent() && HOSTED_VESPA_TENANT.equals(applicationId.tenant())) { + return "http://" + hostname.get() + ":8080/logs"; } Application application = getApplication(applicationId); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java index a4b2bfb8902..e20fef0909c 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ApplicationRepositoryTest.java @@ -234,14 +234,6 @@ public class ApplicationRepositoryTest { assertEquals(200, response.getStatus()); } - @Test(expected = IllegalArgumentException.class) - public void refuseToGetLogsFromHostnameNotInApplication() { - applicationRepository = createApplicationRepository(); - deployApp(testAppLogServerWithContainer); - HttpResponse response = applicationRepository.getLogs(applicationId(), Optional.of("host123.fake.yahoo.com"), ""); - assertEquals(200, response.getStatus()); - } - @Test public void deleteUnusedFileReferences() throws IOException { File fileReferencesDir = temporaryFolder.newFolder(); diff --git a/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java b/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java index f3149ed4998..b2a156862eb 100644 --- a/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java +++ b/container-core/src/main/java/com/yahoo/container/handler/LogHandler.java @@ -35,11 +35,13 @@ public class LogHandler extends ThreadedHttpRequestHandler { Instant to = Optional.ofNullable(request.getProperty("to")) .map(Long::valueOf).map(Instant::ofEpochMilli).orElse(Instant.MAX); + Optional<String> hostname = Optional.ofNullable(request.getProperty("hostname")); + return new HttpResponse(200) { @Override public void render(OutputStream outputStream) { try { - logReader.writeLogs(outputStream, from, to); + logReader.writeLogs(outputStream, from, to, hostname); } catch (Throwable t) { log.log(Level.WARNING, "Failed reading logs from " + from + " to " + to, t); diff --git a/container-core/src/main/java/com/yahoo/container/handler/LogReader.java b/container-core/src/main/java/com/yahoo/container/handler/LogReader.java index 3cf849a6835..8e4b9aea9b8 100644 --- a/container-core/src/main/java/com/yahoo/container/handler/LogReader.java +++ b/container-core/src/main/java/com/yahoo/container/handler/LogReader.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.Optional; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -57,7 +58,7 @@ class LogReader { this.logFilePattern = logFilePattern; } - void writeLogs(OutputStream out, Instant from, Instant to) { + void writeLogs(OutputStream out, Instant from, Instant to, Optional<String> hostname) { double fromSeconds = from.getEpochSecond() + from.getNano() / 1e9; double toSeconds = to.getEpochSecond() + to.getNano() / 1e9; BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out)); @@ -67,7 +68,7 @@ class LogReader { try { // Logs in each sub-list contain entries covering the same time interval, so do a merge sort while reading for (Path log : logs) - logLineIterators.add(new LogLineIterator(log, fromSeconds, toSeconds)); + logLineIterators.add(new LogLineIterator(log, fromSeconds, toSeconds, hostname)); Iterator<LineWithTimestamp> lines = Iterators.mergeSorted(logLineIterators, Comparator.comparingDouble(LineWithTimestamp::timestamp)); @@ -96,14 +97,16 @@ class LogReader { private final BufferedReader reader; private final double from; private final double to; + private final Optional<String> hostname; private LineWithTimestamp next; - private LogLineIterator(Path log, double from, double to) throws IOException { + private LogLineIterator(Path log, double from, double to, Optional<String> hostname) throws IOException { boolean zipped = log.toString().endsWith(".gz"); InputStream in = Files.newInputStream(log); this.reader = new BufferedReader(new InputStreamReader(zipped ? new GZIPInputStream(in) : in, UTF_8)); this.from = from; this.to = to; + this.hostname = hostname; this.next = readNext(); } @@ -131,6 +134,9 @@ class LogReader { if (parts.length != 7) continue; + if (hostname.map(host -> !host.equals(parts[1])).orElse(false)) + continue; + double timestamp = Double.parseDouble(parts[0]); if (timestamp > to) return null; diff --git a/container-core/src/test/java/com/yahoo/container/handler/LogHandlerTest.java b/container-core/src/test/java/com/yahoo/container/handler/LogHandlerTest.java index ab0d0d54675..97aa8864eae 100644 --- a/container-core/src/test/java/com/yahoo/container/handler/LogHandlerTest.java +++ b/container-core/src/test/java/com/yahoo/container/handler/LogHandlerTest.java @@ -9,6 +9,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.time.Instant; +import java.util.Optional; import java.util.concurrent.Executor; import static org.junit.Assert.assertEquals; @@ -47,7 +48,7 @@ public class LogHandlerTest { } @Override - protected void writeLogs(OutputStream out, Instant from, Instant to) { + protected void writeLogs(OutputStream out, Instant from, Instant to, Optional<String> hostname) { try { if (to.isAfter(Instant.ofEpochMilli(1000))) { out.write("newer log".getBytes()); diff --git a/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java b/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java index 3f7a78e13be..ad9398a5eec 100644 --- a/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java +++ b/container-core/src/test/java/com/yahoo/container/handler/LogReaderTest.java @@ -5,7 +5,6 @@ import com.yahoo.vespa.test.file.TestFileSystem; import org.junit.Before; import org.junit.Test; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -14,8 +13,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.time.Instant; +import java.util.Optional; import java.util.regex.Pattern; -import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import static java.nio.charset.StandardCharsets.UTF_8; @@ -26,12 +25,12 @@ public class LogReaderTest { private final FileSystem fileSystem = TestFileSystem.create(); private final Path logDirectory = fileSystem.getPath("/opt/vespa/logs"); - private static final String logv11 = "3600.2\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tfourth\n"; - private static final String logv = "90000.1\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tlast\n"; - private static final String log100 = "0.2\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tsecond\n"; - private static final String log101 = "0.1\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstdout\tinfo\tERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)\n"; - private static final String log110 = "3600.1\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstderr\twarning\tthird\n"; - private static final String log200 = "86400.1\t17491290-v6-1.ostk.bm2.prod.ne1.yahoo.com\t5480\tcontainer\tstderr\twarning\tjava.lang.NullPointerException\\n\\tat org.apache.felix.framework.BundleRevisionImpl.calculateContentPath(BundleRevisionImpl.java:438)\\n\\tat org.apache.felix.framework.BundleRevisionImpl.initializeContentPath(BundleRevisionImpl.java:371)\n"; + private static final String logv11 = "3600.2\tnode1.com\t5480\tcontainer\tstdout\tinfo\tfourth\n"; + private static final String logv = "90000.1\tnode1.com\t5480\tcontainer\tstdout\tinfo\tlast\n"; + private static final String log100 = "0.2\tnode2.com\t5480\tcontainer\tstdout\tinfo\tsecond\n"; + private static final String log101 = "0.1\tnode2.com\t5480\tcontainer\tstdout\tinfo\tERROR: Bundle canary-application [71] Unable to get module class path. (java.lang.NullPointerException)\n"; + private static final String log110 = "3600.1\tnode1.com\t5480\tcontainer\tstderr\twarning\tthird\n"; + private static final String log200 = "86400.1\tnode2.com\t5480\tcontainer\tstderr\twarning\tjava.lang.NullPointerException\\n\\tat org.apache.felix.framework.BundleRevisionImpl.calculateContentPath(BundleRevisionImpl.java:438)\\n\\tat org.apache.felix.framework.BundleRevisionImpl.initializeContentPath(BundleRevisionImpl.java:371)\n"; @Before public void setup() throws IOException { @@ -52,32 +51,41 @@ public class LogReaderTest { } @Test - public void testThatLogsOutsideRangeAreExcluded() throws Exception { + public void testThatLogsOutsideRangeAreExcluded() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); LogReader logReader = new LogReader(logDirectory, Pattern.compile(".*")); - logReader.writeLogs(baos, Instant.ofEpochMilli(150), Instant.ofEpochMilli(3601050)); + logReader.writeLogs(baos, Instant.ofEpochMilli(150), Instant.ofEpochMilli(3601050), Optional.empty()); assertEquals(log100 + logv11 + log110, baos.toString(UTF_8)); } @Test - public void testThatLogsNotMatchingRegexAreExcluded() throws Exception { + public void testThatLogsNotMatchingRegexAreExcluded() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); LogReader logReader = new LogReader(logDirectory, Pattern.compile(".*-1.*")); - logReader.writeLogs(baos, Instant.EPOCH, Instant.EPOCH.plus(Duration.ofDays(2))); + logReader.writeLogs(baos, Instant.EPOCH, Instant.EPOCH.plus(Duration.ofDays(2)), Optional.empty()); assertEquals(log101 + logv11, baos.toString(UTF_8)); } @Test - public void testZippedStreaming() throws IOException { + public void testZippedStreaming() { ByteArrayOutputStream zippedBaos = new ByteArrayOutputStream(); LogReader logReader = new LogReader(logDirectory, Pattern.compile(".*")); - logReader.writeLogs(zippedBaos, Instant.EPOCH, Instant.EPOCH.plus(Duration.ofDays(2))); + logReader.writeLogs(zippedBaos, Instant.EPOCH, Instant.EPOCH.plus(Duration.ofDays(2)), Optional.empty()); assertEquals(log101 + log100 + logv11 + log110 + log200 + logv, zippedBaos.toString(UTF_8)); } + @Test + public void logsForSingeNodeIsRetrieved() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + LogReader logReader = new LogReader(logDirectory, Pattern.compile(".*")); + logReader.writeLogs(baos, Instant.EPOCH, Instant.EPOCH.plus(Duration.ofDays(2)), Optional.of("node2.com")); + + assertEquals(log101 + log100 + log200, baos.toString(UTF_8)); + } + private byte[] compress(String input) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStream zip = new GZIPOutputStream(baos); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/Jira.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/Jira.java index 30bf23f18a9..6848c67fed2 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/Jira.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/jira/Jira.java @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.api.integration.jira; +import java.io.InputStream; import java.util.List; /** @@ -14,5 +15,5 @@ public interface Jira { void commentIssue(JiraIssue issue, JiraComment comment); - void addAttachment(JiraIssue issue, String filename, String fileContent); + void addAttachment(JiraIssue issue, String filename, InputStream fileContent); } diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java index 818fd4db5cd..cc3cb3adc85 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -71,7 +71,8 @@ public class Flags { public static final UnboundListFlag<String> DISABLED_HOST_ADMIN_TASKS = defineListFlag( "disabled-host-admin-tasks", List.of(), String.class, - "List of host-admin task names (as they appear in the log, e.g. root>main>UpgradeTask) that should be skipped", + "List of host-admin task names (as they appear in the log, e.g. root>main>UpgradeTask), or some node-agent " + + "functionality (see NodeAgentTask), that should be skipped", "Takes effect on next host admin tick", HOSTNAME, NODE_TYPE); 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 30ca2e0d218..a5efec1dcb7 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 @@ -5,7 +5,6 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.NodeType; -import java.util.logging.Level; import com.yahoo.vespa.hosted.dockerapi.Container; import com.yahoo.vespa.hosted.dockerapi.ContainerName; import com.yahoo.vespa.hosted.node.admin.component.TaskContext; @@ -16,8 +15,9 @@ import com.yahoo.vespa.hosted.node.admin.maintenance.disk.DiskCleanupRule; import com.yahoo.vespa.hosted.node.admin.maintenance.disk.LinearCleanupRule; import com.yahoo.vespa.hosted.node.admin.nodeadmin.ConvergenceException; import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext; -import com.yahoo.vespa.hosted.node.admin.task.util.file.FileFinder; +import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentTask; import com.yahoo.vespa.hosted.node.admin.task.util.file.DiskSize; +import com.yahoo.vespa.hosted.node.admin.task.util.file.FileFinder; import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath; import com.yahoo.vespa.hosted.node.admin.task.util.process.Terminal; @@ -37,8 +37,8 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.function.Function; +import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Stream; import static com.yahoo.vespa.hosted.node.admin.maintenance.disk.DiskCleanupRule.Priority; import static com.yahoo.yolean.Exceptions.uncheck; @@ -107,6 +107,8 @@ public class StorageMaintainer { } public boolean cleanDiskIfFull(NodeAgentContext context) { + if (context.isDisabled(NodeAgentTask.DiskCleanup)) return false; + double totalBytes = context.node().diskSize().bytes(); // Delete enough bytes to get below 70% disk usage, but only if we are already using more than 80% disk long bytesToRemove = diskUsageFor(context) @@ -148,6 +150,7 @@ public class StorageMaintainer { /** Checks if container has any new coredumps, reports and archives them if so */ public void handleCoreDumpsForContainer(NodeAgentContext context, Optional<Container> container) { + if (context.isDisabled(NodeAgentTask.CoreDumps)) return; coredumpHandler.converge(context, () -> getCoredumpNodeAttributes(context, container)); } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java index 360cea8a60d..fe6b29402b5 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainer.java @@ -2,9 +2,9 @@ package com.yahoo.vespa.hosted.node.admin.maintenance.acl; import com.google.common.net.InetAddresses; -import java.util.logging.Level; import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations; import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext; +import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentTask; import com.yahoo.vespa.hosted.node.admin.task.util.file.Editor; import com.yahoo.vespa.hosted.node.admin.task.util.file.LineEditor; import com.yahoo.vespa.hosted.node.admin.task.util.network.IPAddresses; @@ -18,6 +18,7 @@ import java.nio.file.Path; import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.logging.Level; import java.util.logging.Logger; import static com.yahoo.yolean.Exceptions.uncheck; @@ -51,6 +52,8 @@ public class AclMaintainer { // ip(6)tables operate while having the xtables lock, run with synchronized to prevent multiple NodeAgents // invoking ip(6)tables concurrently. public synchronized void converge(NodeAgentContext context) { + if (context.isDisabled(NodeAgentTask.AclMaintainer)) return; + // Apply acl to the filter table editFlushOnError(context, IPVersion.IPv4, "filter", FilterTableLineEditor.from(context.acl(), IPVersion.IPv4)); editFlushOnError(context, IPVersion.IPv6, "filter", FilterTableLineEditor.from(context.acl(), IPVersion.IPv6)); diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java index 3320851a36c..d6c08a820cd 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java @@ -1,7 +1,6 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.node.admin.maintenance.identity; -import java.util.logging.Level; import com.yahoo.security.KeyAlgorithm; import com.yahoo.security.KeyStoreType; import com.yahoo.security.KeyUtils; @@ -24,6 +23,7 @@ import com.yahoo.vespa.athenz.utils.SiaUtils; import com.yahoo.vespa.hosted.dockerapi.ContainerName; import com.yahoo.vespa.hosted.node.admin.component.ConfigServerInfo; import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext; +import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentTask; import com.yahoo.vespa.hosted.node.admin.task.util.file.FileFinder; import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath; @@ -46,6 +46,7 @@ import java.time.Instant; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import java.util.logging.Logger; /** @@ -108,6 +109,8 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer { } public boolean converge(NodeAgentContext context) { + if (context.isDisabled(NodeAgentTask.CredentialsMaintainer)) return false; + try { context.log(logger, Level.FINE, "Checking certificate"); Path containerSiaDirectory = context.pathOnHostFromPathInNode(CONTAINER_SIA_DIRECTORY); diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContext.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContext.java index d589000c07e..872b8a8096b 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContext.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContext.java @@ -44,6 +44,10 @@ public interface NodeAgentContext extends TaskContext { String vespaUserOnHost(); + default boolean isDisabled(NodeAgentTask task) { + return false; + }; + /** * The vcpu value in NodeSpec is multiplied by the speedup factor per cpu core compared to a historical baseline * for a particular cpu generation of the host (see flavors.def cpuSpeedup). @@ -52,7 +56,7 @@ public interface NodeAgentContext extends TaskContext { */ double unscaledVcpu(); - /** The file system used by the NodeAgentContext. All paths must have the same provider. */ + /** The file system used by the NodeAgentContext. All paths must have the same provider. */ FileSystem fileSystem(); /** diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java index 9f0c8d47d64..c7c0675c30e 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImpl.java @@ -7,6 +7,10 @@ import com.yahoo.config.provision.zone.ZoneApi; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.api.AthenzService; +import com.yahoo.vespa.flags.FetchVector; +import com.yahoo.vespa.flags.FlagSource; +import com.yahoo.vespa.flags.Flags; +import com.yahoo.vespa.flags.InMemoryFlagSource; import com.yahoo.vespa.hosted.dockerapi.ContainerName; import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.Acl; import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec; @@ -18,6 +22,7 @@ import java.nio.file.Path; import java.nio.file.ProviderMismatchException; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; @@ -40,10 +45,11 @@ public class NodeAgentContextImpl implements NodeAgentContext { private final String vespaUser; private final String vespaUserOnHost; private final double cpuSpeedup; + private final Set<NodeAgentTask> disabledNodeAgentTasks; public NodeAgentContextImpl(NodeSpec node, Acl acl, AthenzIdentity identity, DockerNetworking dockerNetworking, ZoneApi zone, - FileSystem fileSystem, + FileSystem fileSystem, FlagSource flagSource, Path pathToContainerStorage, Path pathToVespaHome, String vespaUser, String vespaUserOnHost, double cpuSpeedup) { if (cpuSpeedup <= 0) @@ -55,13 +61,15 @@ public class NodeAgentContextImpl implements NodeAgentContext { this.identity = Objects.requireNonNull(identity); this.dockerNetworking = Objects.requireNonNull(dockerNetworking); this.zone = Objects.requireNonNull(zone); - this.fileSystem = fileSystem; + this.fileSystem = Objects.requireNonNull(fileSystem); this.pathToNodeRootOnHost = requireValidPath(pathToContainerStorage).resolve(containerName.asString()); this.pathToVespaHome = requireValidPath(pathToVespaHome); this.logPrefix = containerName.asString() + ": "; this.vespaUser = vespaUser; this.vespaUserOnHost = vespaUserOnHost; this.cpuSpeedup = cpuSpeedup; + this.disabledNodeAgentTasks = NodeAgentTask.fromString( + Flags.DISABLED_HOST_ADMIN_TASKS.bindTo(flagSource).with(FetchVector.Dimension.HOSTNAME, node.hostname()).value()); } @Override @@ -105,6 +113,11 @@ public class NodeAgentContextImpl implements NodeAgentContext { } @Override + public boolean isDisabled(NodeAgentTask task) { + return disabledNodeAgentTasks.contains(task); + } + + @Override public double unscaledVcpu() { return node.vcpu() / cpuSpeedup; } @@ -212,6 +225,7 @@ public class NodeAgentContextImpl implements NodeAgentContext { private String vespaUser; private String vespaUserOnHost; private FileSystem fileSystem = FileSystems.getDefault(); + private FlagSource flagSource; private double cpuSpeedUp = 1; public Builder(NodeSpec node) { @@ -268,6 +282,11 @@ public class NodeAgentContextImpl implements NodeAgentContext { return this; } + public Builder flagSource(FlagSource flagSource) { + this.flagSource = flagSource; + return this; + } + public Builder cpuSpeedUp(double cpuSpeedUp) { this.cpuSpeedUp = cpuSpeedUp; return this; @@ -301,6 +320,7 @@ public class NodeAgentContextImpl implements NodeAgentContext { } }), fileSystem, + Optional.ofNullable(flagSource).orElseGet(InMemoryFlagSource::new), fileSystem.getPath("/home/docker/container-storage"), fileSystem.getPath("/opt/vespa"), Optional.ofNullable(vespaUser).orElse("vespa"), diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentTask.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentTask.java new file mode 100644 index 00000000000..d57c680e190 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentTask.java @@ -0,0 +1,30 @@ +package com.yahoo.vespa.hosted.node.admin.nodeagent; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public enum NodeAgentTask { + + // The full task name is prefixed with 'node>', e.g. 'node>DiskCleanup' + DiskCleanup, + CoreDumps, + CredentialsMaintainer, + AclMaintainer; + + private static final Map<String, NodeAgentTask> tasksByName = Arrays.stream(NodeAgentTask.values()) + .collect(Collectors.toUnmodifiableMap(NodeAgentTask::taskName, n -> n)); + + private final String taskName; + NodeAgentTask() { + this.taskName = "node>" + name(); + } + + public String taskName() { return taskName; } + + public static Set<NodeAgentTask> fromString(List<String> tasks) { + return tasks.stream().filter(tasksByName::containsKey).map(tasksByName::get).collect(Collectors.toUnmodifiableSet()); + } +} diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImplTest.java index 9bcbce849af..b7e0a2a1d97 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImplTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentContextImplTest.java @@ -2,14 +2,19 @@ package com.yahoo.vespa.hosted.node.admin.nodeagent; import com.yahoo.config.provision.DockerImage; +import com.yahoo.vespa.flags.Flags; +import com.yahoo.vespa.flags.InMemoryFlagSource; import com.yahoo.vespa.test.file.TestFileSystem; import org.junit.Test; import java.nio.file.FileSystem; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * @author freva @@ -84,6 +89,23 @@ public class NodeAgentContextImplTest { assertRewrite("docker.tld/vespa/hosted:1.2.3", "/opt/vespa/log", "/opt/vespa/log"); } + @Test + public void disabledTasksTest() { + NodeAgentContext context1 = createContextWithDisabledTasks(); + assertFalse(context1.isDisabled(NodeAgentTask.DiskCleanup)); + assertFalse(context1.isDisabled(NodeAgentTask.CoreDumps)); + + NodeAgentContext context2 = createContextWithDisabledTasks("root>UpgradeTask", "DiskCleanup", "node>CoreDumps"); + assertFalse(context2.isDisabled(NodeAgentTask.DiskCleanup)); + assertTrue(context2.isDisabled(NodeAgentTask.CoreDumps)); + } + + private static NodeAgentContext createContextWithDisabledTasks(String... tasks) { + InMemoryFlagSource flagSource = new InMemoryFlagSource(); + flagSource.withListFlag(Flags.DISABLED_HOST_ADMIN_TASKS.id(), List.of(tasks), String.class); + return new NodeAgentContextImpl.Builder("node123").flagSource(flagSource).build(); + } + private static void assertRewrite(String dockerImage, String path, String expected) { NodeAgentContext context = new NodeAgentContextImpl.Builder("node123") .nodeSpecBuilder(ns -> ns.wantedDockerImage(DockerImage.fromString(dockerImage))) diff --git a/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp b/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp index 43cf43b02b6..73788d0affe 100644 --- a/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp +++ b/storage/src/vespa/storage/distributor/operations/external/twophaseupdateoperation.cpp @@ -277,8 +277,8 @@ TwoPhaseUpdateOperation::schedulePutsWithUpdatedDocument(std::shared_ptr<documen op.start(intermediate, _manager.getClock().getTimeInMillis()); transitionTo(SendState::PUTS_SENT); - LOG(debug, "Update(%s): sending Put commands with doc %s", - update_doc_id().c_str(), doc->toString(true).c_str()); + LOG(debug, "Update(%s): sending Puts at timestamp %" PRIu64, update_doc_id().c_str(), putTimestamp); + LOG(spam, "Update(%s): Put document is: %s", update_doc_id().c_str(), doc->toString(true).c_str()); if (intermediate._reply.get()) { sendReplyWithResult(sender, intermediate._reply->getResult()); |