summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHÃ¥kon Hallingstad <hakon@oath.com>2018-01-23 10:08:26 +0100
committerGitHub <noreply@github.com>2018-01-23 10:08:26 +0100
commit41f9201a847e9b7aeb9641950db220dba0ab38bf (patch)
treedf8c064cfe520034207333c222e67b64e3509029
parent446c42d02f86780cc0626e29eddab56be33d50e3 (diff)
Revert "Revert "Redefine task and its context""
-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.java17
-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.java77
-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.java3
-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, 540 insertions, 655 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..b6b64dbf5dd
--- /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;
+
+/**
+ * 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..c54f9ee00c8
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/TaskComponent.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.component;
+
+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();
+ }
+
+ 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..9def627e87f
--- /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;
+
+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 8ba93e769e5..db0313583db 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
@@ -18,6 +18,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;
@@ -41,7 +42,7 @@ public class DockerAdminComponent implements AdminComponent {
private ConfigServerHttpRequestExecutor requestExecutor;
- private Optional<NodeAdminStateUpdater> nodeAdminStateUpdater = Optional.empty();
+ private Optional<NodeAdminStateUpdaterImpl> nodeAdminStateUpdater = Optional.empty();
public DockerAdminComponent(ConfigServerConfig configServerConfig,
NodeAdminConfig config,
@@ -108,7 +109,7 @@ public class DockerAdminComponent implements AdminComponent {
metricReceiver,
clock);
- nodeAdminStateUpdater = Optional.of(new NodeAdminStateUpdater(
+ nodeAdminStateUpdater = Optional.of(new NodeAdminStateUpdaterImpl(
nodeRepository,
orchestrator,
storageMaintainer,
@@ -133,6 +134,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 4ee84d12b1d..3e9cb239890 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
@@ -10,11 +10,9 @@ import com.yahoo.vespa.hosted.dockerapi.Docker;
import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
import com.yahoo.vespa.hosted.node.admin.ConfigServerConfig;
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;
@@ -24,8 +22,8 @@ import java.util.stream.Collectors;
* - It will "start" (only) the necessary components.
* - Other components MUST NOT try to start (typically in constructor) since the features
* they provide is NOT WANTED and possibly destructive, and/or the environment may be
- * incompatible. For instance, trying to contact the Docker daemon too early will
- * be fatal: the node admin may not have installed and started the docker daemon.
+ * incompatible. For instance, trying to contact the Docker daemon too early will be
+ * fatal: the node admin may not have installed and started the docker daemon.
*/
public class NodeAdminMain implements AutoCloseable {
private static final Logger logger = Logger.getLogger(NodeAdminMain.class.getName());
@@ -36,9 +34,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,
ConfigServerConfig configServerConfig,
@@ -59,53 +55,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(
- configServerConfig, 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(configServerConfig, 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 9a667c29fb3..a5146fcae09 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
@@ -7,10 +7,9 @@ import com.yahoo.concurrent.classlock.ClassLocking;
import com.yahoo.container.di.componentgraph.Provider;
import com.yahoo.vespa.hosted.dockerapi.Docker;
import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
+import com.yahoo.vespa.hosted.node.admin.ConfigServerConfig;
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;
-import com.yahoo.vespa.hosted.node.admin.ConfigServerConfig;
public class NodeAdminProvider implements Provider<NodeAdminStateUpdater> {
private final NodeAdminMain nodeAdminMain;
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..755e1301c12
--- /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;
+
+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