summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHåkon Hallingstad <hakon@oath.com>2018-01-22 15:44:14 +0100
committerHåkon Hallingstad <hakon@oath.com>2018-01-22 15:44:14 +0100
commitfa9b1942bf51a3301398be5fdc8f18bf3d9485ff (patch)
tree0fed27e3b5047bc781096fd1834c9678cdad9b2a
parentb54f86da25e5183fc45d1d5f1620eb6394d7aca0 (diff)
Redefine task and its context
After discussions with bratseth, we'll not organize tasks in a tree structure, and instead have tasks mostly 1:1 with components. Tasks are put in chains, and may have ordering constraints between them that the chain resolves. The FileSystem API has been removed and instead use the raw java NIO FileSystem, and use Jimfs to mock FileSystem in tests. NodeAdminStateUpdater has been made an interface, since the HTTP request handler needs to delegate to it when getting requests. Much of the public HTTP API will get internalized with standalone NodeAdmin.
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/Timer.java8
-rw-r--r--node-admin/pom.xml5
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/AdminComponent.java7
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/IdempotentTask.java21
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/TaskComponent.java27
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/TaskContext.java21
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/io/FileSystemPath.java68
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/DockerAdminComponent.java6
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminConfig.java8
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminMain.java73
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImpl.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdater.java)17
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java7
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminStateUpdater.java17
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/restapi/RestApiHandler.java4
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/MakeDirectoryTask.java49
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/Task.java17
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/WriteFileTask.java80
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileWriter.java61
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/IOExceptionUtil.java34
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/io/FileSystem.java)65
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/package-info.java5
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/AddYumRepo.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/AddYumRepoTask.java)33
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/package-info.java (renamed from node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/package-info.java)2
-rwxr-xr-xnode-admin/src/main/sh/node-admin.sh11
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/DockerTester.java6
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RebootTest.java5
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RunInContainerTest.java9
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/io/FileSystemTest.java81
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImplTest.java (renamed from node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterTest.java)9
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/AddYumRepoTaskTest.java70
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/MakeDirectoryTaskTest.java49
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/TaskTestBase.java26
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/WriteFileTaskTest.java80
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileWriterTest.java51
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/TestFileSystem.java24
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPathTest.java65
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/AddYumRepoTest.java57
-rw-r--r--parent/pom.xml6
-rwxr-xr-xstandalone-container/src/main/sh/standalone-container.sh21
39 files changed, 552 insertions, 653 deletions
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/Timer.java b/jdisc_core/src/main/java/com/yahoo/jdisc/Timer.java
index c94ebc1ab93..f0dc26844e7 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/Timer.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/Timer.java
@@ -4,6 +4,8 @@ package com.yahoo.jdisc;
import com.google.inject.ImplementedBy;
import com.yahoo.jdisc.core.SystemTimer;
+import java.time.Instant;
+
/**
* <p>This class provides access to the current time in milliseconds, as viewed by the {@link Container}. Inject an
* instance of this class into any component that needs to access time, instead of using
@@ -27,4 +29,10 @@ public interface Timer {
*/
long currentTimeMillis();
+ /**
+ * Convenience method for getting an java.util.Instance from currentTimeMillis().
+ */
+ default Instant currentTime() {
+ return Instant.ofEpochMilli(currentTimeMillis());
+ }
}
diff --git a/node-admin/pom.xml b/node-admin/pom.xml
index 983e4d3a832..7b3b787b503 100644
--- a/node-admin/pom.xml
+++ b/node-admin/pom.xml
@@ -115,6 +115,11 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.google.jimfs</groupId>
+ <artifactId>jimfs</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/AdminComponent.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/AdminComponent.java
index 9bed492bd76..9888cca9c7e 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/AdminComponent.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/AdminComponent.java
@@ -1,6 +1,8 @@
// 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.component;
+import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater;
+
/**
* An AdminComponent cannot assume anything about the environment until enable()
* is called: Required YUM packages may not have been installed, services
@@ -14,6 +16,11 @@ public interface AdminComponent {
void enable();
/**
+ * @return NodeAdminStateUpdater used by the REST API
+ */
+ NodeAdminStateUpdater getNodeAdminStateUpdater();
+
+ /**
* Disable component. May be called more than once.
* Must be compatible with component deconstruct().
*/
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/IdempotentTask.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/IdempotentTask.java
new file mode 100644
index 00000000000..4fd2353c918
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/IdempotentTask.java
@@ -0,0 +1,21 @@
+// 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.component;// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+/**
+ * This class is thread unsafe: All method calls MUST be exclusive and serialized.
+ */
+public interface IdempotentTask {
+ String name();
+
+ /**
+ * Execute an administrative task to converge the system towards some ideal state.
+ *
+ * converge() must be idempotent: it may be called any number of times, or
+ * interrupted at any time e.g. by `kill -9`. The caller must ensure there is at
+ * most one invocation of converge() on this instance at any given time.
+ *
+ * @return false if the system was already converged, i.e. converge() was a no-op.
+ * @throws RuntimeException (or a subclass) if the task is unable to converge.
+ */
+ boolean converge(TaskContext context);
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/TaskComponent.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/TaskComponent.java
new file mode 100644
index 00000000000..36cbaef9151
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/TaskComponent.java
@@ -0,0 +1,27 @@
+// 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.component;// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+import com.yahoo.component.ComponentId;
+import com.yahoo.component.chain.ChainedComponent;
+
+public abstract class TaskComponent extends ChainedComponent implements IdempotentTask {
+ protected TaskComponent(ComponentId id) {
+ super(id);
+ }
+
+ public String name() {
+ return getIdString();
+ }
+
+ /**
+ * Execute an administrative task to converge the system towards some ideal state.
+ *
+ * converge() must be idempotent: it may be retried any number of times, or
+ * interrupted at any time e.g. by `kill -9`. However, it must not be called
+ * concurrently with itself or other methods of this instance.
+ *
+ * @return false if the system was already converged, i.e. converge() was a no-op.
+ * @throws RuntimeException (or a subclass) if the task is unable to converge.
+ */
+ public abstract boolean converge(TaskContext context);
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/TaskContext.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/TaskContext.java
new file mode 100644
index 00000000000..84e386fc54b
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/TaskContext.java
@@ -0,0 +1,21 @@
+// 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.component;// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+import java.nio.file.FileSystem;
+import java.util.EnumSet;
+import java.util.logging.Logger;
+
+public interface TaskContext {
+ enum Cloud { YAHOO, AWS }
+ Cloud cloud();
+
+ enum Role { TENANT_DOCKER_HOST, CONFIG_SERVER_DOCKER_HOST }
+ EnumSet<Role> roles();
+ default boolean hasRole(Role role) {
+ return roles().contains(role);
+ }
+
+ FileSystem fileSystem();
+
+ void logSystemModification(Logger logger, String actionDescription);
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/io/FileSystemPath.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/io/FileSystemPath.java
deleted file mode 100644
index bfec341e05c..00000000000
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/io/FileSystemPath.java
+++ /dev/null
@@ -1,68 +0,0 @@
-// 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.io;
-
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.attribute.FileAttribute;
-
-/**
- * Convenience class for calling FileSystem methods on a fixed Path.
- */
-public class FileSystemPath {
- private final FileSystem fileSystem;
- private final Path path;
-
- FileSystemPath(FileSystem fileSystem, Path path) {
- this.fileSystem = fileSystem;
- this.path = path;
- }
-
- public boolean isDirectory() {
- return fileSystem.isDirectory(path);
- }
-
- public boolean isRegularFile() {
- return fileSystem.isRegularFile(path);
- }
-
- public FileSystemPath createDirectory(FileAttribute<?>... attributes) {
- fileSystem.createDirectory(path, attributes);
- return this;
- }
-
- public String readUtf8File() {
- return fileSystem.readUtf8File(path);
- }
-
- public FileSystemPath writeUtf8File(String content, OpenOption... options) {
- fileSystem.writeUtf8File(path, content, options);
- return this;
- }
-
- public String getPermissions() {
- return fileSystem.getPermissions(path);
- }
-
- public FileSystemPath setPermissions(String permissions) {
- fileSystem.setPermissions(path, permissions);
- return this;
- }
-
- public String getOwner() {
- return fileSystem.getOwner(path);
- }
-
- public FileSystemPath setOwner(String owner) {
- fileSystem.setOwner(path, owner);
- return this;
- }
-
- public String getGroup() {
- return fileSystem.getGroup(path);
- }
-
- public FileSystemPath setGroup(String group) {
- fileSystem.setGroup(path, group);
- return this;
- }
-}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/DockerAdminComponent.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/DockerAdminComponent.java
index 980a244484c..67411e35ef4 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/DockerAdminComponent.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/DockerAdminComponent.java
@@ -17,6 +17,7 @@ import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository;
import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepositoryImpl;
import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator;
import com.yahoo.vespa.hosted.node.admin.orchestrator.OrchestratorImpl;
+import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater;
import com.yahoo.vespa.hosted.node.admin.util.ConfigServerHttpRequestExecutor;
import com.yahoo.vespa.hosted.node.admin.util.Environment;
@@ -39,7 +40,7 @@ public class DockerAdminComponent implements AdminComponent {
private ConfigServerHttpRequestExecutor requestExecutor;
- private Optional<NodeAdminStateUpdater> nodeAdminStateUpdater = Optional.empty();
+ private Optional<NodeAdminStateUpdaterImpl> nodeAdminStateUpdater = Optional.empty();
public DockerAdminComponent(NodeAdminConfig config,
Docker docker,
@@ -104,7 +105,7 @@ public class DockerAdminComponent implements AdminComponent {
metricReceiver,
clock);
- nodeAdminStateUpdater = Optional.of(new NodeAdminStateUpdater(
+ nodeAdminStateUpdater = Optional.of(new NodeAdminStateUpdaterImpl(
nodeRepository,
orchestrator,
storageMaintainer,
@@ -129,6 +130,7 @@ public class DockerAdminComponent implements AdminComponent {
// TODO: Also stop docker
}
+ @Override
public NodeAdminStateUpdater getNodeAdminStateUpdater() {
return nodeAdminStateUpdater.get();
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminConfig.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminConfig.java
index 0be31ffdbae..df26bc2dee5 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminConfig.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminConfig.java
@@ -7,18 +7,16 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public class NodeAdminConfig {
private static final ObjectMapper mapper = new ObjectMapper();
/**
- * A list of components to enable instead of the default.
+ * If null, the default admin component will be used.
*/
- @JsonProperty("components")
- public List<String> components = new ArrayList<>();
+ @JsonProperty("main-component")
+ public String mainComponent = null;
public enum Mode {
aws_tenant,
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminMain.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminMain.java
index 6fd6b0c906c..8639e68b347 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminMain.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminMain.java
@@ -9,11 +9,9 @@ import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.hosted.dockerapi.Docker;
import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
import com.yahoo.vespa.hosted.node.admin.component.AdminComponent;
+import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater;
import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -34,9 +32,7 @@ public class NodeAdminMain implements AutoCloseable {
private final MetricReceiverWrapper metricReceiver;
private final ClassLocking classLocking;
- private List<AdminComponent> enabledComponents = new ArrayList<>();
-
- private Optional<DockerAdminComponent> dockerAdmin = Optional.empty();
+ private AdminComponent mainAdminComponent = null;
public NodeAdminMain(ComponentRegistry<AdminComponent> adminRegistry,
Docker docker,
@@ -55,53 +51,46 @@ public class NodeAdminMain implements AutoCloseable {
public void start() {
NodeAdminConfig config = getConfig();
+ mainAdminComponent = selectAdminComponent(config);
+ mainAdminComponent.enable();
+ }
- if (config.components.isEmpty()) {
- dockerAdmin = Optional.of(new DockerAdminComponent(
- config, docker, metricReceiver, classLocking));
- enable(dockerAdmin.get());
- } else {
- logger.log(LogLevel.INFO, () -> {
- String registeredComponentsList = adminRegistry
- .allComponentsById().keySet().stream()
- .map(ComponentId::stringValue)
- .collect(Collectors.joining(", "));
-
- String requestedComponentsList = config.components.stream()
- .collect(Collectors.joining(", "));
-
- return String.format(
- "Components registered = '%s', enabled = '%s'",
- registeredComponentsList,
- requestedComponentsList);
- });
+ private AdminComponent selectAdminComponent(NodeAdminConfig config) {
+ if (config.mainComponent == null) {
+ return new DockerAdminComponent(config, docker, metricReceiver, classLocking);
+ }
- for (String componentSpecificationString : config.components) {
- AdminComponent component =
- adminRegistry.getComponent(componentSpecificationString);
- if (component == null) {
- throw new IllegalArgumentException("There is no component named '" +
- componentSpecificationString + "'");
- }
- enable(component);
- }
+ logger.log(LogLevel.INFO, () -> {
+ String registeredComponentsList = adminRegistry
+ .allComponentsById().keySet().stream()
+ .map(ComponentId::stringValue)
+ .collect(Collectors.joining(", "));
+
+ return String.format(
+ "Components registered = '%s', enabled = '%s'",
+ registeredComponentsList,
+ config.mainComponent);
+ });
+
+ AdminComponent component = adminRegistry.getComponent(config.mainComponent);
+ if (component == null) {
+ throw new IllegalArgumentException("There is no component named '" +
+ config.mainComponent + "'");
}
- }
- private void enable(AdminComponent component) {
- component.enable();
- enabledComponents.add(component);
+ return component;
}
@Override
public void close() {
- int i = enabledComponents.size();
- while (i --> 0) {
- enabledComponents.remove(i).disable();
+ if (mainAdminComponent != null) {
+ mainAdminComponent.disable();
+ mainAdminComponent = null;
}
}
public NodeAdminStateUpdater getNodeAdminStateUpdater() {
- return dockerAdmin.get().getNodeAdminStateUpdater();
+ assert mainAdminComponent != null : "start() hasn't been called yet";
+ return mainAdminComponent.getNodeAdminStateUpdater();
}
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdater.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImpl.java
index 5ec041298fb..e4e66b57186 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdater.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImpl.java
@@ -12,6 +12,7 @@ import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAttributes;
import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository;
import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator;
import com.yahoo.vespa.hosted.node.admin.orchestrator.OrchestratorException;
+import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater;
import com.yahoo.vespa.hosted.node.admin.util.HttpException;
import com.yahoo.vespa.hosted.provision.Node;
@@ -32,16 +33,16 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
-import static com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater.State.RESUMED;
-import static com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater.State.SUSPENDED_NODE_ADMIN;
-import static com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater.State.TRANSITIONING;
+import static com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater.State.RESUMED;
+import static com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater.State.SUSPENDED_NODE_ADMIN;
+import static com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater.State.TRANSITIONING;
/**
* Pulls information from node repository and forwards containers to run to node admin.
*
* @author dybis, stiankri
*/
-public class NodeAdminStateUpdater {
+public class NodeAdminStateUpdaterImpl implements NodeAdminStateUpdater {
static final Duration FREEZE_CONVERGENCE_TIMEOUT = Duration.ofMinutes(5);
private final AtomicBoolean terminated = new AtomicBoolean(false);
@@ -67,7 +68,7 @@ public class NodeAdminStateUpdater {
private Optional<ClassLock> classLock;
private Instant lastTick;
- public NodeAdminStateUpdater(
+ public NodeAdminStateUpdaterImpl(
NodeRepository nodeRepository,
Orchestrator orchestrator,
StorageMaintainer storageMaintainer,
@@ -111,8 +112,7 @@ public class NodeAdminStateUpdater {
return this.getClass().getSimpleName() + "@" + Integer.toString(System.identityHashCode(this));
}
- public enum State { TRANSITIONING, RESUMED, SUSPENDED_NODE_ADMIN, SUSPENDED}
-
+ @Override
public Map<String, Object> getDebugPage() {
Map<String, Object> debug = new LinkedHashMap<>();
synchronized (monitor) {
@@ -142,6 +142,7 @@ public class NodeAdminStateUpdater {
}
}
+ @Override
public boolean setResumeStateAndCheckIfResumed(State wantedState) {
synchronized (monitor) {
if (this.wantedState != wantedState) {
@@ -309,7 +310,7 @@ public class NodeAdminStateUpdater {
classLocking.interrupt();
- // First we need to stop NodeAdminStateUpdater thread to make sure no new NodeAgents are spawned
+ // First we need to stop NodeAdminStateUpdaterImpl thread to make sure no new NodeAgents are spawned
signalWorkToBeDone();
specVerifierScheduler.shutdown();
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java
index 4543e4f62c0..3e42549058a 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java
@@ -9,7 +9,6 @@ import com.yahoo.vespa.hosted.dockerapi.Docker;
import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
import com.yahoo.vespa.hosted.node.admin.component.AdminComponent;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminMain;
-import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater;
public class NodeAdminProvider implements Provider<NodeAdminStateUpdater> {
private final NodeAdminMain nodeAdminMain;
@@ -19,7 +18,11 @@ public class NodeAdminProvider implements Provider<NodeAdminStateUpdater> {
Docker docker,
MetricReceiverWrapper metricReceiver,
ClassLocking classLocking) {
- nodeAdminMain = new NodeAdminMain(adminRegistry, docker, metricReceiver, classLocking);
+ nodeAdminMain = new NodeAdminMain(
+ adminRegistry,
+ docker,
+ metricReceiver,
+ classLocking);
nodeAdminMain.start();
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminStateUpdater.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminStateUpdater.java
new file mode 100644
index 00000000000..952d54911a0
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminStateUpdater.java
@@ -0,0 +1,17 @@
+// 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.provider;// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+import java.util.Map;
+
+public interface NodeAdminStateUpdater {
+ enum State { TRANSITIONING, RESUMED, SUSPENDED_NODE_ADMIN, SUSPENDED}
+
+ /**
+ * Set the wanted state, and return whether the current state equals it.
+ * Typically, this method should be called repeatedly until current state
+ * has converged.
+ */
+ boolean setResumeStateAndCheckIfResumed(State wantedState);
+
+ Map<String, Object> getDebugPage();
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/restapi/RestApiHandler.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/restapi/RestApiHandler.java
index 03217c85329..a8dcde02ca6 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/restapi/RestApiHandler.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/restapi/RestApiHandler.java
@@ -6,10 +6,9 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
-import com.yahoo.container.logging.AccessLog;
import com.yahoo.vespa.hosted.dockerapi.metrics.DimensionMetrics;
import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
-import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater;
+import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater;
import javax.inject.Inject;
import javax.ws.rs.core.MediaType;
@@ -17,7 +16,6 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
-import java.util.concurrent.Executor;
import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
import static com.yahoo.jdisc.http.HttpRequest.Method.PUT;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/MakeDirectoryTask.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/MakeDirectoryTask.java
deleted file mode 100644
index 522abb81248..00000000000
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/MakeDirectoryTask.java
+++ /dev/null
@@ -1,49 +0,0 @@
-// 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.task;
-
-import com.yahoo.vespa.hosted.node.admin.io.FileSystem;
-
-import java.nio.file.Path;
-
-public class MakeDirectoryTask implements Task {
- private final Path path;
- private boolean withParents = false;
-
- public MakeDirectoryTask(Path path) {
- this.path = path;
- }
-
- public MakeDirectoryTask withParents() {
- this.withParents = true;
- return this;
- }
-
- Path getPath() {
- return path;
- }
-
- boolean getWithParents() {
- return withParents;
- }
-
- private boolean makeDirectory(FileSystem fileSystem,
- Path directory,
- boolean withParents) {
- if (fileSystem.isDirectory(directory)) {
- return false;
- }
-
- if (withParents) {
- makeDirectory(fileSystem, directory.getParent(), withParents);
- }
-
- fileSystem.createDirectory(directory);
-
- return true;
- }
-
- @Override
- public boolean execute(TaskContext context) {
- return makeDirectory(context.getFileSystem(), path, withParents);
- }
-}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/Task.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/Task.java
deleted file mode 100644
index 4d40ac02440..00000000000
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/Task.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// 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.task;
-
-import com.yahoo.vespa.hosted.node.admin.io.FileSystem;
-
-public interface Task {
- interface TaskContext {
- FileSystem getFileSystem();
- boolean executeSubtask(Task task);
- }
-
- /**
- * @return Returns false if task was a no-op. Used for informational purposes only.
- * @throws RuntimeException if task could not be completed.
- */
- boolean execute(TaskContext context);
-}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/WriteFileTask.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/WriteFileTask.java
deleted file mode 100644
index 308a7470d24..00000000000
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/WriteFileTask.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// 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.task;
-
-import com.yahoo.vespa.hosted.node.admin.io.FileSystemPath;
-import org.glassfish.jersey.internal.util.Producer;
-
-import java.nio.file.Path;
-import java.util.Optional;
-
-public class WriteFileTask implements Task {
- private final Path path;
- private final Producer<String> contentProducer;
-
- private Optional<String> owner = Optional.empty();
- private Optional<String> group = Optional.empty();
- private Optional<String> permissions = Optional.empty();
-
- public WriteFileTask(Path path, Producer<String> contentProducer) {
- this.path = path;
- this.contentProducer = contentProducer;
- }
-
- public WriteFileTask withOwner(String owner) {
- this.owner = Optional.of(owner);
- return this;
- }
-
- public WriteFileTask withGroup(String group) {
- this.group = Optional.of(group);
- return this;
- }
-
- /**
- * @param permissions of the form "rwxr-x---".
- */
- public WriteFileTask withPermissions(String permissions) {
- this.permissions = Optional.of(permissions);
- return this;
- }
-
- @Override
- public boolean execute(TaskContext context) {
- final FileSystemPath fileSystemPath = context.getFileSystem().withPath(path);
-
- // TODO: Only return false if content, permission, etc would be unchanged.
- if (fileSystemPath.isRegularFile()) {
- return false;
- }
-
- context.executeSubtask(new MakeDirectoryTask(path.getParent()).withParents());
-
- String content = contentProducer.call();
- fileSystemPath.writeUtf8File(content);
- permissions.ifPresent(fileSystemPath::setPermissions);
- owner.ifPresent(fileSystemPath::setOwner);
- group.ifPresent(fileSystemPath::setGroup);
-
- return true;
- }
-
- public Path getPath() {
- return path;
- }
-
- public Producer<String> getContentProducer() {
- return contentProducer;
- }
-
- public Optional<String> getOwner() {
- return owner;
- }
-
- public Optional<String> getGroup() {
- return group;
- }
-
- public Optional<String> getPermissions() {
- return permissions;
- }
-}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileWriter.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileWriter.java
new file mode 100644
index 00000000000..60a7b3482b2
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileWriter.java
@@ -0,0 +1,61 @@
+// 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.task.util.file;
+
+import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
+import org.glassfish.jersey.internal.util.Producer;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+public class FileWriter {
+ private static final Logger logger = Logger.getLogger(FileWriter.class.getName());
+
+ private final Path path;
+ private final Producer<String> contentProducer;
+
+ private Optional<String> owner = Optional.empty();
+ private Optional<String> group = Optional.empty();
+ private Optional<String> permissions = Optional.empty();
+
+ public FileWriter(Path path, Producer<String> contentProducer) {
+ this.path = path;
+ this.contentProducer = contentProducer;
+ }
+
+ public FileWriter withOwner(String owner) {
+ this.owner = Optional.of(owner);
+ return this;
+ }
+
+ public FileWriter withGroup(String group) {
+ this.group = Optional.of(group);
+ return this;
+ }
+
+ public FileWriter withPermissions(String permissions) {
+ this.permissions = Optional.of(permissions);
+ return this;
+ }
+
+ public boolean converge(TaskContext context) {
+ // TODO: Only return false if content, permission, etc would be unchanged.
+ if (Files.isRegularFile(path)) {
+ return false;
+ }
+
+ context.logSystemModification(logger,"Writing file " + path);
+
+ String content = contentProducer.call();
+
+ UnixPath unixPath = new UnixPath(path);
+ unixPath.createParents();
+ unixPath.writeUtf8File(content);
+ permissions.ifPresent(unixPath::setPermissions);
+ owner.ifPresent(unixPath::setOwner);
+ group.ifPresent(unixPath::setGroup);
+
+ return true;
+ }
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/IOExceptionUtil.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/IOExceptionUtil.java
new file mode 100644
index 00000000000..dee5525d42a
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/IOExceptionUtil.java
@@ -0,0 +1,34 @@
+// 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.task.util.file;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+
+public class IOExceptionUtil {
+ public static <T> void uncheck(RunnableThrowingIOException<T> runnable) {
+ try {
+ runnable.run();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ public static <T> T uncheck(SupplierThrowingIOException<T> supplier) {
+ try {
+ return supplier.get();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @FunctionalInterface
+ public interface SupplierThrowingIOException<T> {
+ T get() throws IOException;
+ }
+
+
+ @FunctionalInterface
+ public interface RunnableThrowingIOException<T> {
+ void run() throws IOException;
+ }
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/io/FileSystem.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java
index c5c2df9e38e..606f8cfb06e 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/io/FileSystem.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java
@@ -1,5 +1,5 @@
// 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.io;
+package com.yahoo.vespa.hosted.node.admin.task.util.file;
import java.io.IOException;
import java.io.UncheckedIOException;
@@ -7,7 +7,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
-import java.nio.file.attribute.FileAttribute;
+import java.nio.file.Paths;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
@@ -15,52 +15,48 @@ import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.attribute.UserPrincipal;
import java.nio.file.attribute.UserPrincipalLookupService;
+import java.time.Instant;
import java.util.Set;
-/**
- * File system operations to be mocked in unit tests.
- */
-public class FileSystem {
- public FileSystemPath withPath(Path path) {
- return new FileSystemPath(this, path);
+// @Immutable
+public class UnixPath {
+ private final Path path;
+
+ public UnixPath(Path path) {
+ this.path = path;
}
- public boolean isDirectory(Path path) {
- return path.toFile().isDirectory();
+ public UnixPath(String path) {
+ this(Paths.get(path));
}
- public boolean isRegularFile(Path path) {
- return path.toFile().isFile();
+ public Path toPath() {
+ return path;
}
- public void createDirectory(Path path, FileAttribute<?>... attributes) {
- uncheck(() -> Files.createDirectory(path, attributes));
+ public void createParents() {
+ uncheck(() -> Files.createDirectories(path.getParent()));
}
- public String readUtf8File(Path path) {
+ public String readUtf8File() {
byte[] byteContent = uncheck(() -> Files.readAllBytes(path));
return new String(byteContent, StandardCharsets.UTF_8);
}
- public void writeUtf8File(Path path, String content, OpenOption... options) {
+ public void writeUtf8File(String content, OpenOption... options) {
byte[] contentInUtf8 = content.getBytes(StandardCharsets.UTF_8);
uncheck(() -> Files.write(path, contentInUtf8, options));
}
- private PosixFileAttributes getAttributes(Path path) {
- return uncheck(() ->
- Files.getFileAttributeView(path, PosixFileAttributeView.class).readAttributes());
- }
-
- public String getPermissions(Path path) {
- return PosixFilePermissions.toString(getAttributes(path).permissions());
+ public String getPermissions() {
+ return PosixFilePermissions.toString(getAttributes().permissions());
}
/**
* @param permissions Example: "rwxr-x---" means rwx for owner, rx for group,
* and no permissions for others.
*/
- public void setPermissions(Path path, String permissions) {
+ public void setPermissions(String permissions) {
Set<PosixFilePermission> permissionSet;
try {
permissionSet = PosixFilePermissions.fromString(permissions);
@@ -72,26 +68,35 @@ public class FileSystem {
uncheck(() -> Files.setPosixFilePermissions(path, permissionSet));
}
- public String getOwner(Path path) {
- return getAttributes(path).owner().getName();
+ public String getOwner() {
+ return getAttributes().owner().getName();
}
- public void setOwner(Path path, String owner) {
+ public void setOwner(String owner) {
UserPrincipalLookupService service = path.getFileSystem().getUserPrincipalLookupService();
UserPrincipal principal = uncheck(() -> service.lookupPrincipalByName(owner));
uncheck(() -> Files.setOwner(path, principal));
}
- public String getGroup(Path path) {
- return getAttributes(path).group().getName();
+ public String getGroup() {
+ return getAttributes().group().getName();
}
- public void setGroup(Path path, String group) {
+ public void setGroup(String group) {
UserPrincipalLookupService service = path.getFileSystem().getUserPrincipalLookupService();
GroupPrincipal principal = uncheck(() -> service.lookupPrincipalByGroupName(group));
uncheck(() -> Files.getFileAttributeView(path, PosixFileAttributeView.class).setGroup(principal));
}
+ public Instant getLastModifiedTime() {
+ return uncheck(() -> Files.getLastModifiedTime(path)).toInstant();
+ }
+
+ private PosixFileAttributes getAttributes() {
+ return uncheck(() ->
+ Files.getFileAttributeView(path, PosixFileAttributeView.class).readAttributes());
+ }
+
@FunctionalInterface
private interface SupplierThrowingIOException<T> {
T get() throws IOException;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/package-info.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/package-info.java
new file mode 100644
index 00000000000..076912c073d
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.vespa.hosted.node.admin.task.util.file;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/AddYumRepoTask.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/AddYumRepo.java
index a2ed6a80084..9ca1c0286f9 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/AddYumRepoTask.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/AddYumRepo.java
@@ -1,11 +1,13 @@
// 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.task;
+package com.yahoo.vespa.hosted.node.admin.task.util.yum;
+
+import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
+import com.yahoo.vespa.hosted.node.admin.task.util.file.FileWriter;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.regex.Pattern;
-public class AddYumRepoTask implements Task {
+public class AddYumRepo {
private static final Pattern REPOSITORY_ID_PATTERN = Pattern.compile("^[a-zA-Z_-]+$");
private final String repositoryId; // e.g. "platform_rpms-latest"
@@ -13,10 +15,10 @@ public class AddYumRepoTask implements Task {
private final String baseurl;
private final boolean enabled;
- public AddYumRepoTask(String repositoryId,
- String name,
- String baseurl,
- boolean enabled) {
+ public AddYumRepo(String repositoryId,
+ String name,
+ String baseurl,
+ boolean enabled) {
this.repositoryId = repositoryId;
this.name = name;
this.baseurl = baseurl;
@@ -24,23 +26,18 @@ public class AddYumRepoTask implements Task {
validateRepositoryId(repositoryId);
}
- @Override
- public boolean execute(TaskContext context) {
- Path path = Paths.get("/etc/yum.repos.d",repositoryId + ".repo");
-
- if (context.getFileSystem().isRegularFile(path)) {
- return false;
- }
+ public boolean converge(TaskContext context) {
+ Path path = context.fileSystem().getPath("/etc/yum.repos.d",repositoryId + ".repo");
- WriteFileTask writeFileTask = new WriteFileTask(path, this::getRepoFileContent)
+ FileWriter fileWriter = new FileWriter(path, this::getRepoFileContent)
.withOwner("root")
.withGroup("root")
.withPermissions("rw-r--r--");
- return context.executeSubtask(writeFileTask);
+ return fileWriter.converge(context);
}
- String getRepoFileContent() {
+ private String getRepoFileContent() {
return String.join("\n",
"# This file was generated by node admin",
"# Do NOT modify this file by hand",
@@ -53,7 +50,7 @@ public class AddYumRepoTask implements Task {
) + "\n";
}
- static void validateRepositoryId(String repositoryId) {
+ private static void validateRepositoryId(String repositoryId) {
if (!REPOSITORY_ID_PATTERN.matcher(repositoryId).matches()) {
throw new IllegalArgumentException("Invalid repository ID '" + repositoryId + "'");
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/package-info.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/package-info.java
index 433884139f6..3c5292982d0 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/package-info.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/package-info.java
@@ -1,5 +1,5 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
@ExportPackage
-package com.yahoo.vespa.hosted.node.admin.task;
+package com.yahoo.vespa.hosted.node.admin.task.util.yum;
import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/node-admin/src/main/sh/node-admin.sh b/node-admin/src/main/sh/node-admin.sh
index ff0ea492318..3196ff9fa32 100755
--- a/node-admin/src/main/sh/node-admin.sh
+++ b/node-admin/src/main/sh/node-admin.sh
@@ -67,16 +67,19 @@ EOF
exit 1
}
-if (( $# != 1 )); then
+if (( $# == 0 )); then
Usage
fi
-case "$1" in
+command="$1"
+shift
+
+case "$command" in
start)
- "$VESPA_HOME"/libexec/vespa/standalone-container.sh start -s node-admin -u root
+ "$VESPA_HOME"/libexec/vespa/standalone-container.sh start -s node-admin -u root "$@"
;;
stop)
- "$VESPA_HOME"/libexec/vespa/standalone-container.sh stop -s node-admin -u root
+ "$VESPA_HOME"/libexec/vespa/standalone-container.sh stop -s node-admin -u root "$@"
;;
*) Usage ;;
esac
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 7a314ff0614..3cfc67824ed 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
@@ -11,7 +11,7 @@ import com.yahoo.vespa.hosted.node.admin.docker.DockerOperationsImpl;
import com.yahoo.vespa.hosted.node.admin.maintenance.acl.AclMaintainer;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdmin;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminImpl;
-import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater;
+import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdaterImpl;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgent;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentImpl;
import com.yahoo.vespa.hosted.node.admin.util.Environment;
@@ -40,7 +40,7 @@ public class DockerTester implements AutoCloseable {
final CallOrderVerifier callOrderVerifier = new CallOrderVerifier();
final Docker dockerMock = new DockerMock(callOrderVerifier);
final NodeRepoMock nodeRepositoryMock = new NodeRepoMock(callOrderVerifier);
- final NodeAdminStateUpdater nodeAdminStateUpdater;
+ final NodeAdminStateUpdaterImpl nodeAdminStateUpdater;
final NodeAdmin nodeAdmin;
private final OrchestratorMock orchestratorMock = new OrchestratorMock(callOrderVerifier);
@@ -66,7 +66,7 @@ public class DockerTester implements AutoCloseable {
Function<String, NodeAgent> nodeAgentFactory = (hostName) -> new NodeAgentImpl(hostName, nodeRepositoryMock,
orchestratorMock, dockerOperations, storageMaintainer, aclMaintainer, environment, clock, NODE_AGENT_SCAN_INTERVAL);
nodeAdmin = new NodeAdminImpl(dockerOperations, nodeAgentFactory, storageMaintainer, aclMaintainer, mr, Clock.systemUTC());
- nodeAdminStateUpdater = new NodeAdminStateUpdater(nodeRepositoryMock, orchestratorMock, storageMaintainer,
+ nodeAdminStateUpdater = new NodeAdminStateUpdaterImpl(nodeRepositoryMock, orchestratorMock, storageMaintainer,
nodeAdmin, "basehostname", clock, NODE_ADMIN_CONVERGE_STATE_INTERVAL, new ClassLocking());
nodeAdminStateUpdater.start();
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RebootTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RebootTest.java
index 0b5d67dd13c..2db93ba4203 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RebootTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RebootTest.java
@@ -5,7 +5,8 @@ import com.yahoo.vespa.hosted.dockerapi.DockerImage;
import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec;
import com.yahoo.vespa.hosted.node.admin.docker.DockerOperationsImpl;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdmin;
-import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater;
+import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater;
+import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdaterImpl;
import com.yahoo.vespa.hosted.provision.Node;
import org.junit.Ignore;
import org.junit.Test;
@@ -41,7 +42,7 @@ public class RebootTest {
"createContainerCommand with DockerImage { imageId=dockerImage }, HostName: host1.test.yahoo.com, ContainerName { name=host1 }",
"updateNodeAttributes with HostName: host1.test.yahoo.com, NodeAttributes{restartGeneration=1, rebootGeneration=null, dockerImage=dockerImage, vespaVersion='null'}");
- NodeAdminStateUpdater updater = dockerTester.nodeAdminStateUpdater;
+ NodeAdminStateUpdaterImpl updater = dockerTester.nodeAdminStateUpdater;
assertThat(updater.setResumeStateAndCheckIfResumed(NodeAdminStateUpdater.State.SUSPENDED),
is(Optional.of("Not all node agents are frozen.")));
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RunInContainerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RunInContainerTest.java
index 23d55bd947c..a0e122d99fc 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RunInContainerTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/integrationTests/RunInContainerTest.java
@@ -15,7 +15,8 @@ import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer;
import com.yahoo.vespa.hosted.node.admin.maintenance.acl.AclMaintainer;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdmin;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminImpl;
-import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater;
+import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater;
+import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdaterImpl;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgent;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentImpl;
import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository;
@@ -77,7 +78,7 @@ public class RunInContainerTest {
@Before
public void startContainer() throws Exception {
- // To test the initial NodeAdminStateUpdater convergence towards RESUME, orchestrator should
+ // To test the initial NodeAdminStateUpdaterImpl convergence towards RESUME, orchestrator should
// deny permission to resume for parent host, otherwise it'll converge to RESUME before REST
// handler comes up
doThrow(new RuntimeException()).when(orchestratorMock).resume(parentHostname);
@@ -241,7 +242,7 @@ public class RunInContainerTest {
(hostName) -> new NodeAgentImpl(hostName, nodeRepositoryMock, orchestratorMock, dockerOperationsMock,
storageMaintainer, aclMaintainer, environment, Clock.systemUTC(), NODE_AGENT_SCAN_INTERVAL);
private final NodeAdmin nodeAdmin = new NodeAdminImpl(dockerOperationsMock, nodeAgentFactory, storageMaintainer, aclMaintainer, mr, Clock.systemUTC());
- private final NodeAdminStateUpdater nodeAdminStateUpdater = new NodeAdminStateUpdater(nodeRepositoryMock,
+ private final NodeAdminStateUpdaterImpl nodeAdminStateUpdater = new NodeAdminStateUpdaterImpl(nodeRepositoryMock,
orchestratorMock, storageMaintainer, nodeAdmin, "localhost.test.yahoo.com", Clock.systemUTC(), NODE_ADMIN_CONVERGE_STATE_INTERVAL, new ClassLocking());
public NodeAdminProviderWithMocks() {
@@ -249,7 +250,7 @@ public class RunInContainerTest {
}
@Override
- public NodeAdminStateUpdater get() {
+ public NodeAdminStateUpdaterImpl get() {
return nodeAdminStateUpdater;
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/io/FileSystemTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/io/FileSystemTest.java
deleted file mode 100644
index 6961efc159c..00000000000
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/io/FileSystemTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// 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.io;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-import java.nio.file.Path;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-public class FileSystemTest {
- @Rule
- public final TemporaryFolder folder = new TemporaryFolder();
-
- private Path root;
- private Path path;
-
- private final FileSystem fileSystem = new FileSystem();
-
- @Before
- public void setUp() throws Exception {
- root = folder.getRoot().toPath();
- path = folder.newFile().toPath();
- }
-
- @Test
- public void isDirectory() throws Exception {
- assertTrue(fileSystem.isDirectory(root));
- assertFalse(fileSystem.isDirectory(path));
- }
-
- @Test
- public void isRegularFile() throws Exception {
- assertTrue(fileSystem.isRegularFile(path));
- assertFalse(fileSystem.isRegularFile(root));
- }
-
- @Test
- public void createDirectory() throws Exception {
- Path dir = root.resolve("subdir");
- fileSystem.createDirectory(dir);
- assertTrue(fileSystem.isDirectory(dir));
- }
-
- @Test
- public void utf8FileIO() throws Exception {
- String original = "foo\nbar\n";
- Path path = root.resolve("example.txt");
- fileSystem.writeUtf8File(path, original);
- String fromFile = fileSystem.readUtf8File(path);
- assertEquals(original, fromFile);
- }
-
- @Test
- public void permissions() throws Exception {
- String expectedPermissions = "rwxr-x---";
- fileSystem.setPermissions(path, expectedPermissions);
- assertEquals(expectedPermissions, fileSystem.getPermissions(path));
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void badPermissionsString() {
- fileSystem.setPermissions(path, "abcdefghi");
- }
-
- @Test
- public void owner() throws Exception {
- String owner = fileSystem.getOwner(path);
- fileSystem.setOwner(path, owner);
- }
-
- @Test
- public void group() throws Exception {
- String group = fileSystem.getGroup(path);
- fileSystem.setGroup(path, group);
- }
-} \ No newline at end of file
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImplTest.java
index 1058278a02c..7920d0cad29 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeadmin/NodeAdminStateUpdaterImplTest.java
@@ -7,6 +7,7 @@ import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer;
import com.yahoo.vespa.hosted.node.admin.noderepository.NodeRepository;
import com.yahoo.vespa.hosted.node.admin.orchestrator.Orchestrator;
import com.yahoo.vespa.hosted.node.admin.orchestrator.OrchestratorException;
+import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminStateUpdater;
import com.yahoo.vespa.hosted.provision.Node;
import org.junit.Test;
@@ -32,11 +33,11 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
/**
- * Basic test of NodeAdminStateUpdater
+ * Basic test of NodeAdminStateUpdaterImpl
*
* @author freva
*/
-public class NodeAdminStateUpdaterTest {
+public class NodeAdminStateUpdaterImplTest {
private final NodeRepository nodeRepository = mock(NodeRepository.class);
private final Orchestrator orchestrator = mock(Orchestrator.class);
private final StorageMaintainer storageMaintainer = mock(StorageMaintainer.class);
@@ -45,7 +46,7 @@ public class NodeAdminStateUpdaterTest {
private final ManualClock clock = new ManualClock();
private final Duration convergeStateInterval = Duration.ofSeconds(30);
- private final NodeAdminStateUpdater refresher = spy(new NodeAdminStateUpdater(
+ private final NodeAdminStateUpdaterImpl refresher = spy(new NodeAdminStateUpdaterImpl(
nodeRepository, orchestrator, storageMaintainer, nodeAdmin, parentHostname, clock, convergeStateInterval, null));
@@ -102,7 +103,7 @@ public class NodeAdminStateUpdaterTest {
// The second orchestration failure happens after the freeze convergence timeout,
// and so SHOULD call setFrozen(false)
when(nodeAdmin.setFrozen(eq(true))).thenReturn(true);
- when(nodeAdmin.subsystemFreezeDuration()).thenReturn(NodeAdminStateUpdater.FREEZE_CONVERGENCE_TIMEOUT.plusMinutes(1));
+ when(nodeAdmin.subsystemFreezeDuration()).thenReturn(NodeAdminStateUpdaterImpl.FREEZE_CONVERGENCE_TIMEOUT.plusMinutes(1));
doThrow(new RuntimeException("Cannot allow to suspend because some reason")).doNothing()
.when(orchestrator).suspend(eq(parentHostname));
tickAfter(35);
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/AddYumRepoTaskTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/AddYumRepoTaskTest.java
deleted file mode 100644
index 3e444f08508..00000000000
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/AddYumRepoTaskTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-// 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.task;
-
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class AddYumRepoTaskTest extends TaskTestBase {
- private Path expectedPath;
- private String expectedContent;
- private AddYumRepoTask task;
-
- public void setUp() {
- String repository = "repo-id";
- String name = "name";
- String baseUrl = "base-url";
- boolean enabled = true;
- expectedContent = "# This file was generated by node admin\n" +
- "# Do NOT modify this file by hand\n" +
- "\n" +
- "[repo-id]\n" +
- "name=name\n" +
- "baseurl=base-url\n" +
- "enabled=1\n" +
- "gpgcheck=0\n";
-
- task = new AddYumRepoTask(repository, name, baseUrl, enabled);
- expectedPath = Paths.get("/etc/yum.repos.d/" + repository + ".repo");
- }
-
- @Test
- public void alreadyExistsIsNoOp() {
- when(fileSystemMock.isRegularFile(expectedPath)).thenReturn(true);
- assertFalse(task.execute(contextMock));
- }
-
- @Test
- public void fileContent() {
- assertEquals(expectedContent, task.getRepoFileContent());
- }
-
- @Test
- public void createsFile() {
- when(fileSystemMock.isRegularFile(expectedPath)).thenReturn(false);
- when(contextMock.executeSubtask(any())).thenReturn(true);
- assertTrue(task.execute(contextMock));
-
- // Writing a file with the expected content
- ArgumentCaptor<WriteFileTask> writeFileTaskArgumentCaptor =
- ArgumentCaptor.forClass(WriteFileTask.class);
- verify(contextMock, times(1))
- .executeSubtask(writeFileTaskArgumentCaptor.capture());
- WriteFileTask writeFileTask = writeFileTaskArgumentCaptor.getValue();
- assertEquals(expectedPath, writeFileTask.getPath());
- assertEquals(expectedContent, writeFileTask.getContentProducer().call());
- assertEquals("rw-r--r--", writeFileTask.getPermissions().get());
- assertEquals("root", writeFileTask.getOwner().get());
- assertEquals("root", writeFileTask.getGroup().get());
- }
-} \ No newline at end of file
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/MakeDirectoryTaskTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/MakeDirectoryTaskTest.java
deleted file mode 100644
index cb4d8ab677c..00000000000
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/MakeDirectoryTaskTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-// 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.task;
-
-import com.yahoo.vespa.hosted.node.admin.io.FileSystem;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.InOrder;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class MakeDirectoryTaskTest {
- private final FileSystem fileSystem = mock(FileSystem.class);
- private final Task.TaskContext context = mock(Task.TaskContext.class);
- private final Path root = Paths.get("/");
- private final Path fooDir = root.resolve("foo");
- private final Path barDir = fooDir.resolve("bar");
- private final MakeDirectoryTask task = new MakeDirectoryTask(barDir);
-
- @Before
- public void setUp() {
- when(context.getFileSystem()).thenReturn(fileSystem);
- }
-
- @Test
- public void directoryExists() {
- when(fileSystem.isDirectory(barDir)).thenReturn(true);
- assertFalse(task.execute(context));
- }
-
- @Test
- public void withParents() {
- when(fileSystem.isDirectory(barDir)).thenReturn(false);
- when(fileSystem.isDirectory(fooDir)).thenReturn(false);
- when(fileSystem.isDirectory(root)).thenReturn(true);
- assertTrue(task.withParents().execute(context));
-
- InOrder inOrder = inOrder(fileSystem);
- inOrder.verify(fileSystem).createDirectory(fooDir);
- inOrder.verify(fileSystem).createDirectory(barDir);
- inOrder.verifyNoMoreInteractions();
- }
-} \ No newline at end of file
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/TaskTestBase.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/TaskTestBase.java
deleted file mode 100644
index ac24ac6524e..00000000000
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/TaskTestBase.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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.task;
-
-import com.yahoo.vespa.hosted.node.admin.io.FileSystem;
-import org.junit.Before;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-abstract class TaskTestBase {
- protected final FileSystem fileSystemMock = mock(FileSystem.class);
- protected final Task.TaskContext contextMock = mock(Task.TaskContext.class);
-
- @Before
- public void baseSetup() {
- when(contextMock.getFileSystem()).thenReturn(fileSystemMock);
- when(fileSystemMock.withPath(any())).thenCallRealMethod();
- setUp();
- }
-
- /**
- * Override this to set up before each test.
- */
- void setUp() {}
-}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/WriteFileTaskTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/WriteFileTaskTest.java
deleted file mode 100644
index 9c998cc6fdb..00000000000
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/WriteFileTaskTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// 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.task;
-
-import org.glassfish.jersey.internal.util.Producer;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-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 WriteFileTaskTest extends TaskTestBase {
- private final String content = "line1\nline2\n";
-
- @Test
- public void testWrite() {
- Path parentDirectory = Paths.get("/foo");
- Path path = parentDirectory.resolve("bar");
-
- @SuppressWarnings("unchecked")
- Producer<String> contentProducer = (Producer<String>) mock(Producer.class);
- when(contentProducer.call()).thenReturn(content);
-
- final String permissions = "rwxr-x---";
- final String owner = "owner";
- final String group = "group";
-
- WriteFileTask task = new WriteFileTask(path, contentProducer)
- .withPermissions(permissions)
- .withOwner(owner)
- .withGroup(group);
-
- when(fileSystemMock.isRegularFile(path)).thenReturn(false);
- when(contextMock.executeSubtask(any(MakeDirectoryTask.class))).thenReturn(false);
-
- assertTrue(task.execute(contextMock));
-
- verify(contentProducer, times(1)).call();
- verify(fileSystemMock).writeUtf8File(path, content);
- verify(fileSystemMock).setPermissions(path, permissions);
- verify(fileSystemMock).setOwner(path, owner);
- verify(fileSystemMock).setGroup(path, group);
-
- // Writing a file with the expected content
- ArgumentCaptor<MakeDirectoryTask> makeDirectoryTaskCaptor =
- ArgumentCaptor.forClass(MakeDirectoryTask.class);
- verify(contextMock, times(1))
- .executeSubtask(makeDirectoryTaskCaptor.capture());
-
- MakeDirectoryTask makeDirectoryTask = makeDirectoryTaskCaptor.getValue();
- assertEquals(parentDirectory, makeDirectoryTask.getPath());
- assertTrue(makeDirectoryTask.getWithParents());
- }
-
- @Test
- public void fileAlreadyExists() {
- Path path = Paths.get("foo");
-
- final String permissions = "rwxr-x---";
- final String owner = "owner";
- final String group = "group";
-
- WriteFileTask task = new WriteFileTask(path, () -> content)
- .withPermissions(permissions)
- .withOwner(owner)
- .withGroup(group);
-
- when(fileSystemMock.isRegularFile(path)).thenReturn(true);
-
- assertFalse(task.execute(contextMock));
- }
-} \ No newline at end of file
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileWriterTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileWriterTest.java
new file mode 100644
index 00000000000..ca4eabf855b
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileWriterTest.java
@@ -0,0 +1,51 @@
+// 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.task.util.file;
+
+import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
+import org.junit.Test;
+
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.time.Instant;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class FileWriterTest {
+ private final FileSystem fileSystem = TestFileSystem.create();
+
+ @Test
+ public void testWrite() {
+ final String content = "content";
+ final String permissions = "rwxr-xr-x";
+ final String owner = "owner";
+ final String group = "group";
+
+ Path path = fileSystem.getPath("/opt/vespa/tmp/file.txt");
+ FileWriter writer = new FileWriter(path, () -> content)
+ .withPermissions(permissions)
+ .withOwner(owner)
+ .withGroup(group);
+ TaskContext context = mock(TaskContext.class);
+ assertTrue(writer.converge(context));
+ verify(context, times(1)).logSystemModification(any(), eq("Writing file " + path));
+
+ UnixPath unixPath = new UnixPath(path);
+ assertEquals(content, unixPath.readUtf8File());
+ assertEquals(permissions, unixPath.getPermissions());
+ assertEquals(owner, unixPath.getOwner());
+ assertEquals(group, unixPath.getGroup());
+ Instant fileTime = unixPath.getLastModifiedTime();
+
+ // Second time is a no-op.
+ assertFalse(writer.converge(context));
+ assertEquals(fileTime, unixPath.getLastModifiedTime());
+ }
+}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/TestFileSystem.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/TestFileSystem.java
new file mode 100644
index 00000000000..465cb671a97
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/TestFileSystem.java
@@ -0,0 +1,24 @@
+// 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.task.util.file;
+
+import com.google.common.jimfs.Configuration;
+import com.google.common.jimfs.Feature;
+import com.google.common.jimfs.Jimfs;
+import com.google.common.jimfs.PathType;
+
+import java.nio.file.FileSystem;
+
+public class TestFileSystem {
+ public static FileSystem create() {
+ // This configuration is based on Configuration.unix(), except:
+ // - Use "posix" attribute view which is necessary for permissions, owner, and group.
+ Configuration configuration = Configuration.builder(PathType.unix())
+ .setRoots("/")
+ .setWorkingDirectory("/work")
+ .setAttributeViews("posix")
+ .setSupportedFeatures(Feature.LINKS, Feature.SYMBOLIC_LINKS, Feature.SECURE_DIRECTORY_STREAM, Feature.FILE_CHANNEL)
+ .build();
+ return Jimfs.newFileSystem(configuration);
+ }
+}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPathTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPathTest.java
new file mode 100644
index 00000000000..821c6397ee7
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPathTest.java
@@ -0,0 +1,65 @@
+// 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.task.util.file;
+
+import org.junit.Test;
+
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class UnixPathTest {
+ final FileSystem fileSystem = TestFileSystem.create();
+
+ @Test
+ public void createParents() throws Exception {
+ Path parentDirectory = fileSystem.getPath("/a/b/c");
+ Path filePath = parentDirectory.resolve("bar");
+ UnixPath path = new UnixPath(filePath);
+
+ assertFalse(Files.exists(fileSystem.getPath("/a")));
+ path.createParents();
+ assertTrue(Files.exists(parentDirectory));
+ }
+
+ @Test
+ public void utf8File() throws Exception {
+ String original = "foo\nbar\n";
+ UnixPath path = new UnixPath(fileSystem.getPath("example.txt"));
+ path.writeUtf8File(original);
+ String fromFile = path.readUtf8File();
+ assertEquals(original, fromFile);
+ }
+
+ @Test
+ public void permissions() throws Exception {
+ String expectedPermissions = "rwxr-x---";
+ UnixPath path = new UnixPath(fileSystem.getPath("file.txt"));
+ path.writeUtf8File("foo");
+ path.setPermissions(expectedPermissions);
+ assertEquals(expectedPermissions, path.getPermissions());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void badPermissionsString() {
+ new UnixPath(fileSystem.getPath("file.txt")).setPermissions("abcdefghi");
+ }
+
+ @Test
+ public void owner() throws Exception {
+ FileSystem fs = TestFileSystem.create();
+ Path path = fs.getPath("file.txt");
+ UnixPath unixPath = new UnixPath(path);
+ unixPath.writeUtf8File("foo");
+
+ unixPath.setOwner("owner");
+ assertEquals("owner", unixPath.getOwner());
+
+ unixPath.setGroup("group");
+ assertEquals("group", unixPath.getGroup());
+ }
+}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/AddYumRepoTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/AddYumRepoTest.java
new file mode 100644
index 00000000000..7b6ab91345b
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/AddYumRepoTest.java
@@ -0,0 +1,57 @@
+// 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.task.util.yum;
+
+import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
+import com.yahoo.vespa.hosted.node.admin.task.util.file.TestFileSystem;
+import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath;
+import org.junit.Test;
+
+import java.nio.file.FileSystem;
+import java.time.Instant;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class AddYumRepoTest {
+ @Test
+ public void converge() throws Exception {
+ String repositoryId = "repoid";
+ String name = "name";
+ String baseurl = "http://foo.com/bar";
+ boolean enabled = true;
+
+ AddYumRepo addYumRepo = new AddYumRepo(
+ repositoryId,
+ name,
+ baseurl,
+ enabled);
+
+ TaskContext context = mock(TaskContext.class);
+
+ FileSystem fileSystem = TestFileSystem.create();
+ when(context.fileSystem()).thenReturn(fileSystem);
+
+ assertTrue(addYumRepo.converge(context));
+
+ UnixPath unixPath = new UnixPath(fileSystem.getPath("/etc/yum.repos.d/" + repositoryId + ".repo"));
+ String content = unixPath.readUtf8File();
+ assertEquals("# This file was generated by node admin\n" +
+ "# Do NOT modify this file by hand\n" +
+ "\n" +
+ "[repoid]\n" +
+ "name=name\n" +
+ "baseurl=http://foo.com/bar\n" +
+ "enabled=1\n" +
+ "gpgcheck=0\n", content);
+ Instant lastModifiedTime = unixPath.getLastModifiedTime();
+
+ // Second time is a no-op
+ assertFalse(addYumRepo.converge(context));
+ assertEquals(lastModifiedTime, unixPath.getLastModifiedTime());
+ }
+
+} \ No newline at end of file
diff --git a/parent/pom.xml b/parent/pom.xml
index de02f5085ea..46b8c90baef 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -607,6 +607,12 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>com.google.jimfs</groupId>
+ <artifactId>jimfs</artifactId>
+ <version>1.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
<version>4.3.0</version>
diff --git a/standalone-container/src/main/sh/standalone-container.sh b/standalone-container/src/main/sh/standalone-container.sh
index dd0693f6f85..02c71beaac5 100755
--- a/standalone-container/src/main/sh/standalone-container.sh
+++ b/standalone-container/src/main/sh/standalone-container.sh
@@ -67,6 +67,7 @@ Manage Vespa standalone jdisc container service.
Options:
-u USER Run as USER. Overrides any VESPA_USER environment variable.
-s SERVICE The service name.
+ -- ARGS... Pass the rest of the arguments (ARGS) to the Java invocation
EOF
exit 1
@@ -89,16 +90,19 @@ FixDataDirectory() {
StartCommand() {
local service="$1"
shift
-
- if (( $# > 0 )); then
- Fail "Too many arguments"
- fi
+ local -a jvm_arguments=("$@")
local service_regex='^[0-9a-zA-Z_-]+$'
if ! [[ "$service" =~ $service_regex ]]; then
Fail "Service must match regex '$service_regex'"
fi
+ local pidfile="$VESPA_HOME/var/run/$service.pid"
+ if test -r "$pidfile"; then
+ echo "$service is already running as PID $(< "$pidfile") according to $pidfile"
+ return
+ fi
+
# common setup
export VESPA_SERVICE_NAME="$service"
@@ -135,6 +139,7 @@ StartCommand() {
FixDataDirectory "$bundlecachedir"
java \
+ "${jvm_arguments[@]}" \
-Xms128m -Xmx2048m \
-XX:+PreserveFramePointer \
-XX:+HeapDumpOnOutOfMemoryError \
@@ -267,6 +272,7 @@ Main() {
local service="standalone/container"
local user="$VESPA_USER"
+ local -a jvm_arguments=()
while (( $# > 0 )); do
case "$1" in
@@ -279,6 +285,11 @@ Main() {
user="$2"
shift 2
;;
+ --)
+ shift
+ jvm_arguments=("$@")
+ break
+ ;;
*) break ;;
esac
done
@@ -300,7 +311,7 @@ Main() {
case "$command" in
help) Usage ;;
- start) StartCommand "$service" "$@" ;;
+ start) StartCommand "$service" "${jvm_arguments[@]}" ;;
stop) StopCommand "$user" "$service" "$@" ;;
*) Fail "Unknown command '$command'" ;;
esac