Repository: brooklyn-library Updated Branches: refs/heads/master 616ae7598 -> e6eb89a1a
Convert AnsibleEntity to a SoftwareProcess entity Project: http://git-wip-us.apache.org/repos/asf/brooklyn-library/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-library/commit/38a9f780 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-library/tree/38a9f780 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-library/diff/38a9f780 Branch: refs/heads/master Commit: 38a9f780299678d706a690fcca8a2de28a4d0ed7 Parents: 2b7d2ba Author: Thomas Bouron <[email protected]> Authored: Tue Aug 29 16:05:58 2017 +0100 Committer: Thomas Bouron <[email protected]> Committed: Tue Aug 29 16:05:58 2017 +0100 ---------------------------------------------------------------------- .../entity/cm/ansible/AnsibleEntityDriver.java | 28 +++ .../entity/cm/ansible/AnsibleEntityImpl.java | 64 +++-- .../cm/ansible/AnsibleEntitySshDriver.java | 123 ++++++++++ .../ansible/AnsibleLifecycleEffectorTasks.java | 231 ------------------- 4 files changed, 199 insertions(+), 247 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/38a9f780/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntityDriver.java ---------------------------------------------------------------------- diff --git a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntityDriver.java b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntityDriver.java new file mode 100644 index 0000000..01ae9cf --- /dev/null +++ b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntityDriver.java @@ -0,0 +1,28 @@ +/* + * 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.cm.ansible; + +import org.apache.brooklyn.entity.software.base.SoftwareProcessDriver; +import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; + +public interface AnsibleEntityDriver extends SoftwareProcessDriver { + String getStatusCmd(); + ProcessTaskWrapper<Integer> ansibleCommand(String module, String args); +} http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/38a9f780/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntityImpl.java ---------------------------------------------------------------------- diff --git a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntityImpl.java b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntityImpl.java index a762721..21359d0 100644 --- a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntityImpl.java +++ b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntityImpl.java @@ -18,28 +18,59 @@ */ package org.apache.brooklyn.entity.cm.ansible; -import org.apache.brooklyn.entity.stock.EffectorStartableImpl; -import org.apache.brooklyn.util.core.task.DynamicTasks; +import org.apache.brooklyn.core.location.Locations; +import org.apache.brooklyn.entity.software.base.SoftwareProcessImpl; +import org.apache.brooklyn.feed.ssh.SshFeed; +import org.apache.brooklyn.feed.ssh.SshPollConfig; +import org.apache.brooklyn.location.ssh.SshMachineLocation; import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; -import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.guava.Maybe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import static com.google.common.base.Preconditions.checkNotNull; +public class AnsibleEntityImpl extends SoftwareProcessImpl implements AnsibleEntity { -public class AnsibleEntityImpl extends EffectorStartableImpl implements AnsibleEntity { + private static final Logger LOG = LoggerFactory.getLogger(AnsibleEntityImpl.class); - private AnsibleLifecycleEffectorTasks lifecycleTasks; + private SshFeed feed; @Override - public void init() { - checkNotNull(getConfig(SERVICE_NAME), "service name is missing. it has to be provided by the user"); - String playbookName = getConfig(ANSIBLE_PLAYBOOK); - if (!Strings.isBlank(playbookName)) setDefaultDisplayName(playbookName + " (ansible)"); + public Class getDriverInterface() { + return AnsibleEntityDriver.class; + } + + @Override + public AnsibleEntityDriver getDriver() { + return (AnsibleEntityDriver) super.getDriver(); + } + + @Override + protected void connectSensors() { + super.connectSensors(); - super.init(); + Maybe<SshMachineLocation> machine = Locations.findUniqueSshMachineLocation(getLocations()); - lifecycleTasks = new AnsibleLifecycleEffectorTasks(); + if (machine.isPresent()) { + String cmd = getDriver().getStatusCmd(); + feed = SshFeed.builder() + .entity(this) + .period(config().get(SERVICE_PROCESS_IS_RUNNING_POLL_PERIOD)) + .machine(machine.get()) + .poll(new SshPollConfig<Boolean>(SERVICE_UP) + .command(cmd) + .setOnSuccess(true) + .setOnFailureOrException(false)) + .build(); + } else { + LOG.warn("Location(s) {} not an ssh-machine location, so not polling for status; setting serviceUp immediately", getLocations()); + sensors().set(SERVICE_UP, true); + } + } - lifecycleTasks.attachLifecycleEffectors(this); + @Override + protected void disconnectSensors() { + if (feed != null) feed.stop(); + super.disconnectSensors(); } @Override @@ -49,14 +80,15 @@ public class AnsibleEntityImpl extends EffectorStartableImpl implements AnsibleE @Override public String ansibleCommand(String module, String args) { - final ProcessTaskWrapper<Integer> command = DynamicTasks.queue( - AnsiblePlaybookTasks.moduleCommand(module, config().get(ANSIBLE_VARS), lifecycleTasks.getRunDir(), args)); + final ProcessTaskWrapper<Integer> command = getDriver().ansibleCommand(module, args); + command.asTask().blockUntilEnded(); + if (0 == command.getExitCode()) { return command.getStdout(); } else { throw new RuntimeException("Command (" + args + ") in module " + module - + " failed with stderr:\n" + command.getStderr() + "\n"); + + " failed with stderr:\n" + command.getStderr() + "\n"); } } } http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/38a9f780/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntitySshDriver.java ---------------------------------------------------------------------- diff --git a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntitySshDriver.java b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntitySshDriver.java new file mode 100644 index 0000000..8846670 --- /dev/null +++ b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleEntitySshDriver.java @@ -0,0 +1,123 @@ +/* + * 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.cm.ansible; + +import static org.apache.brooklyn.util.ssh.BashCommands.sudo; + +import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver; +import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.task.DynamicTasks; +import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; +import org.apache.brooklyn.util.text.Strings; + +public class AnsibleEntitySshDriver extends AbstractSoftwareProcessSshDriver implements AnsibleEntityDriver { + public AnsibleEntitySshDriver(AnsibleEntityImpl entity, SshMachineLocation machine) { + super(entity, machine); + } + + @Override + public boolean isRunning() { + return newScript(MutableMap.of("usePidFile", false), CHECK_RUNNING) + .body.append(getStatusCmd()) + .execute() == 0; + } + + @Override + public void stop() { + final String serviceName = getEntity().config().get(AnsibleConfig.SERVICE_NAME); + + newScript(MutableMap.of("usePidFile", false), STOPPING) + .updateTaskAndFailOnNonZeroResultCode() + .body.append(sudo(String.format(getEntity().config().get(AnsibleConfig.ANSIBLE_SERVICE_STOP), serviceName))) + .execute(); + } + + @Override + public void install() { + Object extraVars = getEntity().config().get(AnsibleConfig.ANSIBLE_VARS); + String playbookName = getEntity().config().get(AnsibleConfig.ANSIBLE_PLAYBOOK); + String playbookUrl = getEntity().config().get(AnsibleConfig.ANSIBLE_PLAYBOOK_URL); + String playbookYaml = getEntity().config().get(AnsibleConfig.ANSIBLE_PLAYBOOK_YAML); + + if (playbookUrl != null && playbookYaml != null) { + throw new IllegalArgumentException( "You can not specify both "+ AnsibleConfig.ANSIBLE_PLAYBOOK_URL.getName() + + " and " + AnsibleConfig.ANSIBLE_PLAYBOOK_YAML.getName() + " as arguments."); + } + + if (playbookUrl == null && playbookYaml == null) { + throw new IllegalArgumentException("You have to specify either " + AnsibleConfig.ANSIBLE_PLAYBOOK_URL.getName() + + " or " + AnsibleConfig.ANSIBLE_PLAYBOOK_YAML.getName() + " as arguments."); + } + + DynamicTasks.queue(AnsiblePlaybookTasks.installAnsible(getInstallDir(), false)); + DynamicTasks.queue(AnsiblePlaybookTasks.setUpHostsFile(false)); + + if (extraVars != null) { + DynamicTasks.queue(AnsiblePlaybookTasks.configureExtraVars(getRunDir(), extraVars, false)); + } + + if (Strings.isNonBlank(playbookUrl)) { + DynamicTasks.queue(AnsiblePlaybookTasks.installPlaybook(getRunDir(), playbookName, playbookUrl)); + } + + if (Strings.isNonBlank(playbookYaml)) { + DynamicTasks.queue(AnsiblePlaybookTasks.buildPlaybookFile(getRunDir(), playbookName)); + } + DynamicTasks.queue(AnsiblePlaybookTasks.runAnsible(getRunDir(), extraVars, playbookName)); + } + + @Override + public void customize() { + newScript(CUSTOMIZING).execute(); + } + + @Override + public void launch() { + final String serviceName = getEntity().config().get(AnsibleConfig.SERVICE_NAME); + + newScript(MutableMap.of("usePidFile", false), LAUNCHING) + .updateTaskAndFailOnNonZeroResultCode() + .body.append(sudo(String.format(getEntity().config().get(AnsibleConfig.ANSIBLE_SERVICE_START), serviceName))) + .execute(); + } + + @Override + public String getStatusCmd() { + String serviceNameCheck = getEntity().config().get(AnsibleConfig.SERVICE_NAME).replaceFirst("^(.)(.*)", "[$1]$2"); + String statusCmd = String.format("ps -ef | grep %s", serviceNameCheck); + + Integer serviceCheckPort = getEntity().config().get(AnsibleConfig.ANSIBLE_SERVICE_CHECK_PORT); + + if (serviceCheckPort != null) { + statusCmd = sudo(String.format("ansible localhost -c local -m wait_for -a \"host=" + + getEntity().config().get(AnsibleConfig.ANSIBLE_SERVICE_CHECK_HOST) + + "\" port=%d\"", serviceCheckPort)); + } + + return statusCmd; + } + + @Override + public ProcessTaskWrapper<Integer> ansibleCommand(String module, String args) { + return DynamicTasks.queue( + AnsiblePlaybookTasks.moduleCommand(module, getEntity().config().get(AnsibleConfig.ANSIBLE_VARS), getRunDir(), args)); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/38a9f780/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleLifecycleEffectorTasks.java ---------------------------------------------------------------------- diff --git a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleLifecycleEffectorTasks.java b/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleLifecycleEffectorTasks.java deleted file mode 100644 index b2a104a..0000000 --- a/software/cm/ansible/src/main/java/org/apache/brooklyn/entity/cm/ansible/AnsibleLifecycleEffectorTasks.java +++ /dev/null @@ -1,231 +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 org.apache.brooklyn.entity.cm.ansible; - -import static org.apache.brooklyn.util.ssh.BashCommands.sudo; - -import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.api.location.MachineLocation; -import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks; -import org.apache.brooklyn.core.entity.Attributes; -import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; -import org.apache.brooklyn.core.entity.trait.Startable; -import org.apache.brooklyn.core.location.Locations; -import org.apache.brooklyn.core.location.Machines; -import org.apache.brooklyn.entity.software.base.SoftwareProcess; -import org.apache.brooklyn.entity.software.base.lifecycle.MachineLifecycleEffectorTasks; -import org.apache.brooklyn.feed.ssh.SshFeed; -import org.apache.brooklyn.feed.ssh.SshPollConfig; -import org.apache.brooklyn.location.ssh.SshMachineLocation; -import org.apache.brooklyn.util.core.task.DynamicTasks; -import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.net.Urls; -import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.time.Duration; -import org.apache.brooklyn.util.time.Time; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Supplier; - -public class AnsibleLifecycleEffectorTasks extends MachineLifecycleEffectorTasks implements AnsibleConfig { - - private static final Logger LOG = LoggerFactory.getLogger(AnsibleLifecycleEffectorTasks.class); - - protected String serviceName; - protected SshFeed serviceSshFeed; - - protected Object extraVars; - protected String baseDir; - protected String runDir; - - public AnsibleLifecycleEffectorTasks() { - } - - public String getServiceName() { - if (serviceName!=null) return serviceName; - return serviceName = entity().config().get(AnsibleConfig.SERVICE_NAME); - } - - public Object getExtraVars() { - if (extraVars != null) return extraVars; - return extraVars = entity().config().get(ANSIBLE_VARS); - } - - public String getBaseDir() { - if (null != baseDir) return baseDir; - return baseDir = MachineLifecycleEffectorTasks.resolveOnBoxDir(entity(), - Machines.findUniqueMachineLocation(entity().getLocations(), SshMachineLocation.class).get()); - } - - public String getRunDir() { - if (null != runDir) return runDir; - return runDir = Urls.mergePaths(getBaseDir(), "apps/"+entity().getApplicationId()+"/ansible/playbooks/" - +entity().getEntityType().getSimpleName()+"_"+entity().getId()); - } - - @Override - public void attachLifecycleEffectors(Entity entity) { - if (getServiceName()==null && getClass().equals(AnsibleLifecycleEffectorTasks.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) { - startWithAnsibleAsync(); - - return "ansible start tasks submitted"; - } - - protected String getPlaybookName() { - return entity().config().get(ANSIBLE_PLAYBOOK); - } - - protected void startWithAnsibleAsync() { - - String installDir = Urls.mergePaths(getBaseDir(), "installs/ansible"); - - String playbookUrl = entity().config().get(ANSIBLE_PLAYBOOK_URL); - String playbookYaml = entity().config().get(ANSIBLE_PLAYBOOK_YAML); - - if (playbookUrl != null && playbookYaml != null) { - throw new IllegalArgumentException( "You can not specify both "+ AnsibleConfig.ANSIBLE_PLAYBOOK_URL.getName() + - " and " + AnsibleConfig.ANSIBLE_PLAYBOOK_YAML.getName() + " as arguments."); - } - - if (playbookUrl == null && playbookYaml == null) { - throw new IllegalArgumentException("You have to specify either " + AnsibleConfig.ANSIBLE_PLAYBOOK_URL.getName() + - " or " + AnsibleConfig.ANSIBLE_PLAYBOOK_YAML.getName() + " as arguments."); - } - - DynamicTasks.queue(AnsiblePlaybookTasks.installAnsible(installDir, false)); - DynamicTasks.queue(AnsiblePlaybookTasks.setUpHostsFile(false)); - - if (getExtraVars() != null) { - DynamicTasks.queue(AnsiblePlaybookTasks.configureExtraVars(getRunDir(), extraVars, false)); - } - - if (Strings.isNonBlank(playbookUrl)) { - DynamicTasks.queue(AnsiblePlaybookTasks.installPlaybook(getRunDir(), getPlaybookName(), playbookUrl)); - } - - if (Strings.isNonBlank(playbookYaml)) { - DynamicTasks.queue(AnsiblePlaybookTasks.buildPlaybookFile(getRunDir(), getPlaybookName())); - } - DynamicTasks.queue(AnsiblePlaybookTasks.runAnsible(getRunDir(), getExtraVars(), getPlaybookName())); - } - - - @Override - protected void postStartCustom() { - boolean result = false; - result |= tryCheckStartService(); - - if (!result) { - LOG.warn("No way to check whether "+entity()+" is running; assuming yes"); - } - entity().sensors().set(SoftwareProcess.SERVICE_UP, true); - - Maybe<SshMachineLocation> machine = Locations.findUniqueSshMachineLocation(entity().getLocations()); - - if (machine.isPresent()) { - // For example âps -f| grep httpdâ matches for any process including the text âhttpdâ, - // which includes the grep command itself, whereas âps | grep [h]ttpdâ matches only processes - // including the text âhttpdâ (doesnât include the grep) and additionally - // provides a correct return code - // - // The command constructed bellow will look like - ps -ef |grep [h]ttpd - String serviceNameCheck = getServiceName().replaceFirst("^(.)(.*)", "[$1]$2"); - String checkCmd = String.format("ps -ef | grep %s", serviceNameCheck); - - Integer serviceCheckPort = entity().config().get(ANSIBLE_SERVICE_CHECK_PORT); - - if (serviceCheckPort != null) { - checkCmd = sudo(String.format("ansible localhost -c local -m wait_for -a \"host=" + - entity().config().get(ANSIBLE_SERVICE_CHECK_HOST) + - "\" port=%d\"", serviceCheckPort)); - } - serviceSshFeed = SshFeed.builder() - .entity(entity()) - .period(Duration.ONE_MINUTE) - .machine(machine.get()) - .poll(new SshPollConfig<Boolean>(Startable.SERVICE_UP) - .command(checkCmd) - .setOnSuccess(true) - .setOnFailureOrException(false)) - .build(); - - entity().feeds().add(serviceSshFeed); - } else { - LOG.warn("Location(s) {} not an ssh-machine location, so not polling for status; " - + "setting serviceUp immediately", entity().getLocations()); - } - super.postStartCustom(); - } - - protected boolean tryCheckStartService() { - if (getServiceName()==null) return false; - - // if it's still up after 5s assume we are good (default behaviour) - Time.sleep(Duration.FIVE_SECONDS); - int result = DynamicTasks.queue(SshEffectorTasks.ssh(sudo(getServiveStartCommand()))).get(); - if (0 != result) { - throw new IllegalStateException("The process for "+entity()+" appears not to be running (service "+getServiceName()+")"); - } - - return true; - } - - @Override - protected String stopProcessesAtMachine() { - boolean result = false; - result |= tryStopService(); - if (!result) { - throw new IllegalStateException("The process for "+entity()+" could not be stopped (no impl!)"); - } - return "stopped"; - } - - @Override - protected StopMachineDetails<Integer> stopAnyProvisionedMachines() { - return super.stopAnyProvisionedMachines(); - } - - protected boolean tryStopService() { - if (getServiceName()==null) return false; - int result = DynamicTasks.queue(SshEffectorTasks.ssh(sudo(getServiveStopCommand()))).get(); - if (0 == result) return true; - if (entity().getAttribute(Attributes.SERVICE_STATE_ACTUAL) != Lifecycle.RUNNING) - return true; - - throw new IllegalStateException("The process for "+entity()+" appears could not be stopped (exit code "+result+" to service stop)"); - } - - private String getServiveStartCommand() { - return String.format(entity().config().get(AnsibleConfig.ANSIBLE_SERVICE_START), getServiceName()); - } - - private String getServiveStopCommand() { - return String.format(entity().config().get(AnsibleConfig.ANSIBLE_SERVICE_STOP), getServiceName()); - } -}
