blob: b2a104a31633d6afe976eb98e42d9262688f6b5f [file] [log] [blame]
/*
* 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());
}
}