diff options
author | Jon Marius Venstad <jonmv@users.noreply.github.com> | 2018-03-13 09:16:37 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-03-13 09:16:37 +0100 |
commit | 3e879a023f4fd92ea21a04299415ecc2efc9b145 (patch) | |
tree | 428283d2116a80b63c6bd735386e7edf9062ce98 /node-admin | |
parent | 4be3552c8c5b87769c55b48e0e1a6e60bae12ffd (diff) | |
parent | 7d3bebd2ee1bbd5e93dc7acc2e2835d5405dd711 (diff) |
Merge pull request #5295 from vespa-engine/jvenstad/motd-and-prompt-in-containers
Jvenstad/motd and prompt in containers
Diffstat (limited to 'node-admin')
11 files changed, 306 insertions, 72 deletions
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/containerdata/ConfigServerContainerData.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/containerdata/ConfigServerContainerData.java index 36da09a72aa..d77e468bc8d 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/containerdata/ConfigServerContainerData.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/containerdata/ConfigServerContainerData.java @@ -1,6 +1,5 @@ package com.yahoo.vespa.hosted.node.admin.containerdata; -import com.yahoo.vespa.hosted.dockerapi.ContainerName; import com.yahoo.vespa.hosted.node.admin.component.Environment; import java.nio.file.Path; @@ -18,9 +17,7 @@ public class ConfigServerContainerData { this.configServerNodeHostName = configServerNodeHostName; } - public void create() { - ContainerData containerData = ContainerData.createCleanContainerData( - environment, ContainerName.fromHostname(configServerNodeHostName)); + public void writeTo(ContainerData containerData) { containerData.addFile(getPath("configserver-config.xml"), createConfigServerConfigXml()); containerData.addFile(getPath("node-repository-config.xml"), createNodeRepoConfigXml()); } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/containerdata/ContainerData.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/containerdata/ContainerData.java index ebfb433de4a..1dc06d52e87 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/containerdata/ContainerData.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/containerdata/ContainerData.java @@ -37,7 +37,7 @@ public class ContainerData { this.destinationPathOnHost = environment.pathInHostFromPathInNode(containerName, ContainerData.containerDataPath); } - public static ContainerData createCleanContainerData(Environment environment, ContainerName containerName) { + public static ContainerData createClean(Environment environment, ContainerName containerName) { ContainerData containerData = new ContainerData(environment, containerName); IOExceptionUtil.uncheck(containerData::cleanup); return containerData; diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/containerdata/MotdContainerData.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/containerdata/MotdContainerData.java new file mode 100644 index 00000000000..24dadd41b5a --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/containerdata/MotdContainerData.java @@ -0,0 +1,71 @@ +package com.yahoo.vespa.hosted.node.admin.containerdata; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; +import com.yahoo.vespa.hosted.node.admin.component.Environment; +import com.yahoo.vespa.hosted.node.admin.task.util.file.Template; +import com.yahoo.vespa.hosted.provision.Node.State; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import java.util.function.BiConsumer; + +/** + * @author jvenstad + */ +public class MotdContainerData { + + private static final Path motdPath = Paths.get("etc/profile.d/motd.sh"); + private static final String templateString = "#!/usr/bin/env bash\n" + + "\n" + + "function motd {\n" + + "\n" + + " local -r uptime=$(uptime | cut -f 3- -d ' ')\n" + + "\n" + + " local -r no_color='\\e[0m'\n" + + " local -r green='\\e[0;32m'\n" + + "## Use red zone name for main prod zones, yellow for other main zones and no colour for cd and dev zones.\n" + + " local -r alert=#if($zone.getSystem() == \"main\")#if($zone.getEnvironment() == \"prod\")'\\e[0;91m'#else'\\e[0;33m'#end#else$green#end\n" + + "\n" + + "\n" + + " echo -e \"\n" + + "${green}Zone : ${alert}$zone.getSystem().toUpperCase() $zone.getEnvironment().toUpperCase() $zone.getRegion().toUpperCase()\n" + + "${green}Node type : ${no_color}$type\n" + + "${green}Node flavor : ${no_color}$flavor\n" + + "${green}Host name : ${no_color}$(hostname)\n" + + "${green}Uptime : ${no_color}$uptime\n" + + "${green}Version : ${no_color}wanted = $wanted.orElse(\"unknown\"); installed = $installed.orElse(\"unknown\")\n" + + "#if($owner.isPresent())\n" + + "${green}Node state : ${no_color}$state\n" + + "${green}Owner : ${no_color}$owner.get().serializedForm()\n" + + "#end\n" + + "\"\n" + + "}\n" + + "\n" + + "# Display motd (gently)\n" + + "[ ! -f ~/.hushlogin ] && motd\n"; + + private final String renderedString; + + public MotdContainerData(ContainerNodeSpec nodeSpec, Environment environment) { + renderedString = Template.of(templateString) + .set("zone", environment) + .set("type", nodeSpec.nodeType) + .set("state", nodeSpec.nodeState) + .set("installed", nodeSpec.vespaVersion) + .set("wanted", nodeSpec.wantedVespaVersion) + .set("owner", nodeSpec.owner.map(id -> ApplicationId.from(id.tenant, id.application, id.instance))) + .set("flavor", nodeSpec.nodeFlavor) + .render(); + } + + public void writeTo(ContainerData containerData) { + writeTo(containerData::addFile); + } + + void writeTo(BiConsumer<Path, String> fileWriter) { + fileWriter.accept(motdPath, renderedString); + } + +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/containerdata/PromptContainerData.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/containerdata/PromptContainerData.java new file mode 100644 index 00000000000..ea9b2312b77 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/containerdata/PromptContainerData.java @@ -0,0 +1,57 @@ +package com.yahoo.vespa.hosted.node.admin.containerdata; + +import com.yahoo.vespa.hosted.node.admin.component.Environment; +import com.yahoo.vespa.hosted.node.admin.task.util.file.Template; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.function.BiConsumer; + +public class PromptContainerData { + + private static final Path promptPath = Paths.get("etc/profile.d/prompt.sh"); + private static final String templateString = "# Make sure we get UTC/GMT all over\n" + + "export TZ=UTC\n" + + "\n" + + "# Skip the rest for non-interactice shells\n" + + "[ -z \"$PS1\" ] && return\n" + + "\n" + + "# Check the window size after each command and, if necessary,\n" + + "# Update the values of LINES and COLUMNS.\n" + + "shopt -s checkwinsize\n" + + "\n" + + "# Colors; see https://wiki.archlinux.org/index.php/Color_Bash_Prompt\n" + + "color_off='\\[\\e[0m\\]' # Text Reset\n" + + "color_bold='\\[\\e[1m\\]' # Bold text\n" + + "\n" + + "env_colour=#if($zone.getSystem() == \"main\")#if($zone.getEnvironment() == \"prod\")'\\e[0;91m'#else'\\e[0;33m'#end#else$green#end\n" + + "\n" + + "\n" + + "PS1=\"${env_colour}$zone.getRegion().toUpperCase()${color_off} [\\u@${color_bold}\\h${color_off}:\\w]\\$ \"\n" + + "\n" + + "# Fix colors\n" + + "if type dircolors > /dev/null 2>&1; then\n" + + " eval $(dircolors -b)\n" + + "fi\n" + + "\n" + + "# Make PS1 available in sub-shells\n" + + "export PS1\n" + + "\n"; + + private final String renderedString; + + public PromptContainerData(Environment environment) { + renderedString = Template.of(templateString) + .set("zone", environment) + .render(); + } + + public void writeTo(ContainerData containerData) { + writeTo(containerData::addFile); + } + + void writeTo(BiConsumer<Path, String> fileWriter) { + fileWriter.accept(promptPath, renderedString); + } + +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java index 37464dcb50b..27e7cbff68e 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java @@ -16,6 +16,9 @@ import com.yahoo.vespa.hosted.dockerapi.metrics.DimensionMetrics; import com.yahoo.vespa.hosted.dockerapi.metrics.Dimensions; import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper; import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; +import com.yahoo.vespa.hosted.node.admin.containerdata.ContainerData; +import com.yahoo.vespa.hosted.node.admin.containerdata.MotdContainerData; +import com.yahoo.vespa.hosted.node.admin.containerdata.PromptContainerData; import com.yahoo.vespa.hosted.node.admin.docker.DockerOperations; import com.yahoo.vespa.hosted.node.admin.maintenance.StorageMaintainer; import com.yahoo.vespa.hosted.node.admin.containerdata.ConfigServerContainerData; @@ -681,9 +684,17 @@ public class NodeAgentImpl implements NodeAgent { } private void createContainerData(ContainerNodeSpec nodeSpec) { + ContainerData containerData = ContainerData.createClean(environment, + ContainerName.fromHostname(nodeSpec.hostname)); + if (nodeSpec.nodeType.equals(NodeType.config.name())) { logger.info("Creating files needed by config server"); - new ConfigServerContainerData(environment, nodeSpec.hostname).create(); + new ConfigServerContainerData(environment, nodeSpec.hostname).writeTo(containerData); } + + logger.info("Creating files for message of the day and the bash prompt"); + new MotdContainerData(nodeSpec, environment).writeTo(containerData); + new PromptContainerData(environment).writeTo(containerData); } + } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/Template.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/Template.java new file mode 100644 index 00000000000..6092cc1f038 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/Template.java @@ -0,0 +1,55 @@ +// 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.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.apache.velocity.app.VelocityEngine; + +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Uses the Velocity engine to render a template, to and from both String and Path objects. + * + * @author hakonhall + * @author jvenstad + */ +public class Template { + + private final VelocityEngine velocityEngine = new VelocityEngine(); + private final VelocityContext velocityContext = new VelocityContext(); + private final String template; + + private Template(String template) { + this.template = template; + + velocityEngine.addProperty(Velocity.RUNTIME_LOG_LOGSYSTEM_CLASS, + "org.apache.velocity.runtime.log.NullLogSystem"); + velocityEngine.init(); + } + + public static Template at(Path templatePath) { + return of(IOExceptionUtil.uncheck(() -> new String(Files.readAllBytes(templatePath)))); + } + + public static Template of(String template) { + return new Template(template); + } + + public Template set(String name, Object value) { + velocityContext.put(name, value); + return this; + } + + public FileWriter getFileWriterTo(Path destinationPath) { + return new FileWriter(destinationPath, this::render); + } + + public String render() { + StringWriter writer = new StringWriter(); + velocityEngine.evaluate(velocityContext, writer, "Template", template); + return writer.toString(); + } + +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/TemplateFile.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/TemplateFile.java deleted file mode 100644 index e9d0770f933..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/TemplateFile.java +++ /dev/null @@ -1,47 +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.util.file; - -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.Velocity; -import org.apache.velocity.app.VelocityEngine; - -import java.io.StringWriter; -import java.nio.file.Path; - -/** - * Make a file based on a Velocity template file. - * - * @author hakonhall - */ -public class TemplateFile { - private final Path templatePath; - private final VelocityEngine velocityEngine; - private final VelocityContext velocityContext = new VelocityContext(); - - public TemplateFile(Path templatePath) { - this.templatePath = templatePath; - velocityEngine = new VelocityEngine(); - velocityEngine.addProperty( - Velocity.RUNTIME_LOG_LOGSYSTEM_CLASS, - "org.apache.velocity.runtime.log.NullLogSystem"); - velocityEngine.addProperty(Velocity.FILE_RESOURCE_LOADER_PATH, templatePath.getParent().toString()); - velocityEngine.init(); - } - - public TemplateFile set(String name, Object value) { - velocityContext.put(name, value); - return this; - } - - public FileWriter getFileWriterTo(Path destinationPath) { - return new FileWriter(destinationPath, this::render); - } - - private String render() { - Template template = velocityEngine.getTemplate(templatePath.getFileName().toString(), "UTF-8"); - StringWriter writer = new StringWriter(); - template.merge(velocityContext, writer); - return writer.toString(); - } -} diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/containerdata/MotdContainerDataTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/containerdata/MotdContainerDataTest.java new file mode 100644 index 00000000000..fe095cd0002 --- /dev/null +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/containerdata/MotdContainerDataTest.java @@ -0,0 +1,61 @@ +package com.yahoo.vespa.hosted.node.admin.containerdata; + +import com.yahoo.vespa.hosted.node.admin.ContainerNodeSpec; +import com.yahoo.vespa.hosted.node.admin.component.Environment; +import com.yahoo.vespa.hosted.node.admin.config.ConfigServerConfig; +import com.yahoo.vespa.hosted.provision.Node; +import org.junit.Test; + +import java.nio.file.Paths; +import java.util.Collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class MotdContainerDataTest { + + @Test + public void writesMotd() { + MotdContainerData motdContainerData = new MotdContainerData(new ContainerNodeSpec.Builder() + .nodeType("tenant") + .owner(new ContainerNodeSpec.Owner("tenant1", + "application1", + "default")) + .nodeState(Node.State.dirty) + .vespaVersion("7.0.0") + .hostname("nope") + .nodeFlavor("D-WAVE") + .allowedToBeDown(false) + .membership(new ContainerNodeSpec.Membership(null, null, null, 0, false)) + .minCpuCores(0) + .minMainMemoryAvailableGb(0) + .minDiskAvailableGb(0) + .fastDisk(false) + .ipAddresses(Collections.emptySet()) + .build(), + new Environment.Builder() + .configServerConfig(new ConfigServerConfig(new ConfigServerConfig.Builder())) + .system("main") + .environment("prod") + .region("aws-us-east-1a") + .defaultFlavor("cherry") + .build()); + + motdContainerData.writeTo((path, content) -> { + assertEquals(path, Paths.get("etc/profile.d/motd.sh")); + + System.out.println(content); + + assertTrue(content.contains("tenant")); + assertTrue(content.contains("D-WAVE")); + assertTrue(content.contains("[0;91m")); + assertTrue(content.contains("MAIN PROD AWS-US-EAST-1A")); + assertTrue(content.contains("tenant1:application1:default")); + assertTrue(content.contains("dirty")); + assertTrue(content.contains("wanted = unknown")); + assertTrue(content.contains("installed = 7.0.0")); + }); + + } + +} diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/containerdata/PromptContainerDataTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/containerdata/PromptContainerDataTest.java new file mode 100644 index 00000000000..4403b5bdad0 --- /dev/null +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/containerdata/PromptContainerDataTest.java @@ -0,0 +1,33 @@ +package com.yahoo.vespa.hosted.node.admin.containerdata; + +import com.yahoo.vespa.hosted.node.admin.component.Environment; +import com.yahoo.vespa.hosted.node.admin.config.ConfigServerConfig; +import org.junit.Test; + +import java.nio.file.Paths; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PromptContainerDataTest { + + @Test + public void writesPrompt() { + PromptContainerData promptContainerData = new PromptContainerData(new Environment.Builder() + .configServerConfig(new ConfigServerConfig(new ConfigServerConfig.Builder())) + .system("main") + .environment("prod") + .region("aws-us-east-1a") + .defaultFlavor("cherry") + .build()); + + promptContainerData.writeTo((path, content) -> { + assertEquals(path, Paths.get("etc/profile.d/prompt.sh")); + + assertTrue(content.contains("[0;91m")); + assertTrue(content.contains("AWS-US-EAST-1A")); + }); + + } + +} diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java index a97a11faab8..9d379babeca 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java @@ -175,6 +175,7 @@ public class NodeAgentImplTest { when(nodeRepository.getContainerNodeSpec(hostName)).thenReturn(Optional.of(nodeSpec)); when(pathResolver.getApplicationStoragePathForNodeAdmin()).thenReturn(Files.createTempDirectory("foo")); + when(pathResolver.getApplicationStoragePathForHost()).thenReturn(Files.createTempDirectory("bar")); when(dockerOperations.pullImageAsyncIfNeeded(eq(dockerImage))).thenReturn(false); when(storageMaintainer.getDiskUsageFor(eq(containerName))).thenReturn(Optional.of(201326592000L)); @@ -230,7 +231,7 @@ public class NodeAgentImplTest { } @Test - public void containerIsRestartedIfFlavorChanged() { + public void containerIsRestartedIfFlavorChanged() throws IOException { final long wantedRestartGeneration = 1; final long currentRestartGeneration = 1; ContainerNodeSpec.Builder specBuilder = nodeSpecBuilder @@ -253,6 +254,7 @@ public class NodeAgentImplTest { .thenReturn(Optional.of(thirdSpec)); when(dockerOperations.pullImageAsyncIfNeeded(any())).thenReturn(true); when(storageMaintainer.getDiskUsageFor(eq(containerName))).thenReturn(Optional.of(201326592000L)); + when(pathResolver.getApplicationStoragePathForHost()).thenReturn(Files.createTempDirectory("bar")); nodeAgent.converge(); nodeAgent.converge(); @@ -472,6 +474,7 @@ public class NodeAgentImplTest { when(nodeRepository.getContainerNodeSpec(eq(hostName))).thenReturn(Optional.of(nodeSpec)); when(pathResolver.getApplicationStoragePathForNodeAdmin()).thenReturn(Files.createTempDirectory("foo")); + when(pathResolver.getApplicationStoragePathForHost()).thenReturn(Files.createTempDirectory("bar")); when(storageMaintainer.getDiskUsageFor(eq(containerName))).thenReturn(Optional.of(201326592000L)); nodeAgent.tick(); @@ -583,6 +586,7 @@ public class NodeAgentImplTest { when(dockerOperations.getContainerStats(eq(containerName))) .thenReturn(Optional.of(stats1)) .thenReturn(Optional.of(stats2)); + when(pathResolver.getApplicationStoragePathForHost()).thenReturn(Files.createTempDirectory("bar")); nodeAgent.converge(); // Run the converge loop once to initialize lastNodeSpec nodeAgent.updateContainerNodeMetrics(); // Update metrics once to init and lastCpuMetric diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/TemplateFileTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/TemplateTest.java index b1d88fdaaee..7aa672e9e80 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/TemplateFileTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/TemplateTest.java @@ -2,40 +2,31 @@ package com.yahoo.vespa.hosted.node.admin.task.util.file; +import com.google.common.jimfs.Jimfs; import com.yahoo.vespa.hosted.node.admin.component.TaskContext; -import org.junit.Rule; +import com.yahoo.vespa.test.file.TestFileSystem; import org.junit.Test; -import org.junit.rules.TemporaryFolder; import java.io.IOException; +import java.nio.file.FileSystem; import java.nio.file.Path; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; -/** - * WARNING: Velocity does not honor an alternative FileSystem like JimFS. - */ -public class TemplateFileTest { - @Rule - public TemporaryFolder folder = new TemporaryFolder(); - - private void writeFile(Path path, String content) { - UnixPath unixPath = new UnixPath(path); - unixPath.createParents(); - unixPath.writeUtf8File(content); - } +public class TemplateTest { @Test public void basic() throws IOException { + FileSystem fileSystem = TestFileSystem.create(); + Path templatePath = fileSystem.getPath("/example.vm"); String templateContent = "a $x, $y b"; - Path templatePath = folder.newFile("example.vm").toPath(); - writeFile(templatePath, templateContent); + new UnixPath(templatePath).writeUtf8File(templateContent); - Path toPath = folder.newFile().toPath(); + Path toPath = fileSystem.getPath("/example"); TaskContext taskContext = mock(TaskContext.class); - boolean converged = new TemplateFile(templatePath) + boolean converged = Template.at(templatePath) .set("x", "foo") .set("y", "bar") .getFileWriterTo(toPath) @@ -46,4 +37,5 @@ public class TemplateFileTest { String actualContent = new UnixPath(toPath).readUtf8File(); assertEquals("a foo, bar b", actualContent); } + }
\ No newline at end of file |