summaryrefslogtreecommitdiffstats
path: root/node-admin
diff options
context:
space:
mode:
authorHåkon Hallingstad <hakon@oath.com>2018-11-23 17:30:13 +0100
committerHåkon Hallingstad <hakon@oath.com>2018-11-23 17:30:13 +0100
commitcadcac9a8c0501f86372eb05d107d7b089643d0a (patch)
tree9593cc78763cba8e790e49c21f28a19182002765 /node-admin
parentf67aa7bfa1553d8cc19ce4eef96f42ff8c31a320 (diff)
Add flags module
FileFlagSource reads flags from files in /etc/vespa/flags and is a component that can be injected in host admin, config server, etc. A flag named foo corresponds to filename foo. In general a FlagSource manages: - Feature flags: A feature is either set (true/enabled) or otherwise false. Touching a file foo means the feature flag foo is set (true). - Value flags: Either a String or empty if not set. The String corresponds to the file content. The plan is to make the config server another source of flags. A unified FlagSource can merge the two sources with some priority and used in e.g. parts of node-admin. In other parts one would only have access to the file source. Defines various flag facades: - FeatureFlag: Used to test whether a feature has been enabled or not. - IntFlag - JacksonFlag: Deserializes JSON to Jackson class, or return default if unset. - LongFlag - OptionalJacksonFlag: Deserializes JSON to Jackson class, or empty if unset. - OptionalStringFlag - StringFlag This is part of removing some of the last Chef recipes. Some minor tweaks have been necessary as part of this and are included in this PR (test whether a systemd service exists, task-friendly file deletion, allow capitalized letters in YUM package name).
Diffstat (limited to 'node-admin')
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileDeleter.java33
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java19
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLine.java9
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtl.java17
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java2
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileDeleterTest.java27
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtlTest.java28
-rw-r--r--node-admin/vespa-node-admin.spec3
8 files changed, 126 insertions, 12 deletions
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileDeleter.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileDeleter.java
new file mode 100644
index 00000000000..efb56be56c5
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileDeleter.java
@@ -0,0 +1,33 @@
+package com.yahoo.vespa.hosted.node.admin.task.util.file;
+
+import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.logging.Logger;
+
+import static com.yahoo.yolean.Exceptions.uncheck;
+
+/**
+ * Deletes a file or empty directory.
+ *
+ * @author hakonhall
+ */
+public class FileDeleter {
+ private static final Logger logger = Logger.getLogger(FileDeleter.class.getName());
+
+ private final Path path;
+
+ public FileDeleter(Path path) {
+ this.path = path;
+ }
+
+ public boolean converge(TaskContext context) {
+ boolean deleted = uncheck(() -> Files.deleteIfExists(path));
+ if (deleted) {
+ context.recordSystemModification(logger, "Deleted file or directory " + path);
+ }
+
+ return deleted;
+ }
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java
index 4bd3aad3f52..1b927cfc682 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java
@@ -49,16 +49,6 @@ public class UnixPath {
return path;
}
- public boolean createParents() {
- Path parent = path.getParent();
- if (Files.isDirectory(parent)) {
- return false;
- }
-
- uncheck(() -> Files.createDirectories(parent));
- return true;
- }
-
public String readUtf8File() {
return new String(readBytes(), StandardCharsets.UTF_8);
}
@@ -141,6 +131,15 @@ public class UnixPath {
return this;
}
+ public UnixPath createParents() {
+ Path parent = path.getParent();
+ if (!Files.isDirectory(parent)) {
+ uncheck(() -> Files.createDirectories(parent));
+ }
+
+ return this;
+ }
+
public UnixPath createDirectory(String permissions) {
Set<PosixFilePermission> set = getPosixFilePermissionsFromString(permissions);
FileAttribute<Set<PosixFilePermission>> attribute = PosixFilePermissions.asFileAttribute(set);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLine.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLine.java
index 44b7da9367b..940b3255766 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLine.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLine.java
@@ -180,6 +180,15 @@ public class CommandLine {
}
/**
+ * By default, a non-zero exit code causes the command execution to fail. This method
+ * will override that predicate.
+ */
+ public CommandLine setSuccessfulExitCodePredicate(Predicate<Integer> successPredicate) {
+ successfulExitCodePredicate = successPredicate;
+ return this;
+ }
+
+ /**
* By default, the output of the command is parsed as UTF-8. This method will set a
* different encoding.
*/
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtl.java
index b61ebb610af..77510f7b6ef 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtl.java
@@ -20,6 +20,9 @@ public class SystemCtl {
// Valid systemd property names from looking at a couple of services.
private static final Pattern PROPERTY_NAME_PATTERN = Pattern.compile("^[a-zA-Z]+$");
+ // Last line of `systemctl list-unit-files <unit>` prints '0 unit files listed.'
+ private static final Pattern UNIT_FILES_LISTED_PATTERN = Pattern.compile("([0-9]+) unit files listed\\.");
+
// Patterns matching properties output by the 'systemctl show' command.
private static final Pattern UNIT_FILE_STATE_PROPERTY_PATTERN = createPropertyPattern("UnitFileState");
private static final Pattern ACTIVE_STATE_PROPERTY_PATTERN = createPropertyPattern("ActiveState");
@@ -53,6 +56,20 @@ public class SystemCtl {
public SystemCtlStop stop(String unit) { return new SystemCtlStop(unit); }
public SystemCtlRestart restart(String unit) { return new SystemCtlRestart(unit); }
+ public boolean serviceExists(TaskContext context, String unit) {
+ return terminal.newCommandLine(context)
+ .add("systemctl").add("list-unit-files").add(unit + ".service").executeSilently()
+ .mapOutput(output -> {
+ // Last line of the form: "1 unit files listed."
+ Matcher matcher = UNIT_FILES_LISTED_PATTERN.matcher(output);
+ if (!matcher.find()) {
+ throw new IllegalArgumentException();
+ }
+
+ return !matcher.group(1).equals("0");
+ });
+ }
+
public class SystemCtlEnable extends SystemCtlCommand {
private SystemCtlEnable(String unit) {
super("enable", unit);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java
index a3a154e2175..fb85815c70f 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java
@@ -43,7 +43,7 @@ public class YumPackageName {
private static final Pattern NAME_VER_REL_PATTERN = Pattern.compile("^((.+)-)?" +
"([a-z0-9._]*[0-9][a-z0-9._]*)-" + // ver contains at least one digit
"([a-z0-9._]*[0-9][a-z0-9._]*)$"); // rel contains at least one digit
- private static final Pattern NAME_PATTERN = Pattern.compile("^[a-z0-9._-]+$");
+ private static final Pattern NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9._-]+$");
private final Optional<String> epoch;
private final String name;
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileDeleterTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileDeleterTest.java
new file mode 100644
index 00000000000..3a60061344f
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/FileDeleterTest.java
@@ -0,0 +1,27 @@
+package com.yahoo.vespa.hosted.node.admin.task.util.file;
+
+import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
+import com.yahoo.vespa.test.file.TestFileSystem;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+public class FileDeleterTest {
+ private final FileSystem fileSystem = TestFileSystem.create();
+ private final UnixPath path = new UnixPath(fileSystem.getPath("/tmp/foo"));
+ private final FileDeleter deleter = new FileDeleter(path.toPath());
+ private final TaskContext context = mock(TaskContext.class);
+
+ @Test
+ public void deleteExisting() throws IOException {
+ assertFalse(deleter.converge(context));
+ path.createParents().writeUtf8File("bar");
+ assertTrue(deleter.converge(context));
+ assertFalse(deleter.converge(context));
+ }
+} \ No newline at end of file
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtlTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtlTest.java
index 315138e897f..fc794c3ab68 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtlTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtlTest.java
@@ -6,7 +6,9 @@ import com.yahoo.vespa.hosted.node.admin.task.util.process.ChildProcessFailureEx
import com.yahoo.vespa.hosted.node.admin.task.util.process.TestTerminal;
import org.junit.Test;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
@@ -133,4 +135,30 @@ public class SystemCtlTest {
terminal.expectCommand("systemctl restart docker 2>&1", 0, "");
assertTrue(new SystemCtl(terminal).restart("docker").converge(taskContext));
}
+
+ @Test
+ public void testUnitExists() {
+ SystemCtl systemCtl = new SystemCtl(terminal);
+
+ terminal.expectCommand("systemctl list-unit-files foo.service 2>&1", 0,
+ "UNIT FILE STATE\n" +
+ "\n" +
+ "0 unit files listed.\n");
+ assertFalse(systemCtl.serviceExists(taskContext, "foo"));
+
+ terminal.expectCommand("systemctl list-unit-files foo.service 2>&1", 0,
+ "UNIT FILE STATE \n" +
+ "foo.service enabled\n" +
+ "\n" +
+ "1 unit files listed.\n");
+ assertTrue(systemCtl.serviceExists(taskContext, "foo"));
+
+ terminal.expectCommand("systemctl list-unit-files foo.service 2>&1", 0, "garbage");
+ try {
+ systemCtl.serviceExists(taskContext, "foo");
+ fail();
+ } catch (Exception e) {
+ assertThat(e.getMessage(), containsString("garbage"));
+ }
+ }
} \ No newline at end of file
diff --git a/node-admin/vespa-node-admin.spec b/node-admin/vespa-node-admin.spec
index 82e92fae52c..ff426e8bfa5 100644
--- a/node-admin/vespa-node-admin.spec
+++ b/node-admin/vespa-node-admin.spec
@@ -33,8 +33,9 @@ mkdir -p "$app_dir"/components
cp node-admin/src/main/application/services.xml "$app_dir"
declare -a jar_components=(
- node-admin/target/node-admin-jar-with-dependencies.jar
docker-api/target/docker-api-jar-with-dependencies.jar
+ flags/target/flags-jar-with-dependencies.jar
+ node-admin/target/node-admin-jar-with-dependencies.jar
)
for path in "${jar_components[@]}"; do
cp "$path" "$app_dir"/components