| /* |
| * 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()); |
| } |
| } |