Repository: brooklyn-server Updated Branches: refs/heads/master cedbd922b -> b771d300a
Adds SetLimitsCustomizer for centos/rhel Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/2ffd8281 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/2ffd8281 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/2ffd8281 Branch: refs/heads/master Commit: 2ffd8281c05542293848d0ec1dcd4cf7472f53c1 Parents: 770ca34 Author: Aled Sage <aled.s...@gmail.com> Authored: Mon Nov 13 16:12:29 2017 +0000 Committer: Aled Sage <aled.s...@gmail.com> Committed: Mon Dec 11 14:48:33 2017 +0000 ---------------------------------------------------------------------- .../camp/brooklyn/AbstractYamlRebindTest.java | 22 ++- .../SetLimitsCustomizerIntegrationTest.java | 71 ++++++++ .../camp/brooklyn/SetLimitsCustomizerTest.java | 168 +++++++++++++++++++ .../util/core/internal/ssh/ExecCmdAsserts.java | 9 + .../entity/machine/SetLimitsCustomizer.java | 144 ++++++++++++++++ .../entity/machine/SetLimitsCustomizerTest.java | 25 +++ 6 files changed, 433 insertions(+), 6 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2ffd8281/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java index 7653d31..e79f0a3 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java @@ -43,7 +43,6 @@ import org.apache.brooklyn.core.mgmt.rebind.RebindOptions; import org.apache.brooklyn.core.mgmt.rebind.RebindTestFixture; import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.stream.Streams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -162,13 +161,11 @@ public class AbstractYamlRebindTest extends RebindTestFixture<StartableApplicati } protected Entity createAndStartApplication(String input, Map<String,?> startParameters) throws Exception { - EntitySpec<?> spec = - mgmt().getTypeRegistry().createSpecFromPlan(CampTypePlanTransformer.FORMAT, input, RegisteredTypeLoadingContexts.spec(Application.class), EntitySpec.class); - final Entity app = mgmt().getEntityManager().createEntity(spec); - getLogger().info("Test created app, and will now start " + app); + final Entity app = createApplicationUnstarted(input); - // start the app (happens automatically if we use camp to instantiate, but not if we use crate spec approach) app.invoke(Startable.START, startParameters).get(); + getLogger().info("Test started app " + app); + return app; } @@ -190,6 +187,19 @@ public class AbstractYamlRebindTest extends RebindTestFixture<StartableApplicati return app; } + protected Entity createApplicationUnstarted(String... multiLineYaml) throws Exception { + return createApplicationUnstarted(joinLines(multiLineYaml)); + } + + protected Entity createApplicationUnstarted(String input) throws Exception { + // starting of the app happens automatically if we use camp to instantiate, but not if we use create spec approach. + EntitySpec<?> spec = + mgmt().getTypeRegistry().createSpecFromPlan(CampTypePlanTransformer.FORMAT, input, RegisteredTypeLoadingContexts.spec(Application.class), EntitySpec.class); + final Entity app = mgmt().getEntityManager().createEntity(spec); + getLogger().info("Test created app (unstarted) " + app); + return app; + } + protected void addCatalogItems(Iterable<String> catalogYaml) { addCatalogItems(joinLines(catalogYaml)); } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2ffd8281/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/SetLimitsCustomizerIntegrationTest.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/SetLimitsCustomizerIntegrationTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/SetLimitsCustomizerIntegrationTest.java new file mode 100644 index 0000000..b62bc22 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/SetLimitsCustomizerIntegrationTest.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.brooklyn; + +import static org.testng.Assert.assertEquals; + +import java.io.File; +import java.nio.file.Files; +import java.util.List; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.entity.machine.SetLimitsCustomizer; +import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; + +public class SetLimitsCustomizerIntegrationTest extends AbstractYamlRebindTest { + + private File file; + + @AfterMethod(alwaysRun=true) + @Override + public void tearDown() throws Exception { + if (file != null) file.delete(); + super.tearDown(); + } + + + // Not using the default /etc/security/limits.d file, because don't want to risk destroying the dev machine's config! + @Test(groups="Integration") + public void testAppendsToGivenFile() throws Exception { + file = File.createTempFile("testAppendsToGivenFile", ".conf"); + + Entity app = createAndStartApplication( + "location: localhost", + "services:", + "- type: " + EmptySoftwareProcess.class.getName(), + " brooklyn.config:", + " provisioning.properties:", + " machineCustomizers:", + " - $brooklyn:object:", + " type: "+SetLimitsCustomizer.class.getName(), + " brooklyn.config:", + " file: " + file.getAbsolutePath(), + " contents:", + " - my line 1", + " - my line 2"); + waitForApplicationTasks(app); + + List<String> actual = Files.readAllLines(file.toPath()); + assertEquals(actual, ImmutableList.of("my line 1", "my line 2"), "actual="+actual); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2ffd8281/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/SetLimitsCustomizerTest.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/SetLimitsCustomizerTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/SetLimitsCustomizerTest.java new file mode 100644 index 0000000..369dfb4 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/SetLimitsCustomizerTest.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.brooklyn; + +import static org.apache.brooklyn.util.core.internal.ssh.ExecCmdAsserts.assertExecContainsLiteral; +import static org.testng.Assert.assertFalse; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.core.entity.trait.Startable; +import org.apache.brooklyn.entity.machine.MachineEntity; +import org.apache.brooklyn.entity.machine.SetLimitsCustomizer; +import org.apache.brooklyn.util.core.internal.ssh.RecordingSshTool; +import org.apache.brooklyn.util.core.internal.ssh.RecordingSshTool.ExecCmd; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +public class SetLimitsCustomizerTest extends AbstractYamlRebindTest { + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + super.setUp(); + RecordingSshTool.clear(); + } + + @AfterMethod(alwaysRun=true) + @Override + public void tearDown() throws Exception { + super.tearDown(); + RecordingSshTool.clear(); + } + + @Test + public void testWritesLimits() throws Exception { + runAppendsToLimitsFile(false); + } + + @Test + public void testRebindBeforeStarts() throws Exception { + runAppendsToLimitsFile(true); + } + + protected void runAppendsToLimitsFile(boolean rebindBeforeStart) throws Exception { + addCatalogItems( + "brooklyn.catalog:", + " version: 0.0.0-SNAPSHOT", + " id: machine-with-ulimits", + " itemType: entity", + " item:", + " type: " + MachineEntity.class.getName(), + " brooklyn.parameters:", + " - name: ulimits", + " type: java.util.List", + " default:", + " - \"* soft nofile 16384\"", + " - \"* hard nofile 16384\"", + " - \"* soft nproc 16384\"", + " - \"* hard nproc 16384\"", + " brooklyn.config:", + " provisioning.properties:", + " machineCustomizers:", + " - $brooklyn:object:", + " type: "+SetLimitsCustomizer.class.getName(), + " brooklyn.config:", + " contents: $brooklyn:config(\"ulimits\")"); + + Entity app = createApplicationUnstarted( + "location:", + " byon:", + " hosts:", + " - 240.0.0.1:1234", + " sshToolClass: "+RecordingSshTool.class.getName(), + " detectMachineDetails: false", + "services:", + "- type: " + MachineEntity.class.getName(), + " brooklyn.config:", + " onbox.base.dir.skipResolution: true", + " sshMonitoring.enabled: false", + " provisioning.properties:", + " machineCustomizers:", + " - $brooklyn:object:", + " type: "+SetLimitsCustomizer.class.getName(), + " brooklyn.config:", + " contents:", + " - \"* soft nofile 1024\"", + " - \"* hard nofile 2048\""); + + if (rebindBeforeStart) { + app = rebind(); + } + app.invoke(Startable.START, ImmutableMap.of()).get(); + waitForApplicationTasks(app); + + RecordingSshTool tool = Iterables.getFirst(RecordingSshTool.getTools(), null); + ExecCmd execCmd = Iterables.getOnlyElement(RecordingSshTool.getExecCmds()); + assertExecContainsLiteral(execCmd, "echo \"* soft nofile 1024\" | tee -a /etc/security/limits.d/50-brooklyn.conf"); + assertExecContainsLiteral(execCmd, "echo \"* hard nofile 2048\" | tee -a /etc/security/limits.d/50-brooklyn.conf"); + + // should be disconnected, so that subsequent connections will pick up the ulimit changes + assertFalse(tool.isConnected()); + } + + @Test + public void runFromCatalog() throws Exception { + addCatalogItems( + "brooklyn.catalog:", + " version: 0.0.0-SNAPSHOT", + " id: machine-with-ulimits", + " itemType: entity", + " item:", + " type: " + MachineEntity.class.getName(), + " brooklyn.parameters:", + " - name: ulimits", + " type: java.util.List", + " default:", + " - \"* soft nofile 1024\"", + " - \"* hard nofile 2048\"", + " brooklyn.config:", + " provisioning.properties:", + " machineCustomizers:", + " - $brooklyn:object:", + " type: "+SetLimitsCustomizer.class.getName(), + " brooklyn.config:", + " contents: $brooklyn:config(\"ulimits\")"); + + Entity app = createAndStartApplication( + "location:", + " byon:", + " hosts:", + " - 240.0.0.1:1234", + " sshToolClass: "+RecordingSshTool.class.getName(), + " detectMachineDetails: false", + "services:", + "- type: machine-with-ulimits", + " brooklyn.config:", + " onbox.base.dir.skipResolution: true"); + waitForApplicationTasks(app); + + RecordingSshTool tool = Iterables.getFirst(RecordingSshTool.getTools(), null); + ExecCmd execCmd = Iterables.getOnlyElement(RecordingSshTool.getExecCmds()); + assertExecContainsLiteral(execCmd, "echo \"* soft nofile 1024\" | tee -a /etc/security/limits.d/50-brooklyn.conf"); + assertExecContainsLiteral(execCmd, "echo \"* hard nofile 2048\" | tee -a /etc/security/limits.d/50-brooklyn.conf"); + + // should be disconnected, so that subsequent connections will pick up the ulimit changes + assertFalse(tool.isConnected()); + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2ffd8281/core/src/test/java/org/apache/brooklyn/util/core/internal/ssh/ExecCmdAsserts.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/util/core/internal/ssh/ExecCmdAsserts.java b/core/src/test/java/org/apache/brooklyn/util/core/internal/ssh/ExecCmdAsserts.java index db2a191..c6d45e6 100644 --- a/core/src/test/java/org/apache/brooklyn/util/core/internal/ssh/ExecCmdAsserts.java +++ b/core/src/test/java/org/apache/brooklyn/util/core/internal/ssh/ExecCmdAsserts.java @@ -52,6 +52,15 @@ public class ExecCmdAsserts { fail(expectedCmdRegex + " not matched by any commands in " + actual+(errMsg != null ? "; "+errMsg : "")); } + public static void assertExecContainsLiteral(ExecCmd actual, String literal) { + for (String cmd : actual.commands) { + if (cmd.contains(literal)) { + return; + } + } + fail("No match for '"+literal+"' in "+actual); + } + public static void assertExecsNotContains(List<? extends ExecCmd> actuals, List<String> expectedNotCmdRegexs) { for (ExecCmd actual : actuals) { assertExecNotContains(actual, expectedNotCmdRegexs); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2ffd8281/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizer.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizer.java b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizer.java new file mode 100644 index 0000000..e3997ff --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizer.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.entity.machine; + +import static com.google.common.base.Preconditions.checkArgument; +import static org.apache.brooklyn.util.ssh.BashCommands.sudo; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.brooklyn.api.location.BasicMachineLocationCustomizer; +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.api.objs.Configurable; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks; +import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks.SshEffectorTaskFactory; +import org.apache.brooklyn.core.objs.BasicConfigurableObject; +import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.apache.brooklyn.util.core.task.DynamicTasks; +import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; +import org.apache.brooklyn.util.text.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.reflect.TypeToken; + +/** + * Sets the limits (such as 'nofile' and 'nproc') on an ssh'able machine. Currently only CentOS and RHEL are supported. + * <p> + * For example: + * <pre> + * {@code + * brooklyn.catalog: + * ... + * item: + * type: org.apache.brooklyn.entity.machine.MachineEntity + * brooklyn.parameters: + * - name: ulimits + * type: java.util.List + * description: | + * Contents to add to the limits config file + * default: + * - "* soft nofile 16384" + * - "* hard nofile 16384" + * - "* soft nproc 16384" + * - "* hard nproc 16384" + * brooklyn.config: + * provisioning.properties: + * machineCustomizers: + * - $brooklyn:object: + * type: org.apache.brooklyn.entity.machine.SetLimitsCustomizer + * brooklyn.config: + * contents: $brooklyn:config("ulimits") + * } + * </pre> + */ +@Beta +public class SetLimitsCustomizer extends BasicMachineLocationCustomizer implements Configurable { + + public static final Logger log = LoggerFactory.getLogger(SetLimitsCustomizer.class); + + public static final ConfigKey<String> FILE_NAME = ConfigKeys.newStringConfigKey( + "file", + "The limits conf file to append to (and to create if necessary)", + "/etc/security/limits.d/50-brooklyn.conf"); + + @SuppressWarnings("serial") + public static final ConfigKey<List<String>> CONTENTS = ConfigKeys.newConfigKey( + new TypeToken<List<String>>() {}, + "contents", + "The contents to be appended to the limits file", + ImmutableList.<String>of()); + + private final BasicConfigurableObject.BasicConfigurationSupport config; + + public SetLimitsCustomizer() { + config = new BasicConfigurableObject.BasicConfigurationSupport(); + } + + @Override + public ConfigurationSupport config() { + return config; + } + + @Override + public <T> T getConfig(ConfigKey<T> key) { + return config().get(key); + } + + @Override + public void customize(MachineLocation machine) { + if (!(machine instanceof SshMachineLocation)) { + throw new IllegalStateException("Machine must be a SshMachineLocation, but got "+machine); + } + String file = config.get(FILE_NAME); + List<String> contents = config.get(CONTENTS); + checkArgument(Strings.isNonBlank(config.get(FILE_NAME)), "File must be non-empty"); + + log.info("SetLimitsCustomizer setting limits on "+machine+" in file "+file+" to: "+Joiner.on("; ").join(contents)); + + try { + List<String> cmds = new ArrayList<>(); + for (String content : contents) { + cmds.add(sudo(String.format("echo \"%s\" | tee -a %s", content, file))); + } + exec((SshMachineLocation)machine, true, cmds.toArray(new String[cmds.size()])); + } catch (Exception e) { + log.info("SetLimitsCustomizer failed to set limits on "+machine+" (rethrowing)", e); + throw e; + } + } + + protected ProcessTaskWrapper<Integer> exec(SshMachineLocation machine, boolean asRoot, String... cmds) { + SshEffectorTaskFactory<Integer> taskFactory = SshEffectorTasks.ssh(machine, cmds).configure(SshMachineLocation.CLOSE_CONNECTION, true); + if (asRoot) taskFactory.runAsRoot(); + ProcessTaskWrapper<Integer> result = DynamicTasks.queue(taskFactory).block(); + if (result.get() != 0) { + throw new IllegalStateException("SetLimitsCustomizer got exit code "+result.get()+" executing on machine "+machine + +"; cmds="+Arrays.asList(cmds)+"; stdout="+result.getStdout()+"; stderr="+result.getStderr()); + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/2ffd8281/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizerTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizerTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizerTest.java new file mode 100644 index 0000000..fe4a91b --- /dev/null +++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetLimitsCustomizerTest.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.entity.machine; + +public class SetLimitsCustomizerTest { + + // See camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/SetLimitsCustomizerTest.java; + // wrote test there so it can use YAML +}