Repository: incubator-brooklyn Updated Branches: refs/heads/master 63c03b15e -> 1f1491740
System Service enricher Installs the entity being enriched as a system service. Saves the launch script and uses it to start the entity. Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/ac42ffc1 Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/ac42ffc1 Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/ac42ffc1 Branch: refs/heads/master Commit: ac42ffc1dbff8fce6f466fcde3d9a24ccfc4c116 Parents: 0ff4216 Author: Svetoslav Neykov <[email protected]> Authored: Tue Mar 17 20:08:49 2015 +0200 Committer: Svetoslav Neykov <[email protected]> Committed: Tue Mar 31 14:33:04 2015 +0300 ---------------------------------------------------------------------- .../entity/service/EntityLaunchListener.java | 111 ++++++++++++++ .../entity/service/InitdServiceInstaller.java | 135 ++++++++++++++++ .../entity/service/SystemServiceEnricher.java | 152 +++++++++++++++++++ .../entity/service/SystemServiceInstaller.java | 25 +++ .../service/SystemServiceInstallerFactory.java | 28 ++++ .../brooklyn/entity/service/service.sh | 51 +++++++ 6 files changed, 502 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac42ffc1/software/base/src/main/java/brooklyn/entity/service/EntityLaunchListener.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/brooklyn/entity/service/EntityLaunchListener.java b/software/base/src/main/java/brooklyn/entity/service/EntityLaunchListener.java new file mode 100644 index 0000000..a374ffe --- /dev/null +++ b/software/base/src/main/java/brooklyn/entity/service/EntityLaunchListener.java @@ -0,0 +1,111 @@ +/* + * 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.service; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import brooklyn.entity.Entity; +import brooklyn.entity.basic.BrooklynTaskTags; +import brooklyn.entity.basic.BrooklynTaskTags.EffectorCallTag; +import brooklyn.entity.basic.Lifecycle; +import brooklyn.event.SensorEvent; +import brooklyn.event.SensorEventListener; +import brooklyn.management.ExecutionManager; +import brooklyn.management.Task; +import brooklyn.util.task.Tasks; + +public class EntityLaunchListener implements Runnable, SensorEventListener<Lifecycle> { + private static final String SSH_LAUNCH_TASK_PREFIX = "ssh: launching"; + private static final String LAUNCH_CHECK_SKIP_TAG = "system-service-update"; + + private final AtomicReference<Task<?>> launchTaskRef = new AtomicReference<Task<?>>(); + private final SystemServiceEnricher enricher; + + public EntityLaunchListener(SystemServiceEnricher enricher) { + this.enricher = checkNotNull(enricher, "enricher"); + } + + @Override + public void onEvent(SensorEvent<Lifecycle> event) { + if (event.getValue() == Lifecycle.RUNNING) { + Task<?>launchTask = getLatestLaunchTask(enricher.getEntity()); + if (launchTask != null) { + launchTaskRef.set(launchTask); + if (!launchTask.isDone()) { + launchTask.addListener(this, enricher.getEntityExecutionContext()); + } + if (launchTask.isDone()) { + run(); + } + } + } + } + + @Override + public void run() { + Task<?> launchTask = launchTaskRef.getAndSet(null); + if (launchTask == null) return; + if (launchTask.isError()) return; + enricher.onLaunched(launchTask); + } + + private Task<?> getLatestLaunchTask(Entity entity) { + Task<?> startEffector = null; + ExecutionManager executionmgr = enricher.getManagementContext().getExecutionManager(); + Set<Task<?>> entityTasks = BrooklynTaskTags.getTasksInEntityContext(executionmgr, entity); + for (Task<?> t : entityTasks) { + if (BrooklynTaskTags.isEffectorTask(t)) { + EffectorCallTag effectorTag = BrooklynTaskTags.getEffectorCallTag(t, false); + if (SystemServiceEnricher.LAUNCH_EFFECTOR_NAMES.contains(effectorTag.getEffectorName()) && + !BrooklynTaskTags.hasTag(t, LAUNCH_CHECK_SKIP_TAG)) { + if (startEffector == null || startEffector.getStartTimeUtc() < t.getStartTimeUtc()) { + startEffector = t; + } + BrooklynTaskTags.addTagDynamically(t, LAUNCH_CHECK_SKIP_TAG); + } + } + } + if (startEffector != null) { + Task<?> launchTask = findSshLaunchChild(startEffector); + if (launchTask != null) { + return launchTask; + } + } + return null; + } + + private Task<?> findSshLaunchChild(Task<?> t) { + Iterable<Task<?>> children = Tasks.children(t); + for (Task<?> c : children) { + if (c.getDisplayName().startsWith(SSH_LAUNCH_TASK_PREFIX)) { + return c; + } + } + for (Task<?> c : children) { + Task<?> launchTask = findSshLaunchChild(c); + if (launchTask != null) { + return launchTask; + } + } + return null; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac42ffc1/software/base/src/main/java/brooklyn/entity/service/InitdServiceInstaller.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/brooklyn/entity/service/InitdServiceInstaller.java b/software/base/src/main/java/brooklyn/entity/service/InitdServiceInstaller.java new file mode 100644 index 0000000..ce52038 --- /dev/null +++ b/software/base/src/main/java/brooklyn/entity/service/InitdServiceInstaller.java @@ -0,0 +1,135 @@ +/* + * 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.service; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.File; +import java.util.Map; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.EntityInternal; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.effector.EffectorTasks; +import brooklyn.entity.trait.HasShortName; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.location.cloud.CloudMachineNamer; +import brooklyn.management.Task; +import brooklyn.policy.Enricher; +import brooklyn.util.ResourceUtils; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.os.Os; +import brooklyn.util.ssh.BashCommands; +import brooklyn.util.task.Tasks; +import brooklyn.util.task.ssh.SshPutTaskWrapper; +import brooklyn.util.task.ssh.SshTasks; +import brooklyn.util.task.system.ProcessTaskWrapper; +import brooklyn.util.text.TemplateProcessor; + + +public class InitdServiceInstaller implements SystemServiceInstaller { + private static final ConfigKey<String> SERVICE_TEMPLATE = ConfigKeys.newStringConfigKey( + "service.initd.service_template", "URL of the template to be used as the /etc/init.d service", "classpath:///brooklyn/entity/service/service.sh"); + + private final Entity entity; + private final Enricher enricher; + + public InitdServiceInstaller(Entity entity, Enricher enricher) { + this.entity = checkNotNull(entity, "entity"); + this.enricher = checkNotNull(enricher, "enricher"); + } + + @Override + public Task<?> getServiceInstallTask() { + ResourceUtils resource = new ResourceUtils(this); + String pidFile = entity.getAttribute(SoftwareProcess.PID_FILE); + String template = resource.getResourceAsString(enricher.config().get(SERVICE_TEMPLATE)); + String serviceName = getServiceName(); + SshMachineLocation sshMachine = EffectorTasks.getSshMachine(entity); + Map<String, Object> params = MutableMap.<String, Object>of( + "service.launch_script", Os.mergePaths(getRunDir(), getStartScriptName()), + "service.name", serviceName, + "service.user", sshMachine.getUser(), + "service.log_path", getLogLocation()); + if (pidFile != null) { + params.put("service.pid_file", pidFile); + } + String service = TemplateProcessor.processTemplateContents(template, (EntityInternal)entity, params); + String tmpServicePath = Os.mergePaths(getRunDir(), serviceName); + String servicePath = "/etc/init.d/" + serviceName; + SshPutTaskWrapper putServiceTask = SshTasks.newSshPutTaskFactory(sshMachine, tmpServicePath) + .contents(service) + .newTask(); + ProcessTaskWrapper<Integer> installServiceTask = SshTasks.newSshExecTaskFactory(sshMachine, + BashCommands.chain( + BashCommands.sudo("mv " + tmpServicePath + " " + servicePath), + BashCommands.sudo("chmod 0755 " + servicePath), + BashCommands.sudo("chkconfig --add " + serviceName), + BashCommands.sudo("chkconfig " + serviceName + " on"))) + .requiringExitCodeZero() + .newTask(); + + return Tasks.<Void>builder() + .name("install (init.d)") + .description("Install init.d service") + .add(putServiceTask) + .add(installServiceTask) + .build(); + } + + private String getServiceName() { + String serviceNameTemplate = enricher.config().get(SystemServiceEnricher.SERVICE_NAME); + return serviceNameTemplate + .replace("${id}", entity.getId()) + .replace("${entity_name}", getEntityName()); + } + + private CharSequence getEntityName() { + String name; + if (entity instanceof HasShortName) { + name = ((HasShortName)entity).getShortName(); + } else if (entity instanceof Entity) { + name = ((Entity)entity).getDisplayName(); + } else { + name = "brooklyn-service"; + } + return CloudMachineNamer.sanitize(name.toString()).toLowerCase(); + } + + private String getStartScriptName() { + return enricher.config().get(SystemServiceEnricher.LAUNCH_SCRIPT_NAME); + } + + private String getRunDir() { + return entity.getAttribute(SoftwareProcess.RUN_DIR); + } + + private String getLogLocation() { + String logFileLocation = entity.getAttribute(Attributes.LOG_FILE_LOCATION); + if (logFileLocation != null) { + return new File(logFileLocation).getParent(); + } else { + return "/tmp"; + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac42ffc1/software/base/src/main/java/brooklyn/entity/service/SystemServiceEnricher.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/brooklyn/entity/service/SystemServiceEnricher.java b/software/base/src/main/java/brooklyn/entity/service/SystemServiceEnricher.java new file mode 100644 index 0000000..e47e375 --- /dev/null +++ b/software/base/src/main/java/brooklyn/entity/service/SystemServiceEnricher.java @@ -0,0 +1,152 @@ +/* + * 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.service; + +import java.util.Set; + +import brooklyn.config.ConfigKey; +import brooklyn.enricher.basic.AbstractEnricher; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.BrooklynTaskTags; +import brooklyn.entity.basic.BrooklynTaskTags.WrappedStream; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.EntityLocal; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.effector.EffectorTasks; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.management.ExecutionContext; +import brooklyn.management.Task; +import brooklyn.policy.Enricher; +import brooklyn.util.net.Urls; +import brooklyn.util.task.BasicExecutionManager; +import brooklyn.util.task.DynamicTasks; +import brooklyn.util.task.TaskBuilder; +import brooklyn.util.task.ssh.SshPutTaskWrapper; +import brooklyn.util.task.ssh.SshTasks; +import brooklyn.util.task.system.ProcessTaskFactory; +import brooklyn.util.task.system.ProcessTaskWrapper; + +import com.google.common.collect.ImmutableSet; + +public class SystemServiceEnricher extends AbstractEnricher implements Enricher { + public static final String DEFAULT_ENRICHER_UNIQUE_TAG = "systemService.tag"; + protected static final Set<String> LAUNCH_EFFECTOR_NAMES = ImmutableSet.of("start", "restart"); + public static final ConfigKey<String> LAUNCH_SCRIPT_NAME = ConfigKeys.newStringConfigKey( + "service.script_name", "The name of the launch script to be created in the runtime directory of the entity.", "service-launch.sh"); + public static final ConfigKey<String> SERVICE_NAME = ConfigKeys.newStringConfigKey( + "service.name", "The name of the system service. Can use ${entity_name} and ${id} variables to template the value.", "${entity_name}-${id}"); + + @Override + public void setEntity(EntityLocal entity) { + super.setEntity(entity); + subscribeLaunch(); + uniqueTag = DEFAULT_ENRICHER_UNIQUE_TAG; + } + + private void subscribeLaunch() { + subscribe(entity, Attributes.SERVICE_STATE_ACTUAL, new EntityLaunchListener(this)); + } + + public void onLaunched(Task<?> task) { + WrappedStream streamStdin = BrooklynTaskTags.stream(task, BrooklynTaskTags.STREAM_STDIN); + if (streamStdin == null) return; + + WrappedStream streamEnv = BrooklynTaskTags.stream(task, BrooklynTaskTags.STREAM_ENV); + String stdin = streamStdin.streamContents.get(); + String env = streamEnv.streamContents.get(); + + final SshMachineLocation sshMachine = EffectorTasks.getSshMachine(entity); + final String launchScriptPath = Urls.mergePaths(getRunDir(), getStartScriptName()); + + Task<Void> installerTask = TaskBuilder.<Void>builder() + .name("install (service)") + .description("Install as a system service") + .body(new Runnable() { + @Override + public void run() { + ProcessTaskFactory<Integer> taskFactory = SshTasks.newSshExecTaskFactory(sshMachine, "[ -e '" + launchScriptPath + "' ]") + .summary("check installed") + .allowingNonZeroExitCode(); + boolean isInstalled = DynamicTasks.queue(taskFactory).get() == 0; + if (!isInstalled) { + Task<?> serviceInstallTask = SystemServiceInstallerFactory.of(entity, SystemServiceEnricher.this).getServiceInstallTask(); + DynamicTasks.queue(serviceInstallTask); + } + } + }) + .build(); + + SshPutTaskWrapper updateLaunchScriptTask = SshTasks.newSshPutTaskFactory(sshMachine, launchScriptPath).contents(getLaunchScript(stdin, env)).newTask(); + ProcessTaskWrapper<Integer> makeExecutableTask = SshTasks.newSshExecTaskFactory(sshMachine, "chmod +x " + launchScriptPath) + .requiringExitCodeZero() + .newTask(); + Task<Void> udpateTask = TaskBuilder.<Void>builder() + .name("update-launch") + .description("Update launch script used by the system service") + .add(updateLaunchScriptTask) + .add(makeExecutableTask) + .build(); + + Task<Void> updateService = TaskBuilder.<Void>builder() + .name("update-system-service") + .description("Update system service") + .add(installerTask) + .add(udpateTask) + .tag(BrooklynTaskTags.tagForContextEntity(entity)) + .tag(BrooklynTaskTags.NON_TRANSIENT_TASK_TAG) + .build(); + + submitTopLevel(updateService); + } + + private void submitTopLevel(Task<Void> updateService) { + Task<?> currentTask = BasicExecutionManager.getPerThreadCurrentTask().get(); + BasicExecutionManager.getPerThreadCurrentTask().set(null); + try { + Entities.submit(entity, updateService); + } finally { + BasicExecutionManager.getPerThreadCurrentTask().set(currentTask); + } + } + + private String getLaunchScript(String stdin, String env) { + // (?m) - multiline enable + // insert export at beginning of each line + return env.replaceAll("(?m)^", "export ") + "\n" + stdin; + } + + private String getRunDir() { + return entity.getAttribute(SoftwareProcess.RUN_DIR); + } + + private String getStartScriptName() { + return config().get(LAUNCH_SCRIPT_NAME); + } + + ExecutionContext getEntityExecutionContext() { + return getManagementContext().getExecutionContext(entity); + } + + protected Entity getEntity() { + return entity; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac42ffc1/software/base/src/main/java/brooklyn/entity/service/SystemServiceInstaller.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/brooklyn/entity/service/SystemServiceInstaller.java b/software/base/src/main/java/brooklyn/entity/service/SystemServiceInstaller.java new file mode 100644 index 0000000..a4d8b6d --- /dev/null +++ b/software/base/src/main/java/brooklyn/entity/service/SystemServiceInstaller.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 brooklyn.entity.service; + +import brooklyn.management.Task; + +public interface SystemServiceInstaller { + Task<?> getServiceInstallTask(); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac42ffc1/software/base/src/main/java/brooklyn/entity/service/SystemServiceInstallerFactory.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/brooklyn/entity/service/SystemServiceInstallerFactory.java b/software/base/src/main/java/brooklyn/entity/service/SystemServiceInstallerFactory.java new file mode 100644 index 0000000..08da83a --- /dev/null +++ b/software/base/src/main/java/brooklyn/entity/service/SystemServiceInstallerFactory.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 brooklyn.entity.service; + +import brooklyn.entity.Entity; +import brooklyn.policy.Enricher; + +public class SystemServiceInstallerFactory { + public static SystemServiceInstaller of(Entity entity, Enricher systemServiceEnricher) { + return new InitdServiceInstaller(entity, systemServiceEnricher); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac42ffc1/software/base/src/main/resources/brooklyn/entity/service/service.sh ---------------------------------------------------------------------- diff --git a/software/base/src/main/resources/brooklyn/entity/service/service.sh b/software/base/src/main/resources/brooklyn/entity/service/service.sh new file mode 100644 index 0000000..4a1b293 --- /dev/null +++ b/software/base/src/main/resources/brooklyn/entity/service/service.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# +# 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. +# +# chkconfig: - 80 20 +# +### BEGIN INIT INFO +# Provides: ${service.name} +# Required-Start: $network $syslog +# Required-Stop: $network $syslog +# Default-Start: +# Default-Stop: +# Short-Description: Brooklyn entity service +# Description: Service for Brooklyn managed entity +### END INIT INFO + +case $1 in + start) + touch ${service.log_path}/${service.name}.log + chown ${service.user} ${service.log_path}/${service.name}.log + sudo -u ${service.user} ${service.launch_script} >> ${service.log_path}/${service.name}.log 2>&1 + ;; +# stop) +# ;; +# restart) +# ;; +# status) +# ;; +# reload) +# ;; + *) +# echo "Usage: $0 {start|stop|restart|reload|status}" + echo "Usage: $0 {start}" + exit 2 + ;; +esac
