http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltConfigs.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltConfigs.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltConfigs.java deleted file mode 100644 index 2de0e5d..0000000 --- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltConfigs.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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 brooklyn.entity.salt; - -import java.util.Map; - -import brooklyn.config.ConfigKey; -import brooklyn.entity.Entity; -import brooklyn.entity.basic.EntityInternal; -import brooklyn.entity.proxying.EntitySpec; -import brooklyn.event.basic.MapConfigKey.MapModifications; -import brooklyn.event.basic.SetConfigKey.SetModifications; - -import com.google.common.annotations.Beta; -import com.google.common.base.Preconditions; - -/** - * Conveniences for configuring brooklyn Salt entities - * - * @since 0.6.0 - */ -@Beta -public class SaltConfigs { - - public static void addToRunList(EntitySpec<?> entity, String...states) { - for (String state : states) { - entity.configure(SaltConfig.SALT_RUN_LIST, SetModifications.addItem(state)); - } - } - - public static void addToRunList(EntityInternal entity, String...states) { - for (String state : states) { - entity.setConfig(SaltConfig.SALT_RUN_LIST, SetModifications.addItem(state)); - } - } - - public static void addToFormuals(EntitySpec<?> entity, String formulaName, String formulaUrl) { - entity.configure(SaltConfig.SALT_FORMULAS.subKey(formulaName), formulaUrl); - } - - public static void addToFormulas(EntityInternal entity, String formulaName, String formulaUrl) { - entity.setConfig(SaltConfig.SALT_FORMULAS.subKey(formulaName), formulaUrl); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static void addLaunchAttributes(EntitySpec<?> entity, Map<? extends Object,? extends Object> attributesMap) { - entity.configure(SaltConfig.SALT_LAUNCH_ATTRIBUTES, MapModifications.add((Map)attributesMap)); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static void addLaunchAttributes(EntityInternal entity, Map<? extends Object,? extends Object> attributesMap) { - entity.setConfig(SaltConfig.SALT_LAUNCH_ATTRIBUTES, MapModifications.add((Map)attributesMap)); - } - - /** replaces the attributes underneath the rootAttribute parameter with the given value; - * see {@link #addLaunchAttributesMap(EntitySpec, Map)} for richer functionality */ - public static void setLaunchAttribute(EntitySpec<?> entity, String rootAttribute, Object value) { - entity.configure(SaltConfig.SALT_LAUNCH_ATTRIBUTES.subKey(rootAttribute), value); - } - - /** replaces the attributes underneath the rootAttribute parameter with the given value; - * see {@link #addLaunchAttributesMap(EntitySpec, Map)} for richer functionality */ - public static void setLaunchAttribute(EntityInternal entity, String rootAttribute, Object value) { - entity.setConfig(SaltConfig.SALT_LAUNCH_ATTRIBUTES.subKey(rootAttribute), value); - } - - public static <T> T getRequiredConfig(Entity entity, ConfigKey<T> key) { - return Preconditions.checkNotNull( - Preconditions.checkNotNull(entity, "Entity must be supplied").getConfig(key), - "Key "+key+" is required on "+entity); - } - -}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java deleted file mode 100644 index 82e1b17..0000000 --- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * 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 brooklyn.entity.salt; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.config.BrooklynServerConfig; -import brooklyn.entity.Entity; -import brooklyn.entity.basic.Attributes; -import brooklyn.entity.basic.Lifecycle; -import brooklyn.entity.software.MachineLifecycleEffectorTasks; -import brooklyn.entity.software.SshEffectorTasks; -import brooklyn.location.MachineLocation; -import brooklyn.util.net.Urls; -import brooklyn.util.ssh.BashCommands; -import brooklyn.util.task.DynamicTasks; -import brooklyn.util.task.Tasks; -import brooklyn.util.time.Duration; -import brooklyn.util.time.Time; - -import com.google.common.annotations.Beta; -import com.google.common.base.Supplier; - -/** - * Creates effectors to start, restart, and stop processes using SaltStack. - * <p> - * Instances of this should use the {@link SaltConfig} config attributes to configure startup, - * and invoke {@link #usePidFile(String)} or {@link #useService(String)} to determine check-running and stop behaviour. - * Alternatively this can be subclassed and {@link #postStartCustom()} and {@link #stopProcessesAtMachine()} overridden. - * - * @since 0.6.0 - */ -@Beta -public class SaltLifecycleEffectorTasks extends MachineLifecycleEffectorTasks implements SaltConfig { - - private static final Logger log = LoggerFactory.getLogger(SaltLifecycleEffectorTasks.class); - - protected SaltStackMaster master = null; - protected String pidFile, serviceName, windowsServiceName; - - public SaltLifecycleEffectorTasks() { - } - - public SaltLifecycleEffectorTasks usePidFile(String pidFile) { - this.pidFile = pidFile; - return this; - } - public SaltLifecycleEffectorTasks useService(String serviceName) { - this.serviceName = serviceName; - return this; - } - public SaltLifecycleEffectorTasks useWindowsService(String serviceName) { - this.windowsServiceName = serviceName; - return this; - } - public SaltLifecycleEffectorTasks master(SaltStackMaster master) { - this.master = master; - return this; - } - - @Override - public void attachLifecycleEffectors(Entity entity) { - if (pidFile==null && serviceName==null && getClass().equals(SaltLifecycleEffectorTasks.class)) { - // warn on incorrect usage - log.warn("Uses of "+getClass()+" must define a PID file or a service name (or subclass and override {start,stop} methods as per javadoc) " + - "in order for check-running and stop to work"); - } - - super.attachLifecycleEffectors(entity); - } - - @Override - protected String startProcessesAtMachine(Supplier<MachineLocation> machineS) { - startMinionAsync(); - return "salt start tasks submitted"; - } - - protected void startMinionAsync() { - // TODO make directories more configurable (both for ssh-drivers and for this) - String installDir = Urls.mergePaths(BrooklynServerConfig.getMgmtBaseDir(entity().getManagementContext()), "salt-install"); - String runDir = Urls.mergePaths(BrooklynServerConfig.getMgmtBaseDir(entity().getManagementContext()), - "apps/"+entity().getApplicationId()+"/salt-entities/"+entity().getId()); - - Boolean masterless = entity().getConfig(SaltConfig.MASTERLESS_MODE); - if (masterless) { - DynamicTasks.queue( - SaltTasks.installFormulas(installDir, SaltConfigs.getRequiredConfig(entity(), SALT_FORMULAS), false), - SaltTasks.buildSaltFile(runDir, - SaltConfigs.getRequiredConfig(entity(), SALT_RUN_LIST), - entity().getConfig(SALT_LAUNCH_ATTRIBUTES)), - SaltTasks.installSaltMinion(entity(), runDir, installDir, false), - SaltTasks.runSalt(runDir)); - } else { - throw new UnsupportedOperationException("Salt master mode not yet supported for minions"); - } - } - - @Override - protected void postStartCustom() { - boolean result = false; - result |= tryCheckStartPid(); - result |= tryCheckStartService(); - result |= tryCheckStartWindowsService(); - if (!result) { - throw new IllegalStateException("The process for "+entity()+" appears not to be running (no way to check!)"); - } - } - - protected boolean tryCheckStartPid() { - if (pidFile==null) return false; - - // if it's still up after 5s assume we are good (default behaviour) - Time.sleep(Duration.FIVE_SECONDS); - if (!DynamicTasks.queue(SshEffectorTasks.isPidFromFileRunning(pidFile).runAsRoot()).get()) { - throw new IllegalStateException("The process for "+entity()+" appears not to be running (pid file "+pidFile+")"); - } - - // and set the PID - entity().setAttribute(Attributes.PID, - Integer.parseInt(DynamicTasks.queue(SshEffectorTasks.ssh("cat "+pidFile).runAsRoot()).block().getStdout().trim())); - return true; - } - - protected boolean tryCheckStartService() { - if (serviceName==null) return false; - - // if it's still up after 5s assume we are good (default behaviour) - Time.sleep(Duration.FIVE_SECONDS); - if (!((Integer)0).equals(DynamicTasks.queue(SshEffectorTasks.ssh("/etc/init.d/"+serviceName+" status").runAsRoot()).get())) { - throw new IllegalStateException("The process for "+entity()+" appears not to be running (service "+serviceName+")"); - } - - return true; - } - - protected boolean tryCheckStartWindowsService() { - if (windowsServiceName==null) return false; - - // if it's still up after 5s assume we are good (default behaviour) - Time.sleep(Duration.FIVE_SECONDS); - if (!((Integer)0).equals(DynamicTasks.queue(SshEffectorTasks.ssh("sc query \""+serviceName+"\" | find \"RUNNING\"").runAsCommand()).get())) { - throw new IllegalStateException("The process for "+entity()+" appears not to be running (windowsService "+windowsServiceName+")"); - } - - return true; - } - - @Override - protected String stopProcessesAtMachine() { - boolean result = false; - result |= tryStopService(); - result |= tryStopPid(); - if (!result) { - throw new IllegalStateException("The process for "+entity()+" appears could not be stopped (no impl!)"); - } - return "stopped"; - } - - protected boolean tryStopService() { - if (serviceName==null) return false; - int result = DynamicTasks.queue(SshEffectorTasks.ssh("/etc/init.d/"+serviceName+" stop").runAsRoot()).get(); - if (0==result) return true; - if (entity().getAttribute(Attributes.SERVICE_STATE)!=Lifecycle.RUNNING) - return true; - - throw new IllegalStateException("The process for "+entity()+" appears could not be stopped (exit code "+result+" to service stop)"); - } - - protected boolean tryStopPid() { - Integer pid = entity().getAttribute(Attributes.PID); - if (pid==null) { - if (entity().getAttribute(Attributes.SERVICE_STATE)==Lifecycle.RUNNING && pidFile==null) - log.warn("No PID recorded for "+entity()+" when running, with PID file "+pidFile+"; skipping kill in "+Tasks.current()); - else - if (log.isDebugEnabled()) - log.debug("No PID recorded for "+entity()+"; skipping ("+entity().getAttribute(Attributes.SERVICE_STATE)+" / "+pidFile+")"); - return false; - } - - // allow non-zero exit as process may have already been killed - DynamicTasks.queue(SshEffectorTasks.ssh( - "kill "+pid, "sleep 5", BashCommands.ok("kill -9 "+pid)).allowingNonZeroExitCode().runAsRoot()).block(); - - if (DynamicTasks.queue(SshEffectorTasks.isPidRunning(pid).runAsRoot()).get()) { - throw new IllegalStateException("Process for "+entity()+" in "+pid+" still running after kill"); - } - entity().setAttribute(Attributes.PID, null); - return true; - } - - /** - * {@inheritDoc} - * - * @return the Salt master entity if it exists. - * @see #master(SaltStackMaster) - * @see SaltConfig#MASTERLESS_MODE - */ - @Override - public SaltStackMaster getMaster() { - return master; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMaster.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMaster.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMaster.java deleted file mode 100644 index 0db1841..0000000 --- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMaster.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 brooklyn.entity.salt; - -import java.util.List; - -import org.apache.brooklyn.catalog.Catalog; -import brooklyn.config.ConfigKey; -import brooklyn.entity.basic.BrooklynConfigKeys; -import brooklyn.entity.basic.ConfigKeys; -import brooklyn.entity.basic.SoftwareProcess; -import brooklyn.entity.proxying.ImplementedBy; -import brooklyn.event.AttributeSensor; -import brooklyn.event.basic.BasicAttributeSensor; -import brooklyn.event.basic.PortAttributeSensorAndConfigKey; -import brooklyn.util.flags.SetFromFlag; - -import com.google.common.reflect.TypeToken; - -@ImplementedBy(SaltStackMasterImpl.class) -@Catalog(name="SaltStack Master", description="The Salt master server") -public interface SaltStackMaster extends SoftwareProcess { - - @SetFromFlag("version") - ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(BrooklynConfigKeys.SUGGESTED_VERSION, "stable"); - - @SetFromFlag("bootstrapUrl") - ConfigKey<String> BOOTSTRAP_URL = ConfigKeys.newStringConfigKey( - "salt.bootstrap.url", "The URL that returns the Salt boostrap commands", - "http://bootstrap.saltstack.org/"); - - @SetFromFlag("masterUser") - ConfigKey<String> MASTER_USER = ConfigKeys.newStringConfigKey( - "salt.master.user", "The user that runs the Salt master daemon process", - "root"); - - @SetFromFlag("masterConfigTemplate") - ConfigKey<String> MASTER_CONFIG_TEMPLATE_URL = ConfigKeys.newStringConfigKey( - "salt.master.config.templateUrl", "The template for the Salt master configuration (URL)", - "classpath:///brooklyn/entity/salt/master"); - - @SetFromFlag("saltPort") - PortAttributeSensorAndConfigKey SALT_PORT = new PortAttributeSensorAndConfigKey( - "salt.port", "Port used for communication between Salt master and minion processes", "4506+"); - - @SetFromFlag("publishPort") - PortAttributeSensorAndConfigKey PUBLISH_PORT = new PortAttributeSensorAndConfigKey( - "salt.publish.port", "Port used by the Salt master publisher", "4505+"); - - @SuppressWarnings("serial") - AttributeSensor<List<String>> MINION_IDS = new BasicAttributeSensor<List<String>>(new TypeToken<List<String>>() {}, - "salt.minions", "List of Salt minion IDs"); - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterDriver.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterDriver.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterDriver.java deleted file mode 100644 index 55d46da..0000000 --- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterDriver.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 brooklyn.entity.salt; - -import brooklyn.entity.basic.SoftwareProcessDriver; - -public interface SaltStackMasterDriver extends SoftwareProcessDriver { - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterImpl.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterImpl.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterImpl.java deleted file mode 100644 index 2a6815c..0000000 --- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterImpl.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 brooklyn.entity.salt; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.entity.basic.SoftwareProcessImpl; -import brooklyn.event.feed.ConfigToAttributes; - -public class SaltStackMasterImpl extends SoftwareProcessImpl implements SaltStackMaster { - - private static final Logger log = LoggerFactory.getLogger(SaltStackMasterImpl.class); - - public SaltStackMasterImpl() { - super(); - } - - @Override - public Class getDriverInterface() { - return SaltStackMasterDriver.class; - } - - @Override - protected void connectSensors() { - super.connectSensors(); - - // TODO what sensors should we poll? - ConfigToAttributes.apply(this); - - connectServiceUpIsRunning(); - } - - @Override - protected void disconnectSensors() { - disconnectServiceUpIsRunning(); - - super.disconnectSensors(); - } -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterSshDriver.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterSshDriver.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterSshDriver.java deleted file mode 100644 index 8a326e7..0000000 --- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterSshDriver.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 brooklyn.entity.salt; - -import brooklyn.entity.basic.Entities; -import brooklyn.entity.java.JavaSoftwareProcessSshDriver; -import brooklyn.location.basic.SshMachineLocation; -import brooklyn.util.ssh.BashCommands; -import brooklyn.util.task.DynamicTasks; - -import com.google.common.collect.ImmutableMap; - -public class SaltStackMasterSshDriver extends JavaSoftwareProcessSshDriver implements SaltStackMasterDriver { - - public SaltStackMasterSshDriver(SaltStackMasterImpl entity, SshMachineLocation machine) { - super(entity, machine); - } - - @Override - public SaltStackMasterImpl getEntity() { - return (SaltStackMasterImpl) super.getEntity(); - } - - @Override - protected String getLogFileLocation() { - return "master.log"; - } - - private String getPidFile() { - return "master.pid"; - } - - @Override - public void install() { - String url = Entities.getRequiredUrlConfig(getEntity(), SaltStackMaster.BOOTSTRAP_URL); - copyTemplate(url, "/etc/salt/master"); - - // Copy the file contents to the remote machine -// DynamicTasks.queue(SshEffectorTasks.put("/tmp/cumulus.yaml").contents(contents)).get(); - - // Run Salt bootstrap task to install master - DynamicTasks.queue(SaltTasks.installSaltMaster(getEntity(), getRunDir(), true)); - - - newScript("createInstallDir") - .body.append("mkdir -p "+getInstallDir()) - .failOnNonZeroResultCode() - .execute(); - - newScript(INSTALLING). - failOnNonZeroResultCode(). - body.append("").execute(); - } - - @Override - public void customize() { - } - - @Override - public void launch() { - newScript(ImmutableMap.of("usePidFile", false), LAUNCHING) - .body.append(BashCommands.sudo("start salt-master")) - .execute(); - } - - @Override - public boolean isRunning() { - return newScript(ImmutableMap.of("usePidFile", false), CHECK_RUNNING) - .body.append(BashCommands.sudo("status salt-master")) - .execute() == 0; - } - - @Override - public void stop() { - newScript(ImmutableMap.of("usePidFile", false), STOPPING) - .body.append(BashCommands.sudo("stop salt-master")) - .execute(); - } -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltTasks.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltTasks.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltTasks.java deleted file mode 100644 index 5b4c4ee..0000000 --- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltTasks.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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 brooklyn.entity.salt; - -import static brooklyn.util.ssh.BashCommands.INSTALL_CURL; -import static brooklyn.util.ssh.BashCommands.INSTALL_TAR; -import static brooklyn.util.ssh.BashCommands.INSTALL_UNZIP; -import static brooklyn.util.ssh.BashCommands.downloadToStdout; -import static brooklyn.util.ssh.BashCommands.sudo; - -import java.util.Map; - -import brooklyn.entity.Entity; -import brooklyn.entity.basic.Entities; -import brooklyn.entity.effector.EffectorTasks; -import brooklyn.entity.software.SshEffectorTasks; -import brooklyn.management.TaskFactory; -import brooklyn.util.ResourceUtils; -import brooklyn.util.collections.MutableMap; -import brooklyn.util.net.Urls; -import brooklyn.util.ssh.BashCommands; -import brooklyn.util.task.DynamicTasks; -import brooklyn.util.task.Tasks; -import brooklyn.util.text.TemplateProcessor; - -import com.google.common.annotations.Beta; - -@Beta -public class SaltTasks { - - public static TaskFactory<?> installSaltMaster(Entity master, String saltDirectory, boolean force) { - // TODO check on entity whether it is salt _server_ - String boostrapUrl = master.getConfig(SaltStackMaster.BOOTSTRAP_URL); - String version = master.getConfig(SaltStackMaster.SUGGESTED_VERSION); - String installCmd = cdAndRun(saltDirectory, - BashCommands.chain( - INSTALL_CURL, - INSTALL_TAR, - INSTALL_UNZIP, - "( "+downloadToStdout(boostrapUrl) + " | " + sudo("sh -s -- -M -N "+version)+" )")); - if (!force) installCmd = BashCommands.alternatives("which salt-master", installCmd); - return SshEffectorTasks.ssh(installCmd).summary("install salt master"); - } - - public static TaskFactory<?> installSaltMinion(final Entity minion, final String runDir, final String installDir, final boolean force) { - return Tasks.<Void>builder().name("install minion").body( - new Runnable() { - public void run() { - // Setup bootstrap installation command for minion - String boostrapUrl = minion.getConfig(SaltStackMaster.BOOTSTRAP_URL); - String installCmd = cdAndRun(runDir, BashCommands.chain( - INSTALL_CURL, - INSTALL_TAR, - INSTALL_UNZIP, - "( "+downloadToStdout(boostrapUrl) + " | " + sudo("sh")+" )")); - if (!force) installCmd = BashCommands.alternatives("which salt-minion", installCmd); - - // Process the minion configuration template - Boolean masterless = minion.getConfig(SaltConfig.MASTERLESS_MODE); - String url = masterless ? Entities.getRequiredUrlConfig(minion, SaltConfig.MASTERLESS_CONFIGURATION_URL) - : Entities.getRequiredUrlConfig(minion, SaltConfig.MINION_CONFIGURATION_URL); - Map<String, Object> config = MutableMap.<String, Object>builder() - .put("entity", minion) - .put("runDir", runDir) - .put("installDir", installDir) - .put("formulas", minion.getConfig(SaltConfig.SALT_FORMULAS)) - .build(); - String contents = TemplateProcessor.processTemplateContents(new ResourceUtils(minion).getResourceAsString(url), config); - - // Copy the file contents to the remote machine and install/start salt-minion - DynamicTasks.queue( - SshEffectorTasks.ssh(installCmd), - SshEffectorTasks.put("/tmp/minion") - .contents(contents) - .createDirectory(), - SshEffectorTasks.ssh(sudo("mv /tmp/minion /etc/salt/minion")), // TODO clunky - SshEffectorTasks.ssh(sudo("restart salt-minion")) - ); - } - }).buildFactory(); - } - - public static TaskFactory<?> installFormulas(final String installDir, final Map<String,String> formulasAndUrls, final boolean force) { - return Tasks.<Void>builder().name("install formulas").body( - new Runnable() { - public void run() { - Entity e = EffectorTasks.findEntity(); - if (formulasAndUrls==null) - throw new IllegalStateException("No formulas defined to install at "+e); - for (String formula: formulasAndUrls.keySet()) - DynamicTasks.queue(installFormula(installDir, formula, formulasAndUrls.get(formula), force)); - } - }).buildFactory(); - } - - public static TaskFactory<?> installFormula(String installDir, String formula, String url, boolean force) { - return SshEffectorTasks.ssh(cdAndRun(installDir, SaltBashCommands.downloadAndExpandFormula(url, formula, force))) - .summary("install formula "+formula) - .requiringExitCodeZero(); - } - - protected static String cdAndRun(String targetDirectory, String command) { - return BashCommands.chain( - "mkdir -p "+targetDirectory, - "cd "+targetDirectory, - command); - } - - public static TaskFactory<?> buildSaltFile(String runDir, Iterable<? extends String> runList, Map<String, Object> attributes) { - StringBuilder top = new StringBuilder() - .append("base:\n") - .append(" '*':\n"); - for (String run : runList) { - top.append(" - " + run + "\n"); - } - - return SshEffectorTasks.put(Urls.mergePaths(runDir, "base", "top.sls")) - .contents(top.toString()) - .summary("build salt top file") - .createDirectory(); - } - - public static TaskFactory<?> runSalt(String runDir) { - return SshEffectorTasks.ssh(cdAndRun(runDir, BashCommands.sudo("salt-call state.highstate"))) - .summary("run salt install") - .requiringExitCodeZero(); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNodeSaltImpl.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNodeSaltImpl.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNodeSaltImpl.java new file mode 100644 index 0000000..66c7acb --- /dev/null +++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNodeSaltImpl.java @@ -0,0 +1,179 @@ +/* + * 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.database.postgresql; + +import org.apache.brooklyn.entity.database.postgresql.PostgreSqlNodeSaltImpl; +import org.apache.brooklyn.entity.salt.SaltConfig; +import org.apache.brooklyn.entity.salt.SaltConfigs; +import org.apache.brooklyn.entity.salt.SaltLifecycleEffectorTasks; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.Effector; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.EffectorStartableImpl; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.database.postgresql.PostgreSqlNode; +import brooklyn.entity.effector.EffectorBody; +import brooklyn.entity.effector.Effectors; +import brooklyn.entity.software.SshEffectorTasks; +import brooklyn.event.basic.DependentConfiguration; +import brooklyn.event.feed.ssh.SshFeed; +import brooklyn.event.feed.ssh.SshPollConfig; +import brooklyn.location.Location; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.util.ResourceUtils; +import brooklyn.util.config.ConfigBag; +import brooklyn.util.ssh.BashCommands; +import brooklyn.util.task.DynamicTasks; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +public class PostgreSqlNodeSaltImpl extends EffectorStartableImpl implements PostgreSqlNode, SoftwareProcess { + + private static final Logger LOG = LoggerFactory.getLogger(PostgreSqlNodeSaltImpl.class); + + public static final Effector<String> EXECUTE_SCRIPT = Effectors.effector(String.class, "executeScript") + .description("invokes a script") + .parameter(ExecuteScriptEffectorBody.SCRIPT) + .impl(new ExecuteScriptEffectorBody()) + .build(); + + private SshFeed feed; + + @Override + public void init() { + super.init(); + new SaltPostgreSqlLifecycle().attachLifecycleEffectors(this); + } + + public static class SaltPostgreSqlLifecycle extends SaltLifecycleEffectorTasks { + public SaltPostgreSqlLifecycle() { + usePidFile("/var/run/postgresql/*.pid"); + useService("postgresql"); + } + + @Override + protected void startMinionAsync() { + Entities.warnOnIgnoringConfig(entity(), SaltConfig.SALT_FORMULAS); + Entities.warnOnIgnoringConfig(entity(), SaltConfig.SALT_RUN_LIST); + Entities.warnOnIgnoringConfig(entity(), SaltConfig.SALT_LAUNCH_ATTRIBUTES); + + // TODO Set these as defaults, rather than replacing user's value!? + SaltConfigs.addToFormulas(entity(), "postgres", "https://github.com/saltstack-formulas/postgres-formula/archive/master.tar.gz"); + SaltConfigs.addToRunList(entity(), "postgres"); + SaltConfigs.addLaunchAttributes(entity(), ImmutableMap.<String,Object>builder() + .put("port", DependentConfiguration.attributeWhenReady(entity(), PostgreSqlNode.POSTGRESQL_PORT)) + .put("listen_addresses", "*") + .put("pg_hba.type", "host") + .put("pg_hba.db", "all") + .put("pg_hba.user", "all") + .put("pg_hba.addr", "0.0.0.0/0") + .put("pg_hba.method", "md5") + .build()); + + super.startMinionAsync(); + } + + @Override + protected void postStartCustom() { + super.postStartCustom(); + + // now run the creation script + String creationScriptUrl = entity().getConfig(PostgreSqlNode.CREATION_SCRIPT_URL); + String creationScript; + if (creationScriptUrl != null) { + creationScript = new ResourceUtils(entity()).getResourceAsString(creationScriptUrl); + } else { + creationScript = entity().getConfig(PostgreSqlNode.CREATION_SCRIPT_CONTENTS); + } + entity().invoke(PostgreSqlNodeSaltImpl.EXECUTE_SCRIPT, + ConfigBag.newInstance().configure(ExecuteScriptEffectorBody.SCRIPT, creationScript).getAllConfig()).getUnchecked(); + + // and finally connect sensors + ((PostgreSqlNodeSaltImpl) entity()).connectSensors(); + } + + @Override + protected void preStopCustom() { + ((PostgreSqlNodeSaltImpl) entity()).disconnectSensors(); + super.preStopCustom(); + } + } + + public static class ExecuteScriptEffectorBody extends EffectorBody<String> { + public static final ConfigKey<String> SCRIPT = ConfigKeys.newStringConfigKey("script", "contents of script to run"); + + @Override + public String call(ConfigBag parameters) { + return DynamicTasks.queue(SshEffectorTasks.ssh( + BashCommands.pipeTextTo( + parameters.get(SCRIPT), + BashCommands.sudoAsUser("postgres", "psql --file -"))) + .requiringExitCodeZero()).getStdout(); + } + } + + protected void connectSensors() { + setAttribute(DATASTORE_URL, String.format("postgresql://%s:%s/", getAttribute(HOSTNAME), getAttribute(POSTGRESQL_PORT))); + + Location machine = Iterables.get(getLocations(), 0, null); + + if (machine instanceof SshMachineLocation) { + feed = SshFeed.builder() + .entity(this) + .machine((SshMachineLocation)machine) + .poll(new SshPollConfig<Boolean>(SERVICE_UP) + .command("ps -ef | grep [p]ostgres") + .setOnSuccess(true) + .setOnFailureOrException(false)) + .build(); + } else { + LOG.warn("Location(s) %s not an ssh-machine location, so not polling for status; setting serviceUp immediately", getLocations()); + } + } + + protected void disconnectSensors() { + if (feed != null) feed.stop(); + } + + @Override + public Integer getPostgreSqlPort() { return getAttribute(POSTGRESQL_PORT); } + + @Override + public String getSharedMemory() { return getConfig(SHARED_MEMORY); } + + @Override + public Integer getMaxConnections() { return getConfig(MAX_CONNECTIONS); } + + @Override + public String getShortName() { + return "PostgreSQL"; + } + + @Override + public String executeScript(String commands) { + return Entities.invokeEffector(this, this, EXECUTE_SCRIPT, + ConfigBag.newInstance().configure(ExecuteScriptEffectorBody.SCRIPT, commands).getAllConfig()).getUnchecked(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltBashCommands.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltBashCommands.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltBashCommands.java new file mode 100644 index 0000000..a9a07bf --- /dev/null +++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltBashCommands.java @@ -0,0 +1,92 @@ +/* + * 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.salt; + +import static brooklyn.util.ssh.BashCommands.downloadToStdout; + +import javax.annotation.Nullable; + +import org.apache.commons.io.FilenameUtils; + +import brooklyn.entity.chef.ChefBashCommands; +import brooklyn.util.ssh.BashCommands; +import brooklyn.util.text.Identifiers; +import brooklyn.util.text.Strings; + +import com.google.common.annotations.Beta; +import com.google.common.io.Files; + +/** + * BASH commands useful for setting up SaltStack. + */ +@Beta +public class SaltBashCommands { + + /** + * SaltStack formulas can be found at {@code https://github.com/saltstack-formulas} as repositories. + * <p> + * This assumes the download is an archive containing a single directory on the root which will + * be renamed to {@code formulaName}. if that directory already has the correct name {@code formulaName} + * can be null, but if taking from a GitHub tarball it will typically be of the form {@code formulaName-master/} + * hence the renaming. + */ + // TODO support installing from classpath, and using the repository (tie in with those methods) + public static final String downloadAndExpandFormula(String source, @Nullable String formulaName, boolean force) { + String dl = downloadAndExpandFormula(source); + if (formulaName==null) return dl; + String tmpName = "tmp-"+Strings.makeValidFilename(formulaName)+"-"+Identifiers.makeRandomId(4); + String installCmd = BashCommands.chain("mkdir "+tmpName, "cd "+tmpName, dl, + BashCommands.requireTest("`ls | wc -w` -eq 1", "The archive must contain exactly one directory"), + "FORMULA_EXPANDED_DIR=`ls`", + "mv $FORMULA_EXPANDED_DIR '../"+formulaName+"'", + "cd ..", + "rm -rf "+tmpName); + if (!force) return BashCommands.alternatives("ls "+formulaName, installCmd); + else return BashCommands.alternatives("rm -rf "+formulaName, installCmd); + } + + /** + * Same as {@link #downloadAndExpandFormula(String, String)} with no formula name. + * <p> + * Equivalent to the following command, but substituting the given {@code sourceUrl}. + * <pre>{@code + * curl -f -L https://github.com/saltstack-formulas/nginx-formula/archive/master.tar.gz | tar xvz + * }</pre> + */ + public static final String downloadAndExpandFormula(String sourceUrl) { + String ext = Files.getFileExtension(sourceUrl); + if ("tar".equalsIgnoreCase(ext)) + return downloadToStdout(sourceUrl) + " | tar xv"; + if ("tgz".equalsIgnoreCase(ext) || sourceUrl.toLowerCase().endsWith(".tar.gz")) + return downloadToStdout(sourceUrl) + " | tar xvz"; + + String target = FilenameUtils.getName(sourceUrl); + if (target==null) target = ""; else target = target.trim(); + target += "_"+Strings.makeRandomId(4); + + if ("zip".equalsIgnoreCase(ext) || "tar.gz".equalsIgnoreCase(ext)) + return BashCommands.chain( + BashCommands.commandToDownloadUrlAs(sourceUrl, target), + "unzip "+target, + "rm "+target); + + throw new UnsupportedOperationException("No way to expand "+sourceUrl+" (yet)"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfig.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfig.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfig.java new file mode 100644 index 0000000..3b5962f --- /dev/null +++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfig.java @@ -0,0 +1,100 @@ +/* + * 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.salt; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.event.AttributeSensor; +import brooklyn.event.basic.BasicAttributeSensor; +import brooklyn.event.basic.BasicConfigKey; +import brooklyn.event.basic.MapConfigKey; +import brooklyn.event.basic.SetConfigKey; +import brooklyn.management.TaskAdaptable; +import brooklyn.management.TaskFactory; +import brooklyn.util.flags.SetFromFlag; + +import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.reflect.TypeToken; + +/** + * {@link ConfigKey}s used to configure Salt entities. + * + * @see SaltConfigs + * @see SaltLifecycleEffectorTasks + */ +@Beta +public interface SaltConfig { + + MapConfigKey<String> SALT_FORMULAS = new MapConfigKey<String>(String.class, + "salt.formulaUrls", "Map of Salt formula URLs (normally GutHub repository archives from the salt-formulas user)"); + SetConfigKey<String> SALT_RUN_LIST = new SetConfigKey<String>(String.class, + "salt.runList", "Set of Salt states to install from the formula URLs"); + MapConfigKey<Object> SALT_LAUNCH_ATTRIBUTES = new MapConfigKey<Object>(Object.class, "salt.launch.attributes", "TODO"); + + @SetFromFlag("master") + ConfigKey<SaltStackMaster> MASTER = ConfigKeys.newConfigKey(SaltStackMaster.class, + "salt.master.entity", "The Salt master server"); + + AttributeSensor<String> MINION_ID = new BasicAttributeSensor<String>(String.class, + "salt.minionId", "The ID for a Salt minion"); + + @SetFromFlag("masterless") + ConfigKey<Boolean> MASTERLESS_MODE = ConfigKeys.newBooleanConfigKey( + "salt.masterless", "Salt masterless, minion only configuration (default uses master and minion)", + Boolean.FALSE); + + @SetFromFlag("masterConfigUrl") + ConfigKey<String> MASTER_CONFIGURATION_URL = ConfigKeys.newStringConfigKey( + "salt.master.templateUrl", "The Salt master configuration file template URL", + "classpath://org/apache/brooklyn/entity/salt/master"); + + @SetFromFlag("minionConfigUrl") + ConfigKey<String> MINION_CONFIGURATION_URL = ConfigKeys.newStringConfigKey( + "salt.minion.templateUrl", "The Salt minion configuration file template URL", + "classpath://org/apache/brooklyn/entity/salt/minion"); + + @SetFromFlag("masterlessConfigUrl") + ConfigKey<String> MASTERLESS_CONFIGURATION_URL = ConfigKeys.newStringConfigKey( + "salt.masterless.templateUrl", "The Salt minion masterless configuration file template URL", + "classpath://org/apache/brooklyn/entity/salt/masterless"); + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @SetFromFlag("minionIdFunction") + ConfigKey<Function<Entity, String>> MINION_ID_FUNCTION = new BasicConfigKey(Function.class, + "salt.minionId.function", "Function to generate the ID of a Salt minion for an entity", Functions.toStringFunction()); + + @SuppressWarnings("serial") + ConfigKey<TaskFactory<? extends TaskAdaptable<Boolean>>> IS_RUNNING_TASK = ConfigKeys.newConfigKey( + new TypeToken<TaskFactory<? extends TaskAdaptable<Boolean>>>() {}, + "salt.driver.isRunningTask"); + + @SuppressWarnings("serial") + ConfigKey<TaskFactory<?>> STOP_TASK = ConfigKeys.newConfigKey( + new TypeToken<TaskFactory<?>>() {}, + "salt.driver.stopTask"); + + /** + * The {@link SaltStackMaster master} entity. + */ + SaltStackMaster getMaster(); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfigs.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfigs.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfigs.java new file mode 100644 index 0000000..1622d85 --- /dev/null +++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfigs.java @@ -0,0 +1,89 @@ +/* + * 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.salt; + +import java.util.Map; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.EntityInternal; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.event.basic.MapConfigKey.MapModifications; +import brooklyn.event.basic.SetConfigKey.SetModifications; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; + +/** + * Conveniences for configuring brooklyn Salt entities + * + * @since 0.6.0 + */ +@Beta +public class SaltConfigs { + + public static void addToRunList(EntitySpec<?> entity, String...states) { + for (String state : states) { + entity.configure(SaltConfig.SALT_RUN_LIST, SetModifications.addItem(state)); + } + } + + public static void addToRunList(EntityInternal entity, String...states) { + for (String state : states) { + entity.setConfig(SaltConfig.SALT_RUN_LIST, SetModifications.addItem(state)); + } + } + + public static void addToFormuals(EntitySpec<?> entity, String formulaName, String formulaUrl) { + entity.configure(SaltConfig.SALT_FORMULAS.subKey(formulaName), formulaUrl); + } + + public static void addToFormulas(EntityInternal entity, String formulaName, String formulaUrl) { + entity.setConfig(SaltConfig.SALT_FORMULAS.subKey(formulaName), formulaUrl); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static void addLaunchAttributes(EntitySpec<?> entity, Map<? extends Object,? extends Object> attributesMap) { + entity.configure(SaltConfig.SALT_LAUNCH_ATTRIBUTES, MapModifications.add((Map)attributesMap)); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static void addLaunchAttributes(EntityInternal entity, Map<? extends Object,? extends Object> attributesMap) { + entity.setConfig(SaltConfig.SALT_LAUNCH_ATTRIBUTES, MapModifications.add((Map)attributesMap)); + } + + /** replaces the attributes underneath the rootAttribute parameter with the given value; + * see {@link #addLaunchAttributesMap(EntitySpec, Map)} for richer functionality */ + public static void setLaunchAttribute(EntitySpec<?> entity, String rootAttribute, Object value) { + entity.configure(SaltConfig.SALT_LAUNCH_ATTRIBUTES.subKey(rootAttribute), value); + } + + /** replaces the attributes underneath the rootAttribute parameter with the given value; + * see {@link #addLaunchAttributesMap(EntitySpec, Map)} for richer functionality */ + public static void setLaunchAttribute(EntityInternal entity, String rootAttribute, Object value) { + entity.setConfig(SaltConfig.SALT_LAUNCH_ATTRIBUTES.subKey(rootAttribute), value); + } + + public static <T> T getRequiredConfig(Entity entity, ConfigKey<T> key) { + return Preconditions.checkNotNull( + Preconditions.checkNotNull(entity, "Entity must be supplied").getConfig(key), + "Key "+key+" is required on "+entity); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java new file mode 100644 index 0000000..7005db9 --- /dev/null +++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java @@ -0,0 +1,220 @@ +/* + * 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.salt; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.BrooklynServerConfig; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.Lifecycle; +import brooklyn.entity.software.MachineLifecycleEffectorTasks; +import brooklyn.entity.software.SshEffectorTasks; +import brooklyn.location.MachineLocation; +import brooklyn.util.net.Urls; +import brooklyn.util.ssh.BashCommands; +import brooklyn.util.task.DynamicTasks; +import brooklyn.util.task.Tasks; +import brooklyn.util.time.Duration; +import brooklyn.util.time.Time; + +import com.google.common.annotations.Beta; +import com.google.common.base.Supplier; + +/** + * Creates effectors to start, restart, and stop processes using SaltStack. + * <p> + * Instances of this should use the {@link SaltConfig} config attributes to configure startup, + * and invoke {@link #usePidFile(String)} or {@link #useService(String)} to determine check-running and stop behaviour. + * Alternatively this can be subclassed and {@link #postStartCustom()} and {@link #stopProcessesAtMachine()} overridden. + * + * @since 0.6.0 + */ +@Beta +public class SaltLifecycleEffectorTasks extends MachineLifecycleEffectorTasks implements SaltConfig { + + private static final Logger log = LoggerFactory.getLogger(SaltLifecycleEffectorTasks.class); + + protected SaltStackMaster master = null; + protected String pidFile, serviceName, windowsServiceName; + + public SaltLifecycleEffectorTasks() { + } + + public SaltLifecycleEffectorTasks usePidFile(String pidFile) { + this.pidFile = pidFile; + return this; + } + public SaltLifecycleEffectorTasks useService(String serviceName) { + this.serviceName = serviceName; + return this; + } + public SaltLifecycleEffectorTasks useWindowsService(String serviceName) { + this.windowsServiceName = serviceName; + return this; + } + public SaltLifecycleEffectorTasks master(SaltStackMaster master) { + this.master = master; + return this; + } + + @Override + public void attachLifecycleEffectors(Entity entity) { + if (pidFile==null && serviceName==null && getClass().equals(SaltLifecycleEffectorTasks.class)) { + // warn on incorrect usage + log.warn("Uses of "+getClass()+" must define a PID file or a service name (or subclass and override {start,stop} methods as per javadoc) " + + "in order for check-running and stop to work"); + } + + super.attachLifecycleEffectors(entity); + } + + @Override + protected String startProcessesAtMachine(Supplier<MachineLocation> machineS) { + startMinionAsync(); + return "salt start tasks submitted"; + } + + protected void startMinionAsync() { + // TODO make directories more configurable (both for ssh-drivers and for this) + String installDir = Urls.mergePaths(BrooklynServerConfig.getMgmtBaseDir(entity().getManagementContext()), "salt-install"); + String runDir = Urls.mergePaths(BrooklynServerConfig.getMgmtBaseDir(entity().getManagementContext()), + "apps/"+entity().getApplicationId()+"/salt-entities/"+entity().getId()); + + Boolean masterless = entity().getConfig(SaltConfig.MASTERLESS_MODE); + if (masterless) { + DynamicTasks.queue( + SaltTasks.installFormulas(installDir, SaltConfigs.getRequiredConfig(entity(), SALT_FORMULAS), false), + SaltTasks.buildSaltFile(runDir, + SaltConfigs.getRequiredConfig(entity(), SALT_RUN_LIST), + entity().getConfig(SALT_LAUNCH_ATTRIBUTES)), + SaltTasks.installSaltMinion(entity(), runDir, installDir, false), + SaltTasks.runSalt(runDir)); + } else { + throw new UnsupportedOperationException("Salt master mode not yet supported for minions"); + } + } + + @Override + protected void postStartCustom() { + boolean result = false; + result |= tryCheckStartPid(); + result |= tryCheckStartService(); + result |= tryCheckStartWindowsService(); + if (!result) { + throw new IllegalStateException("The process for "+entity()+" appears not to be running (no way to check!)"); + } + } + + protected boolean tryCheckStartPid() { + if (pidFile==null) return false; + + // if it's still up after 5s assume we are good (default behaviour) + Time.sleep(Duration.FIVE_SECONDS); + if (!DynamicTasks.queue(SshEffectorTasks.isPidFromFileRunning(pidFile).runAsRoot()).get()) { + throw new IllegalStateException("The process for "+entity()+" appears not to be running (pid file "+pidFile+")"); + } + + // and set the PID + entity().setAttribute(Attributes.PID, + Integer.parseInt(DynamicTasks.queue(SshEffectorTasks.ssh("cat "+pidFile).runAsRoot()).block().getStdout().trim())); + return true; + } + + protected boolean tryCheckStartService() { + if (serviceName==null) return false; + + // if it's still up after 5s assume we are good (default behaviour) + Time.sleep(Duration.FIVE_SECONDS); + if (!((Integer)0).equals(DynamicTasks.queue(SshEffectorTasks.ssh("/etc/init.d/"+serviceName+" status").runAsRoot()).get())) { + throw new IllegalStateException("The process for "+entity()+" appears not to be running (service "+serviceName+")"); + } + + return true; + } + + protected boolean tryCheckStartWindowsService() { + if (windowsServiceName==null) return false; + + // if it's still up after 5s assume we are good (default behaviour) + Time.sleep(Duration.FIVE_SECONDS); + if (!((Integer)0).equals(DynamicTasks.queue(SshEffectorTasks.ssh("sc query \""+serviceName+"\" | find \"RUNNING\"").runAsCommand()).get())) { + throw new IllegalStateException("The process for "+entity()+" appears not to be running (windowsService "+windowsServiceName+")"); + } + + return true; + } + + @Override + protected String stopProcessesAtMachine() { + boolean result = false; + result |= tryStopService(); + result |= tryStopPid(); + if (!result) { + throw new IllegalStateException("The process for "+entity()+" appears could not be stopped (no impl!)"); + } + return "stopped"; + } + + protected boolean tryStopService() { + if (serviceName==null) return false; + int result = DynamicTasks.queue(SshEffectorTasks.ssh("/etc/init.d/"+serviceName+" stop").runAsRoot()).get(); + if (0==result) return true; + if (entity().getAttribute(Attributes.SERVICE_STATE)!=Lifecycle.RUNNING) + return true; + + throw new IllegalStateException("The process for "+entity()+" appears could not be stopped (exit code "+result+" to service stop)"); + } + + protected boolean tryStopPid() { + Integer pid = entity().getAttribute(Attributes.PID); + if (pid==null) { + if (entity().getAttribute(Attributes.SERVICE_STATE)==Lifecycle.RUNNING && pidFile==null) + log.warn("No PID recorded for "+entity()+" when running, with PID file "+pidFile+"; skipping kill in "+Tasks.current()); + else + if (log.isDebugEnabled()) + log.debug("No PID recorded for "+entity()+"; skipping ("+entity().getAttribute(Attributes.SERVICE_STATE)+" / "+pidFile+")"); + return false; + } + + // allow non-zero exit as process may have already been killed + DynamicTasks.queue(SshEffectorTasks.ssh( + "kill "+pid, "sleep 5", BashCommands.ok("kill -9 "+pid)).allowingNonZeroExitCode().runAsRoot()).block(); + + if (DynamicTasks.queue(SshEffectorTasks.isPidRunning(pid).runAsRoot()).get()) { + throw new IllegalStateException("Process for "+entity()+" in "+pid+" still running after kill"); + } + entity().setAttribute(Attributes.PID, null); + return true; + } + + /** + * {@inheritDoc} + * + * @return the Salt master entity if it exists. + * @see #master(SaltStackMaster) + * @see SaltConfig#MASTERLESS_MODE + */ + @Override + public SaltStackMaster getMaster() { + return master; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMaster.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMaster.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMaster.java new file mode 100644 index 0000000..8ce2e3b --- /dev/null +++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMaster.java @@ -0,0 +1,70 @@ +/* + * 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.salt; + +import java.util.List; + +import org.apache.brooklyn.catalog.Catalog; +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.BrooklynConfigKeys; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.proxying.ImplementedBy; +import brooklyn.event.AttributeSensor; +import brooklyn.event.basic.BasicAttributeSensor; +import brooklyn.event.basic.PortAttributeSensorAndConfigKey; +import brooklyn.util.flags.SetFromFlag; + +import com.google.common.reflect.TypeToken; + +@ImplementedBy(SaltStackMasterImpl.class) +@Catalog(name="SaltStack Master", description="The Salt master server") +public interface SaltStackMaster extends SoftwareProcess { + + @SetFromFlag("version") + ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(BrooklynConfigKeys.SUGGESTED_VERSION, "stable"); + + @SetFromFlag("bootstrapUrl") + ConfigKey<String> BOOTSTRAP_URL = ConfigKeys.newStringConfigKey( + "salt.bootstrap.url", "The URL that returns the Salt boostrap commands", + "http://bootstrap.saltstack.org/"); + + @SetFromFlag("masterUser") + ConfigKey<String> MASTER_USER = ConfigKeys.newStringConfigKey( + "salt.master.user", "The user that runs the Salt master daemon process", + "root"); + + @SetFromFlag("masterConfigTemplate") + ConfigKey<String> MASTER_CONFIG_TEMPLATE_URL = ConfigKeys.newStringConfigKey( + "salt.master.config.templateUrl", "The template for the Salt master configuration (URL)", + "classpath://org/apache/brooklyn/entity/salt/master"); + + @SetFromFlag("saltPort") + PortAttributeSensorAndConfigKey SALT_PORT = new PortAttributeSensorAndConfigKey( + "salt.port", "Port used for communication between Salt master and minion processes", "4506+"); + + @SetFromFlag("publishPort") + PortAttributeSensorAndConfigKey PUBLISH_PORT = new PortAttributeSensorAndConfigKey( + "salt.publish.port", "Port used by the Salt master publisher", "4505+"); + + @SuppressWarnings("serial") + AttributeSensor<List<String>> MINION_IDS = new BasicAttributeSensor<List<String>>(new TypeToken<List<String>>() {}, + "salt.minions", "List of Salt minion IDs"); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterDriver.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterDriver.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterDriver.java new file mode 100644 index 0000000..703213a --- /dev/null +++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterDriver.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.salt; + +import brooklyn.entity.basic.SoftwareProcessDriver; + +public interface SaltStackMasterDriver extends SoftwareProcessDriver { + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterImpl.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterImpl.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterImpl.java new file mode 100644 index 0000000..1adfd1a --- /dev/null +++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterImpl.java @@ -0,0 +1,56 @@ +/* + * 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.salt; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.SoftwareProcessImpl; +import brooklyn.event.feed.ConfigToAttributes; + +public class SaltStackMasterImpl extends SoftwareProcessImpl implements SaltStackMaster { + + private static final Logger log = LoggerFactory.getLogger(SaltStackMasterImpl.class); + + public SaltStackMasterImpl() { + super(); + } + + @Override + public Class getDriverInterface() { + return SaltStackMasterDriver.class; + } + + @Override + protected void connectSensors() { + super.connectSensors(); + + // TODO what sensors should we poll? + ConfigToAttributes.apply(this); + + connectServiceUpIsRunning(); + } + + @Override + protected void disconnectSensors() { + disconnectServiceUpIsRunning(); + + super.disconnectSensors(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterSshDriver.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterSshDriver.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterSshDriver.java new file mode 100644 index 0000000..4b88acc --- /dev/null +++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterSshDriver.java @@ -0,0 +1,95 @@ +/* + * 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.salt; + +import brooklyn.entity.basic.Entities; +import brooklyn.entity.java.JavaSoftwareProcessSshDriver; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.util.ssh.BashCommands; +import brooklyn.util.task.DynamicTasks; + +import com.google.common.collect.ImmutableMap; + +public class SaltStackMasterSshDriver extends JavaSoftwareProcessSshDriver implements SaltStackMasterDriver { + + public SaltStackMasterSshDriver(SaltStackMasterImpl entity, SshMachineLocation machine) { + super(entity, machine); + } + + @Override + public SaltStackMasterImpl getEntity() { + return (SaltStackMasterImpl) super.getEntity(); + } + + @Override + protected String getLogFileLocation() { + return "master.log"; + } + + private String getPidFile() { + return "master.pid"; + } + + @Override + public void install() { + String url = Entities.getRequiredUrlConfig(getEntity(), SaltStackMaster.BOOTSTRAP_URL); + copyTemplate(url, "/etc/salt/master"); + + // Copy the file contents to the remote machine +// DynamicTasks.queue(SshEffectorTasks.put("/tmp/cumulus.yaml").contents(contents)).get(); + + // Run Salt bootstrap task to install master + DynamicTasks.queue(SaltTasks.installSaltMaster(getEntity(), getRunDir(), true)); + + + newScript("createInstallDir") + .body.append("mkdir -p "+getInstallDir()) + .failOnNonZeroResultCode() + .execute(); + + newScript(INSTALLING). + failOnNonZeroResultCode(). + body.append("").execute(); + } + + @Override + public void customize() { + } + + @Override + public void launch() { + newScript(ImmutableMap.of("usePidFile", false), LAUNCHING) + .body.append(BashCommands.sudo("start salt-master")) + .execute(); + } + + @Override + public boolean isRunning() { + return newScript(ImmutableMap.of("usePidFile", false), CHECK_RUNNING) + .body.append(BashCommands.sudo("status salt-master")) + .execute() == 0; + } + + @Override + public void stop() { + newScript(ImmutableMap.of("usePidFile", false), STOPPING) + .body.append(BashCommands.sudo("stop salt-master")) + .execute(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltTasks.java ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltTasks.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltTasks.java new file mode 100644 index 0000000..5cec099 --- /dev/null +++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltTasks.java @@ -0,0 +1,145 @@ +/* + * 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.salt; + +import static brooklyn.util.ssh.BashCommands.INSTALL_CURL; +import static brooklyn.util.ssh.BashCommands.INSTALL_TAR; +import static brooklyn.util.ssh.BashCommands.INSTALL_UNZIP; +import static brooklyn.util.ssh.BashCommands.downloadToStdout; +import static brooklyn.util.ssh.BashCommands.sudo; + +import java.util.Map; + +import brooklyn.entity.Entity; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.effector.EffectorTasks; +import brooklyn.entity.software.SshEffectorTasks; +import brooklyn.management.TaskFactory; +import brooklyn.util.ResourceUtils; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.net.Urls; +import brooklyn.util.ssh.BashCommands; +import brooklyn.util.task.DynamicTasks; +import brooklyn.util.task.Tasks; +import brooklyn.util.text.TemplateProcessor; + +import com.google.common.annotations.Beta; + +@Beta +public class SaltTasks { + + public static TaskFactory<?> installSaltMaster(Entity master, String saltDirectory, boolean force) { + // TODO check on entity whether it is salt _server_ + String boostrapUrl = master.getConfig(SaltStackMaster.BOOTSTRAP_URL); + String version = master.getConfig(SaltStackMaster.SUGGESTED_VERSION); + String installCmd = cdAndRun(saltDirectory, + BashCommands.chain( + INSTALL_CURL, + INSTALL_TAR, + INSTALL_UNZIP, + "( "+downloadToStdout(boostrapUrl) + " | " + sudo("sh -s -- -M -N "+version)+" )")); + if (!force) installCmd = BashCommands.alternatives("which salt-master", installCmd); + return SshEffectorTasks.ssh(installCmd).summary("install salt master"); + } + + public static TaskFactory<?> installSaltMinion(final Entity minion, final String runDir, final String installDir, final boolean force) { + return Tasks.<Void>builder().name("install minion").body( + new Runnable() { + public void run() { + // Setup bootstrap installation command for minion + String boostrapUrl = minion.getConfig(SaltStackMaster.BOOTSTRAP_URL); + String installCmd = cdAndRun(runDir, BashCommands.chain( + INSTALL_CURL, + INSTALL_TAR, + INSTALL_UNZIP, + "( "+downloadToStdout(boostrapUrl) + " | " + sudo("sh")+" )")); + if (!force) installCmd = BashCommands.alternatives("which salt-minion", installCmd); + + // Process the minion configuration template + Boolean masterless = minion.getConfig(SaltConfig.MASTERLESS_MODE); + String url = masterless ? Entities.getRequiredUrlConfig(minion, SaltConfig.MASTERLESS_CONFIGURATION_URL) + : Entities.getRequiredUrlConfig(minion, SaltConfig.MINION_CONFIGURATION_URL); + Map<String, Object> config = MutableMap.<String, Object>builder() + .put("entity", minion) + .put("runDir", runDir) + .put("installDir", installDir) + .put("formulas", minion.getConfig(SaltConfig.SALT_FORMULAS)) + .build(); + String contents = TemplateProcessor.processTemplateContents(new ResourceUtils(minion).getResourceAsString(url), config); + + // Copy the file contents to the remote machine and install/start salt-minion + DynamicTasks.queue( + SshEffectorTasks.ssh(installCmd), + SshEffectorTasks.put("/tmp/minion") + .contents(contents) + .createDirectory(), + SshEffectorTasks.ssh(sudo("mv /tmp/minion /etc/salt/minion")), // TODO clunky + SshEffectorTasks.ssh(sudo("restart salt-minion")) + ); + } + }).buildFactory(); + } + + public static TaskFactory<?> installFormulas(final String installDir, final Map<String,String> formulasAndUrls, final boolean force) { + return Tasks.<Void>builder().name("install formulas").body( + new Runnable() { + public void run() { + Entity e = EffectorTasks.findEntity(); + if (formulasAndUrls==null) + throw new IllegalStateException("No formulas defined to install at "+e); + for (String formula: formulasAndUrls.keySet()) + DynamicTasks.queue(installFormula(installDir, formula, formulasAndUrls.get(formula), force)); + } + }).buildFactory(); + } + + public static TaskFactory<?> installFormula(String installDir, String formula, String url, boolean force) { + return SshEffectorTasks.ssh(cdAndRun(installDir, SaltBashCommands.downloadAndExpandFormula(url, formula, force))) + .summary("install formula "+formula) + .requiringExitCodeZero(); + } + + protected static String cdAndRun(String targetDirectory, String command) { + return BashCommands.chain( + "mkdir -p "+targetDirectory, + "cd "+targetDirectory, + command); + } + + public static TaskFactory<?> buildSaltFile(String runDir, Iterable<? extends String> runList, Map<String, Object> attributes) { + StringBuilder top = new StringBuilder() + .append("base:\n") + .append(" '*':\n"); + for (String run : runList) { + top.append(" - " + run + "\n"); + } + + return SshEffectorTasks.put(Urls.mergePaths(runDir, "base", "top.sls")) + .contents(top.toString()) + .summary("build salt top file") + .createDirectory(); + } + + public static TaskFactory<?> runSalt(String runDir) { + return SshEffectorTasks.ssh(cdAndRun(runDir, BashCommands.sudo("salt-call state.highstate"))) + .summary("run salt install") + .requiringExitCodeZero(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/resources/brooklyn/entity/salt/master ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/resources/brooklyn/entity/salt/master b/sandbox/extra/src/main/resources/brooklyn/entity/salt/master deleted file mode 100644 index 72d7eb9..0000000 --- a/sandbox/extra/src/main/resources/brooklyn/entity/salt/master +++ /dev/null @@ -1,65 +0,0 @@ -[#ftl] -# -# 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. -# -#Â /etc/salt/master -## - -#interface: 0.0.0.0 -#ipv6: False -#publish_port: ${entity.publishPort,c} # 4505 - -#user: root -#max_open_files: 100000 -#worker_threads: 5 -ret_port: ${entity.saltPort,c} # 4506 - -root_dir: / -pidfile: ${driver.pidFile} -pki_dir: ${runDir}/pki -cachedir: ${runDir}/cache -log_file: ${driver.logFileLocation} -key_logfile: ${runDir}/key.log - -#verify_env: True -#keep_jobs: 24 - -#timeout: 5 -#loop_interval: 60 - -output: nested -color: False -log_level: info -log_level_logfile: debug # Debugging - -#job_cache: True -#minion_data_cache: True - -#open_mode: False -#auto_accept: False -#autosign_file: autosign.conf -#permissive_pki_access: False - -fileserver_backend: - - git - -gitfs_remotes: - - git://github.com/saltstack/salt-states.git - - git://github.com/saltstack-formulas/postgres-formula.git - - ${entity.remoteUrl} - # TODO iterate through formula URLs http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/resources/brooklyn/entity/salt/masterless ---------------------------------------------------------------------- diff --git a/sandbox/extra/src/main/resources/brooklyn/entity/salt/masterless b/sandbox/extra/src/main/resources/brooklyn/entity/salt/masterless deleted file mode 100644 index ba41c6e..0000000 --- a/sandbox/extra/src/main/resources/brooklyn/entity/salt/masterless +++ /dev/null @@ -1,53 +0,0 @@ -[#ftl] -## -# -# 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. -# -# SaltStack Masterless Minion Configuration -# -#Â /etc/salt/minion -## - -# Minion configuration -id: ${entity.id} -user: root -backup_mode: minion - -# Directory settings -root_dir: / -pidfile: ${runDir}/salt-minion.pid -pki_dir: ${runDir}/pki -cachedir: ${runDir}/cache -log_file: ${runDir}/minion.log -key_logfile: ${runDir}/key.log - -file_client: local -file_roots: - base: - - ${runDir}/base -[#list formulas?keys as formulaName] - - ${installDir}/${formulaName} -[/#list] - -#verify_env: True -#cache_jobs: True # Debugging - -output: nested -color: False -log_level: info -log_level_logfile: debug # Debugging
