diff options
author | Håkon Hallingstad <hakon@oath.com> | 2018-11-23 17:30:13 +0100 |
---|---|---|
committer | Håkon Hallingstad <hakon@oath.com> | 2018-11-23 17:30:13 +0100 |
commit | cadcac9a8c0501f86372eb05d107d7b089643d0a (patch) | |
tree | 9593cc78763cba8e790e49c21f28a19182002765 /node-admin | |
parent | f67aa7bfa1553d8cc19ce4eef96f42ff8c31a320 (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')
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 |