http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcess.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcess.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcess.java new file mode 100644 index 0000000..573bc00 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcess.java @@ -0,0 +1,362 @@ +/* + * 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.software.base; + +import java.util.Collection; +import java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.MachineProvisioningLocation; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.config.MapConfigKey; +import org.apache.brooklyn.entity.annotation.Effector; +import org.apache.brooklyn.entity.core.Attributes; +import org.apache.brooklyn.entity.core.BrooklynConfigKeys; +import org.apache.brooklyn.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.entity.lifecycle.Lifecycle.Transition; +import org.apache.brooklyn.entity.trait.Startable; +import org.apache.brooklyn.sensor.core.AttributeSensorAndConfigKey; +import org.apache.brooklyn.sensor.core.Sensors; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.flags.SetFromFlag; +import org.apache.brooklyn.util.time.Duration; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.TypeToken; + +public interface SoftwareProcess extends Entity, Startable { + + AttributeSensor<String> HOSTNAME = Attributes.HOSTNAME; + AttributeSensor<String> ADDRESS = Attributes.ADDRESS; + AttributeSensor<String> SUBNET_HOSTNAME = Attributes.SUBNET_HOSTNAME; + AttributeSensor<String> SUBNET_ADDRESS = Attributes.SUBNET_ADDRESS; + + @SuppressWarnings("serial") + ConfigKey<Collection<Integer>> REQUIRED_OPEN_LOGIN_PORTS = ConfigKeys.newConfigKey( + new TypeToken<Collection<Integer>>() {}, + "requiredOpenLoginPorts", + "The port(s) to be opened, to allow login", + ImmutableSet.of(22)); + + @SetFromFlag("startTimeout") + ConfigKey<Duration> START_TIMEOUT = BrooklynConfigKeys.START_TIMEOUT; + + @SetFromFlag("startLatch") + ConfigKey<Boolean> START_LATCH = BrooklynConfigKeys.START_LATCH; + + @SetFromFlag("setupLatch") + ConfigKey<Boolean> SETUP_LATCH = BrooklynConfigKeys.SETUP_LATCH; + + @SetFromFlag("installResourcesLatch") + ConfigKey<Boolean> INSTALL_RESOURCES_LATCH = BrooklynConfigKeys.INSTALL_RESOURCES_LATCH; + + @SetFromFlag("installLatch") + ConfigKey<Boolean> INSTALL_LATCH = BrooklynConfigKeys.INSTALL_LATCH; + + @SetFromFlag("runtimeResourcesLatch") + ConfigKey<Boolean> RUNTIME_RESOURCES_LATCH = BrooklynConfigKeys.RUNTIME_RESOURCES_LATCH; + + @SetFromFlag("customizeLatch") + ConfigKey<Boolean> CUSTOMIZE_LATCH = BrooklynConfigKeys.CUSTOMIZE_LATCH; + + @SetFromFlag("launchLatch") + ConfigKey<Boolean> LAUNCH_LATCH = BrooklynConfigKeys.LAUNCH_LATCH; + + @SetFromFlag("skipStart") + ConfigKey<Boolean> ENTITY_STARTED = BrooklynConfigKeys.SKIP_ENTITY_START; + + @SetFromFlag("skipStartIfRunning") + ConfigKey<Boolean> SKIP_ENTITY_START_IF_RUNNING = BrooklynConfigKeys.SKIP_ENTITY_START_IF_RUNNING; + + @SetFromFlag("skipInstall") + ConfigKey<Boolean> SKIP_INSTALLATION = BrooklynConfigKeys.SKIP_ENTITY_INSTALLATION; + + @SetFromFlag("preInstallCommand") + ConfigKey<String> PRE_INSTALL_COMMAND = BrooklynConfigKeys.PRE_INSTALL_COMMAND; + + @SetFromFlag("postInstallCommand") + ConfigKey<String> POST_INSTALL_COMMAND = BrooklynConfigKeys.POST_INSTALL_COMMAND; + + @SetFromFlag("preLaunchCommand") + ConfigKey<String> PRE_LAUNCH_COMMAND = BrooklynConfigKeys.PRE_LAUNCH_COMMAND; + + @SetFromFlag("postLaunchCommand") + ConfigKey<String> POST_LAUNCH_COMMAND = BrooklynConfigKeys.POST_LAUNCH_COMMAND; + + @SetFromFlag("version") + ConfigKey<String> SUGGESTED_VERSION = BrooklynConfigKeys.SUGGESTED_VERSION; + + @SetFromFlag("downloadUrl") + AttributeSensorAndConfigKey<String,String> DOWNLOAD_URL = Attributes.DOWNLOAD_URL; + + @SetFromFlag("downloadAddonUrls") + AttributeSensorAndConfigKey<Map<String,String>,Map<String,String>> DOWNLOAD_ADDON_URLS = Attributes.DOWNLOAD_ADDON_URLS; + + @SetFromFlag("installLabel") + ConfigKey<String> INSTALL_UNIQUE_LABEL = BrooklynConfigKeys.INSTALL_UNIQUE_LABEL; + + @SetFromFlag("expandedInstallDir") + AttributeSensorAndConfigKey<String,String> EXPANDED_INSTALL_DIR = BrooklynConfigKeys.EXPANDED_INSTALL_DIR; + + @SetFromFlag("installDir") + AttributeSensorAndConfigKey<String,String> INSTALL_DIR = BrooklynConfigKeys.INSTALL_DIR; + @Deprecated + ConfigKey<String> SUGGESTED_INSTALL_DIR = BrooklynConfigKeys.SUGGESTED_INSTALL_DIR; + + @SetFromFlag("runDir") + AttributeSensorAndConfigKey<String,String> RUN_DIR = BrooklynConfigKeys.RUN_DIR; + @Deprecated + ConfigKey<String> SUGGESTED_RUN_DIR = BrooklynConfigKeys.SUGGESTED_RUN_DIR; + + public static final ConfigKey<Boolean> OPEN_IPTABLES = ConfigKeys.newBooleanConfigKey("openIptables", + "Whether to open the INBOUND_PORTS via iptables rules; " + + "if true then ssh in to run iptables commands, as part of machine provisioning", false); + + public static final ConfigKey<Boolean> STOP_IPTABLES = ConfigKeys.newBooleanConfigKey("stopIptables", + "Whether to stop iptables entirely; " + + "if true then ssh in to stop the iptables service, as part of machine provisioning", false); + + public static final ConfigKey<Boolean> DONT_REQUIRE_TTY_FOR_SUDO = ConfigKeys.newBooleanConfigKey("dontRequireTtyForSudo", + "Whether to explicitly set /etc/sudoers, so don't need tty (will leave unchanged if 'false'); " + + "some machines require a tty for sudo; brooklyn by default does not use a tty " + + "(so that it can get separate error+stdout streams); you can enable a tty as an " + + "option to every ssh command, or you can do it once and " + + "modify the machine so that a tty is not subsequently required.", + false); + + /** + * Files to be copied to the server before pre-install. + * <p> + * Map of {@code classpath://foo/file.txt} (or other url) source to destination path, + * as {@code subdir/file} relative to installation directory or {@code /absolute/path/to/file}. + * + * @see #PRE_INSTALL_TEMPLATES + */ + @Beta + @SuppressWarnings("serial") + @SetFromFlag("preInstallFiles") + ConfigKey<Map<String, String>> PRE_INSTALL_FILES = ConfigKeys.newConfigKey(new TypeToken<Map<String, String>>() { }, + "files.preinstall", "Mapping of files, to be copied before install, to destination name relative to installDir"); + + /** + * Templates to be filled in and then copied to the server before install. + * + * @see #PRE_INSTALL_FILES + */ + @Beta + @SuppressWarnings("serial") + @SetFromFlag("preInstallTemplates") + ConfigKey<Map<String, String>> PRE_INSTALL_TEMPLATES = ConfigKeys.newConfigKey(new TypeToken<Map<String, String>>() { }, + "templates.preinstall", "Mapping of templates, to be filled in and copied before pre-install, to destination name relative to installDir"); + + /** + * Files to be copied to the server before install. + * <p> + * Map of {@code classpath://foo/file.txt} (or other url) source to destination path, + * as {@code subdir/file} relative to installation directory or {@code /absolute/path/to/file}. + * + * @see #INSTALL_TEMPLATES + */ + @Beta + @SuppressWarnings("serial") + @SetFromFlag("installFiles") + ConfigKey<Map<String, String>> INSTALL_FILES = ConfigKeys.newConfigKey(new TypeToken<Map<String, String>>() { }, + "files.install", "Mapping of files, to be copied before install, to destination name relative to installDir"); + + /** + * Templates to be filled in and then copied to the server before install. + * + * @see #INSTALL_FILES + */ + @Beta + @SuppressWarnings("serial") + @SetFromFlag("installTemplates") + ConfigKey<Map<String, String>> INSTALL_TEMPLATES = ConfigKeys.newConfigKey(new TypeToken<Map<String, String>>() { }, + "templates.install", "Mapping of templates, to be filled in and copied before install, to destination name relative to installDir"); + + /** + * Files to be copied to the server after customisation. + * <p> + * Map of {@code classpath://foo/file.txt} (or other url) source to destination path, + * as {@code subdir/file} relative to runtime directory or {@code /absolute/path/to/file}. + * + * @see #RUNTIME_TEMPLATES + */ + @Beta + @SuppressWarnings("serial") + @SetFromFlag("runtimeFiles") + ConfigKey<Map<String, String>> RUNTIME_FILES = ConfigKeys.newConfigKey(new TypeToken<Map<String, String>>() { }, + "files.runtime", "Mapping of files, to be copied before customisation, to destination name relative to runDir"); + + /** + * Templates to be filled in and then copied to the server after customisation. + * + * @see #RUNTIME_FILES + */ + @Beta + @SuppressWarnings("serial") + @SetFromFlag("runtimeTemplates") + ConfigKey<Map<String, String>> RUNTIME_TEMPLATES = ConfigKeys.newConfigKey(new TypeToken<Map<String, String>>() { }, + "templates.runtime", "Mapping of templates, to be filled in and copied before customisation, to destination name relative to runDir"); + + @SetFromFlag("env") + MapConfigKey<Object> SHELL_ENVIRONMENT = new MapConfigKey<Object>(Object.class, + "shell.env", "Map of environment variables to pass to the runtime shell", MutableMap.<String,Object>of()); + + @SetFromFlag("provisioningProperties") + MapConfigKey<Object> PROVISIONING_PROPERTIES = new MapConfigKey<Object>(Object.class, + "provisioning.properties", "Custom properties to be passed in when provisioning a new machine", MutableMap.<String,Object>of()); + + @SetFromFlag("maxRebindSensorsDelay") + ConfigKey<Duration> MAXIMUM_REBIND_SENSOR_CONNECT_DELAY = ConfigKeys.newConfigKey(Duration.class, + "softwareProcess.maxSensorRebindDelay", + "The maximum delay to apply when reconnecting sensors when rebinding to this entity. " + + "Brooklyn will wait a random amount of time, up to the value of this config key, to " + + "avoid a thundering herd problem when the entity shares its machine with " + + "several others. Set to null or to 0 to disable any delay.", + Duration.TEN_SECONDS); + + /** + * Sets the object that manages the sequence of calls of the entity's driver. + */ + @Beta + @SetFromFlag("lifecycleEffectorTasks") + ConfigKey<SoftwareProcessDriverLifecycleEffectorTasks> LIFECYCLE_EFFECTOR_TASKS = ConfigKeys.newConfigKey(SoftwareProcessDriverLifecycleEffectorTasks.class, + "softwareProcess.lifecycleTasks", "An object that handles lifecycle of an entity's associated machine.", + new SoftwareProcessDriverLifecycleEffectorTasks()); + + ConfigKey<Boolean> RETRIEVE_USAGE_METRICS = ConfigKeys.newBooleanConfigKey( + "metrics.usage.retrieve", + "Whether to retrieve the usage (e.g. performance) metrics", + true); + + /** Controls the behavior when starting (stop, restart) {@link Startable} children as part of the start (stop, restart) effector on this entity + * <p> + * (NB: restarts are currently not propagated to children in the default {@link SoftwareProcess} + * due to the various semantics which may be desired; this may change, but if entities have specific requirements for restart, + * developers should either subclass the {@link SoftwareProcessDriverLifecycleEffectorTasks} and/or lean on sensors from the parent */ + enum ChildStartableMode { + /** do nothing with {@link Startable} children */ + NONE(true, false, false), + /** start (stop) {@link Startable} children concurrent with *driver* start (stop), + * in foreground, so invoking entity will wait for children to complete. + * <p> + * if the child requires the parent to reach a particular state before acting, + * when running in foreground the parent should communicate its state using sensors + * which the child listens for. + * note that often sensors at the parent are not activated until it is started, + * so the usual sensors connected at an entity may not be available when running in this mode */ + FOREGROUND(false, false, false), + /** as {@link #FOREGROUND} but {@link ChildStartableMode#isLate} */ + FOREGROUND_LATE(false, false, true), + /** start {@link Startable} children concurrent with *driver* start (stop, restart), + * but in background, ie disassociated from the effector task at this entity + * (so that this entity can complete start/stop independent of children) */ + BACKGROUND(false, true, false), + /** as {@link #BACKGROUND} but {@link ChildStartableMode#isLate} */ + BACKGROUND_LATE(false, true, true); + + /** whether starting (stopping, restarting) children is disabled */ + public final boolean isDisabled; + /** whether starting (stopping, restarting) children is backgrounded, so parent should not wait on them */ + public final boolean isBackground; + /** whether starting (stopping, restarting) children should be nested, so start occurs after the driver is started, + * and stop before the driver is stopped (if false the children operations are concurrent with the parent), + * (with restart always being done in parallel though this behaviour may change) */ + public final boolean isLate; + + private ChildStartableMode(boolean isDisabled, boolean isBackground, boolean isLate) { + this.isDisabled = isDisabled; + this.isBackground = isBackground; + this.isLate = isLate; + } + + } + + @SetFromFlag("childStartMode") + ConfigKey<ChildStartableMode> CHILDREN_STARTABLE_MODE = ConfigKeys.newConfigKey(ChildStartableMode.class, + "children.startable.mode", "Controls behaviour when starting Startable children as part of this entity's lifecycle.", + ChildStartableMode.NONE); + + @SuppressWarnings("rawtypes") + AttributeSensor<MachineProvisioningLocation> PROVISIONING_LOCATION = Sensors.newSensor( + MachineProvisioningLocation.class, "softwareservice.provisioningLocation", "Location used to provision a machine where this is running"); + + AttributeSensor<Boolean> SERVICE_PROCESS_IS_RUNNING = Sensors.newBooleanSensor("service.process.isRunning", + "Whether the process for the service is confirmed as running"); + + AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL; + AttributeSensor<Transition> SERVICE_STATE_EXPECTED = Attributes.SERVICE_STATE_EXPECTED; + + AttributeSensor<String> PID_FILE = Sensors.newStringSensor("softwareprocess.pid.file", "PID file"); + + @Beta + public static class RestartSoftwareParameters { + @Beta /** @since 0.7.0 semantics of parameters to restart being explored */ + public static final ConfigKey<Boolean> RESTART_CHILDREN = ConfigKeys.newConfigKey(Boolean.class, "restartChildren", + "Whether to restart children; default false", false); + + @Beta /** @since 0.7.0 semantics of parameters to restart being explored */ + public static final ConfigKey<Object> RESTART_MACHINE = ConfigKeys.newConfigKey(Object.class, "restartMachine", + "Whether to restart/replace the machine provisioned for this entity: 'true', 'false', or 'auto' are supported, " + + "with the default being 'auto' which means to restart or reprovision the machine if there is no simpler way known to restart the entity " + + "(for example, if the machine is unhealthy, it would not be possible to restart the process, not even via a stop-then-start sequence); " + + "if the machine was not provisioned for this entity, this parameter has no effect", + RestartMachineMode.AUTO.toString().toLowerCase()); + + // we supply a typed variant for retrieval; we want the untyped (above) to use lower case as the default in the GUI + // (very hard if using enum, since enum takes the name, and RendererHints do not apply to parameters) + @Beta /** @since 0.7.0 semantics of parameters to restart being explored */ + public static final ConfigKey<RestartMachineMode> RESTART_MACHINE_TYPED = ConfigKeys.newConfigKey(RestartMachineMode.class, "restartMachine"); + + public enum RestartMachineMode { TRUE, FALSE, AUTO } + } + + @Beta + public static class StopSoftwareParameters { + //IF_NOT_STOPPED includes STARTING, STOPPING, RUNNING + public enum StopMode { ALWAYS, IF_NOT_STOPPED, NEVER }; + + @Beta /** @since 0.7.0 semantics of parameters to restart being explored */ + public static final ConfigKey<StopMode> STOP_PROCESS_MODE = ConfigKeys.newConfigKey(StopMode.class, "stopProcessMode", + "When to stop the process with regard to the entity state. " + + "ALWAYS will try to stop the process even if the entity is marked as stopped, " + + "IF_NOT_STOPPED stops the process only if the entity is not marked as stopped, " + + "NEVER doesn't stop the process.", StopMode.IF_NOT_STOPPED); + + @Beta /** @since 0.7.0 semantics of parameters to restart being explored */ + public static final ConfigKey<StopMode> STOP_MACHINE_MODE = ConfigKeys.newConfigKey(StopMode.class, "stopMachineMode", + "When to stop the machine with regard to the entity state. " + + "ALWAYS will try to stop the machine even if the entity is marked as stopped, " + + "IF_NOT_STOPPED stops the machine only if the entity is not marked as stopped, " + + "NEVER doesn't stop the machine.", StopMode.IF_NOT_STOPPED); + } + + // NB: the START, STOP, and RESTART effectors themselves are (re)defined by MachineLifecycleEffectorTasks + + /** + * @since 0.8.0 + */ + @Effector(description="Populates the attribute service.notUp.diagnostics, with any available health indicators") + @Beta + void populateServiceNotUpDiagnostics(); +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessDriver.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessDriver.java new file mode 100644 index 0000000..8221282 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessDriver.java @@ -0,0 +1,75 @@ +/* + * 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.software.base; + +import org.apache.brooklyn.api.entity.drivers.EntityDriver; +import org.apache.brooklyn.api.internal.EntityLocal; +import org.apache.brooklyn.entity.trait.Startable; + +/** + * The {@link EntityDriver} for a {@link SoftwareProcess}. + * + * <p/> + * In many cases it is cleaner to store entity lifecycle effectors (and sometimes other implementations) in a class to + * which the entity delegates. Classes implementing this interface provide this delegate, often inheriting utilities + * specific to a particular transport (e.g. ssh) shared among many different entities. + * <p/> + * In this way, it is also possible for entities to cleanly support multiple mechanisms for start/stop and other methods. + */ +public interface SoftwareProcessDriver extends EntityDriver { + + /** + * The entity whose components we are controlling. + */ + EntityLocal getEntity(); + + /** + * Whether the entity components have started. + */ + boolean isRunning(); + + /** + * Rebinds the driver to a pre-existing software process. + */ + void rebind(); + + /** + * Performs software start (or queues tasks to do this) + */ + void start(); + + /** + * Performs software restart (or queues tasks to do this). + * Unlike stop/start implementations here are expected to update SERVICE_STATE for STOPPING and STARTING + * as appropriate (but framework will set RUNNING afterwards, after detecting it is running). + * @see Startable#restart() + */ + void restart(); + + /** + * Performs software stop (or queues tasks to do this) + * @see Startable#stop() + */ + void stop(); + + /** + * Kills the process, ungracefully and immediately where possible (e.g. with `kill -9`). + */ + void kill(); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessDriverLifecycleEffectorTasks.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessDriverLifecycleEffectorTasks.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessDriverLifecycleEffectorTasks.java new file mode 100644 index 0000000..56dfcb2 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessDriverLifecycleEffectorTasks.java @@ -0,0 +1,261 @@ +/* + * 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.software.base; + +import java.util.Map; + +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.api.location.MachineProvisioningLocation; +import org.apache.brooklyn.api.mgmt.TaskAdaptable; +import org.apache.brooklyn.entity.core.Attributes; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.entity.lifecycle.ServiceStateLogic; +import org.apache.brooklyn.entity.software.base.SoftwareProcess.ChildStartableMode; +import org.apache.brooklyn.entity.software.base.SoftwareProcess.RestartSoftwareParameters; +import org.apache.brooklyn.entity.software.base.SoftwareProcess.RestartSoftwareParameters.RestartMachineMode; +import org.apache.brooklyn.entity.software.base.lifecycle.MachineLifecycleEffectorTasks; +import org.apache.brooklyn.entity.trait.StartableMethods; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.task.DynamicTasks; +import org.apache.brooklyn.util.text.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Supplier; + +/** Thin shim delegating to driver to do start/stop/restart, wrapping as tasks, + * with common code pulled up to {@link MachineLifecycleEffectorTasks} for non-driver usage + * @since 0.6.0 */ +@Beta +public class SoftwareProcessDriverLifecycleEffectorTasks extends MachineLifecycleEffectorTasks { + + private static final Logger log = LoggerFactory.getLogger(SoftwareProcessDriverLifecycleEffectorTasks.class); + + public void restart(ConfigBag parameters) { + RestartMachineMode isRestartMachine = parameters.get(RestartSoftwareParameters.RESTART_MACHINE_TYPED); + if (isRestartMachine==null) + isRestartMachine=RestartMachineMode.AUTO; + if (isRestartMachine==RestartMachineMode.AUTO) + isRestartMachine = getDefaultRestartStopsMachine() ? RestartMachineMode.TRUE : RestartMachineMode.FALSE; + + if (isRestartMachine==RestartMachineMode.TRUE) { + log.debug("restart of "+entity()+" requested be applied at machine level"); + super.restart(parameters); + return; + } + + DynamicTasks.queue("pre-restart", new PreRestartTask()); + + log.debug("restart of "+entity()+" appears to have driver and hostname - doing driver-level restart"); + entity().getDriver().restart(); + + restartChildren(parameters); + + DynamicTasks.queue("post-restart", new PostRestartTask()); + } + + private class PreRestartTask implements Runnable { + @Override + public void run() { + preRestartCustom(); + } + } + + private class PostRestartTask implements Runnable { + @Override + public void run() { + postStartCustom(); + postRestartCustom(); + ServiceStateLogic.setExpectedState(entity(), Lifecycle.RUNNING); + } + } + + @Override + protected boolean getDefaultRestartStopsMachine() { + if (entity().getDriver() == null) { + log.debug("restart of "+entity()+" has no driver - doing machine-level restart"); + return true; + } + + if (Strings.isEmpty(entity().getAttribute(Attributes.HOSTNAME))) { + log.debug("restart of "+entity()+" has no hostname - doing machine-level restart"); + return true; + } + + return false; + } + + @Override + protected SoftwareProcessImpl entity() { + return (SoftwareProcessImpl) super.entity(); + } + + @Override + protected Map<String, Object> obtainProvisioningFlags(final MachineProvisioningLocation<?> location) { + return entity().obtainProvisioningFlags(location); + } + + @Override + protected void preStartCustom(MachineLocation machine) { + entity().initDriver(machine); + + // Note: must only apply config-sensors after adding to locations and creating driver; + // otherwise can't do things like acquire free port from location, or allowing driver to set up ports + super.preStartCustom(machine); + + entity().preStart(); + } + + /** returns how children startables should be handled (reporting none for efficiency if there are no children) */ + protected ChildStartableMode getChildrenStartableModeEffective() { + if (entity().getChildren().isEmpty()) return ChildStartableMode.NONE; + ChildStartableMode result = entity().getConfig(SoftwareProcess.CHILDREN_STARTABLE_MODE); + if (result!=null) return result; + return ChildStartableMode.NONE; + } + + @Override + protected String startProcessesAtMachine(final Supplier<MachineLocation> machineS) { + ChildStartableMode mode = getChildrenStartableModeEffective(); + TaskAdaptable<?> children = null; + if (!mode.isDisabled) { + children = StartableMethods.startingChildren(entity(), machineS.get()); + // submit rather than queue so it runs in parallel + // (could also wrap as parallel task with driver.start() - + // but only benefit is that child starts show as child task, + // rather than bg task, so not worth the code complexity) + if (!mode.isLate) Entities.submit(entity(), children); + } + + entity().getDriver().start(); + String result = "Started with driver "+entity().getDriver(); + + if (!mode.isDisabled) { + if (mode.isLate) { + DynamicTasks.waitForLast(); + if (mode.isBackground) { + Entities.submit(entity(), children); + } else { + // when running foreground late, there is no harm here in queueing + DynamicTasks.queue(children); + } + } + if (!mode.isBackground) children.asTask().getUnchecked(); + result += "; children started "+mode; + } + return result; + } + + @Override + protected void postStartCustom() { + entity().postDriverStart(); + if (entity().connectedSensors) { + // many impls aren't idempotent - though they should be! + log.debug("skipping connecting sensors for "+entity()+" in driver-tasks postStartCustom because already connected (e.g. restarting)"); + } else { + log.debug("connecting sensors for "+entity()+" in driver-tasks postStartCustom because already connected (e.g. restarting)"); + entity().connectSensors(); + } + entity().waitForServiceUp(); + entity().postStart(); + } + + @Override + protected void preStopConfirmCustom() { + super.preStopConfirmCustom(); + + entity().preStopConfirmCustom(); + } + + @Override + protected void preStopCustom() { + super.preStopCustom(); + + entity().preStop(); + } + + @Override + protected void preRestartCustom() { + super.preRestartCustom(); + + entity().preRestart(); + } + + @Override + protected void postRestartCustom() { + super.postRestartCustom(); + + entity().postRestart(); + } + + @Override + protected String stopProcessesAtMachine() { + String result; + + ChildStartableMode mode = getChildrenStartableModeEffective(); + TaskAdaptable<?> children = null; + Exception childException = null; + + if (!mode.isDisabled) { + children = StartableMethods.stoppingChildren(entity()); + + if (mode.isBackground || !mode.isLate) Entities.submit(entity(), children); + else { + DynamicTasks.queue(children); + try { + DynamicTasks.waitForLast(); + } catch (Exception e) { + childException = e; + } + } + } + + if (entity().getDriver() != null) { + entity().getDriver().stop(); + result = "Driver stop completed"; + } else { + result = "No driver (nothing to do here)"; + } + + if (!mode.isDisabled && !mode.isBackground) { + try { + children.asTask().get(); + } catch (Exception e) { + childException = e; + log.debug("Error stopping children; continuing and will rethrow if no other errors", e); + } + } + + if (childException!=null) + throw new IllegalStateException(result+"; but error stopping child: "+childException, childException); + + return result; + } + + @Override + protected void postStopCustom() { + super.postStopCustom(); + + entity().postStop(); + } + +} + http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessImpl.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessImpl.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessImpl.java new file mode 100644 index 0000000..a3e8bcd --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessImpl.java @@ -0,0 +1,651 @@ +/* + * 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.software.base; + +import groovy.time.TimeDuration; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.drivers.DriverDependentEntity; +import org.apache.brooklyn.api.entity.drivers.EntityDriverManager; +import org.apache.brooklyn.api.internal.EntityLocal; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.api.location.MachineProvisioningLocation; +import org.apache.brooklyn.api.location.PortRange; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.api.sensor.EnricherSpec; +import org.apache.brooklyn.api.sensor.SensorEvent; +import org.apache.brooklyn.api.sensor.SensorEventListener; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.entity.core.AbstractEntity; +import org.apache.brooklyn.entity.core.Attributes; +import org.apache.brooklyn.entity.core.BrooklynConfigKeys; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.entity.lifecycle.ServiceStateLogic; +import org.apache.brooklyn.entity.lifecycle.Lifecycle.Transition; +import org.apache.brooklyn.entity.lifecycle.ServiceStateLogic.ServiceNotUpLogic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.location.basic.LocationConfigKeys; +import org.apache.brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.cloud.CloudLocationConfig; +import org.apache.brooklyn.sensor.enricher.AbstractEnricher; +import org.apache.brooklyn.sensor.feed.function.FunctionFeed; +import org.apache.brooklyn.sensor.feed.function.FunctionPollConfig; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.flags.TypeCoercions; +import org.apache.brooklyn.util.core.task.DynamicTasks; +import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.time.CountdownTimer; +import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.time.Time; + +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import com.google.common.reflect.TypeToken; + +/** + * An {@link Entity} representing a piece of software which can be installed, run, and controlled. + * A single such entity can only run on a single {@link MachineLocation} at a time (you can have multiple on the machine). + * It typically takes config keys for suggested versions, filesystem locations to use, and environment variables to set. + * <p> + * It exposes sensors for service state (Lifecycle) and status (String), and for host info, log file location. + */ +public abstract class SoftwareProcessImpl extends AbstractEntity implements SoftwareProcess, DriverDependentEntity { + private static final Logger log = LoggerFactory.getLogger(SoftwareProcessImpl.class); + + private transient SoftwareProcessDriver driver; + + /** @see #connectServiceUpIsRunning() */ + private volatile FunctionFeed serviceProcessIsRunning; + + protected boolean connectedSensors = false; + + public SoftwareProcessImpl() { + super(MutableMap.of(), null); + } + public SoftwareProcessImpl(Entity parent) { + this(MutableMap.of(), parent); + } + public SoftwareProcessImpl(Map properties) { + this(properties, null); + } + public SoftwareProcessImpl(Map properties, Entity parent) { + super(properties, parent); + } + + protected void setProvisioningLocation(MachineProvisioningLocation val) { + if (getAttribute(PROVISIONING_LOCATION) != null) throw new IllegalStateException("Cannot change provisioning location: existing="+getAttribute(PROVISIONING_LOCATION)+"; new="+val); + setAttribute(PROVISIONING_LOCATION, val); + } + + protected MachineProvisioningLocation getProvisioningLocation() { + return getAttribute(PROVISIONING_LOCATION); + } + + @Override + public SoftwareProcessDriver getDriver() { + return driver; + } + + protected SoftwareProcessDriver newDriver(MachineLocation loc){ + EntityDriverManager entityDriverManager = getManagementContext().getEntityDriverManager(); + return (SoftwareProcessDriver)entityDriverManager.build(this, loc); + } + + protected MachineLocation getMachineOrNull() { + return Iterables.get(Iterables.filter(getLocations(), MachineLocation.class), 0, null); + } + + @Override + public void init() { + super.init(); + getLifecycleEffectorTasks().attachLifecycleEffectors(this); + } + + @Override + protected void initEnrichers() { + super.initEnrichers(); + ServiceNotUpLogic.updateNotUpIndicator(this, SERVICE_PROCESS_IS_RUNNING, "No information yet on whether this service is running"); + // add an indicator above so that if is_running comes through, the map is cleared and an update is guaranteed + addEnricher(EnricherSpec.create(UpdatingNotUpFromServiceProcessIsRunning.class).uniqueTag("service-process-is-running-updating-not-up")); + addEnricher(EnricherSpec.create(ServiceNotUpDiagnosticsCollector.class).uniqueTag("service-not-up-diagnostics-collector")); + } + + /** + * @since 0.8.0 + */ + protected static class ServiceNotUpDiagnosticsCollector extends AbstractEnricher implements SensorEventListener<Object> { + public ServiceNotUpDiagnosticsCollector() { + } + + @Override + public void setEntity(EntityLocal entity) { + super.setEntity(entity); + if (!(entity instanceof SoftwareProcess)) { + throw new IllegalArgumentException("Expected SoftwareProcess, but got entity "+entity); + } + subscribe(entity, Attributes.SERVICE_UP, this); + onUpdated(); + } + + @Override + public void onEvent(SensorEvent<Object> event) { + onUpdated(); + } + + protected void onUpdated() { + Boolean up = entity.getAttribute(SERVICE_UP); + if (up == null || up) { + entity.setAttribute(ServiceStateLogic.SERVICE_NOT_UP_DIAGNOSTICS, ImmutableMap.<String, Object>of()); + } else { + ((SoftwareProcess)entity).populateServiceNotUpDiagnostics(); + } + } + } + + @Override + public void populateServiceNotUpDiagnostics() { + if (getDriver() == null) { + ServiceStateLogic.updateMapSensorEntry(this, ServiceStateLogic.SERVICE_NOT_UP_DIAGNOSTICS, "driver", "No driver"); + return; + } + + Location loc = getDriver().getLocation(); + if (loc instanceof SshMachineLocation) { + if (!((SshMachineLocation)loc).isSshable()) { + ServiceStateLogic.updateMapSensorEntry( + this, + ServiceStateLogic.SERVICE_NOT_UP_DIAGNOSTICS, + "sshable", + "The machine for this entity does not appear to be sshable"); + } + return; + } + + boolean processIsRunning = getDriver().isRunning(); + if (!processIsRunning) { + ServiceStateLogic.updateMapSensorEntry( + this, + ServiceStateLogic.SERVICE_NOT_UP_DIAGNOSTICS, + SERVICE_PROCESS_IS_RUNNING.getName(), + "The software process for this entity does not appear to be running"); + } + } + + /** subscribes to SERVICE_PROCESS_IS_RUNNING and SERVICE_UP; the latter has no effect if the former is set, + * but to support entities which set SERVICE_UP directly we want to make sure that the absence of + * SERVICE_PROCESS_IS_RUNNING does not trigger any not-up indicators */ + protected static class UpdatingNotUpFromServiceProcessIsRunning extends AbstractEnricher implements SensorEventListener<Object> { + public UpdatingNotUpFromServiceProcessIsRunning() {} + + @Override + public void setEntity(EntityLocal entity) { + super.setEntity(entity); + subscribe(entity, SERVICE_PROCESS_IS_RUNNING, this); + subscribe(entity, Attributes.SERVICE_UP, this); + onUpdated(); + } + + @Override + public void onEvent(SensorEvent<Object> event) { + onUpdated(); + } + + protected void onUpdated() { + Boolean isRunning = entity.getAttribute(SERVICE_PROCESS_IS_RUNNING); + if (Boolean.FALSE.equals(isRunning)) { + ServiceNotUpLogic.updateNotUpIndicator(entity, SERVICE_PROCESS_IS_RUNNING, "The software process for this entity does not appear to be running"); + return; + } + if (Boolean.TRUE.equals(isRunning)) { + ServiceNotUpLogic.clearNotUpIndicator(entity, SERVICE_PROCESS_IS_RUNNING); + return; + } + // no info on "isRunning" + Boolean isUp = entity.getAttribute(Attributes.SERVICE_UP); + if (Boolean.TRUE.equals(isUp)) { + // if service explicitly set up, then don't apply our rule + ServiceNotUpLogic.clearNotUpIndicator(entity, SERVICE_PROCESS_IS_RUNNING); + return; + } + // service not up, or no info + ServiceNotUpLogic.updateNotUpIndicator(entity, SERVICE_PROCESS_IS_RUNNING, "No information on whether this service is running"); + } + } + + /** + * Called before driver.start; guarantees the driver will exist, and locations will have been set. + */ + protected void preStart() { + } + + /** + * Called after driver.start(). Default implementation is to wait to confirm the driver + * definitely started the process. + */ + protected void postDriverStart() { + waitForEntityStart(); + } + + /** + * For binding to the running app (e.g. connecting sensors to registry). Will be called + * on start() and on rebind(). + * <p> + * Implementations should be idempotent (ie tell whether sensors already connected), + * though the framework is pretty good about not calling when already connected. + * TODO improve the framework's feed system to detect duplicate additions + */ + protected void connectSensors() { + connectedSensors = true; + } + + /** + * For connecting the {@link #SERVICE_UP} sensor to the value of the {@code getDriver().isRunning()} expression. + * <p> + * Should be called inside {@link #connectSensors()}. + * + * @see #disconnectServiceUpIsRunning() + */ + protected void connectServiceUpIsRunning() { + serviceProcessIsRunning = FunctionFeed.builder() + .entity(this) + .period(Duration.FIVE_SECONDS) + .poll(new FunctionPollConfig<Boolean, Boolean>(SERVICE_PROCESS_IS_RUNNING) + .suppressDuplicates(true) + .onException(Functions.constant(Boolean.FALSE)) + .callable(new Callable<Boolean>() { + public Boolean call() { + return getDriver().isRunning(); + } + })) + .build(); + } + + /** + * For disconnecting the {@link #SERVICE_UP} feed. + * <p> + * Should be called from {@link #disconnectSensors()}. + * + * @see #connectServiceUpIsRunning() + */ + protected void disconnectServiceUpIsRunning() { + if (serviceProcessIsRunning != null) serviceProcessIsRunning.stop(); + // set null so the SERVICE_UP enricher runs (possibly removing it), then remove so everything is removed + // TODO race because the is-running check may be mid-task + setAttribute(SERVICE_PROCESS_IS_RUNNING, null); + removeAttribute(SERVICE_PROCESS_IS_RUNNING); + } + + /** + * Called after the rest of start has completed (after {@link #connectSensors()} and {@link #waitForServiceUp()}) + */ + protected void postStart() { + } + + protected void preStopConfirmCustom() { + } + + protected void preStop() { + // note asymmetry that disconnectSensors is done in the entity not the driver + // whereas on start the *driver* calls connectSensors, before calling postStart, + // ie waiting for the entity truly to be started before calling postStart; + // TODO feels like that confusion could be eliminated with a single place for pre/post logic!) + log.debug("disconnecting sensors for "+this+" in entity.preStop"); + disconnectSensors(); + + // Must set the serviceProcessIsRunning explicitly to false - we've disconnected the sensors + // so nothing else will. + // Otherwise, if restarted, there will be no change to serviceProcessIsRunning, so the + // serviceUpIndicators will not change, so serviceUp will not be reset. + // TODO Is there a race where disconnectSensors could leave a task of the feeds still running + // which could set serviceProcessIsRunning to true again before the task completes and the feed + // is fully terminated? + setAttribute(SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, false); + } + + /** + * Called after the rest of stop has completed (after VM deprovisioned, but before state set to STOPPED) + */ + protected void postStop() { + } + + /** + * Called before driver.restart; guarantees the driver will exist, and locations will have been set. + */ + protected void preRestart() { + } + + protected void postRestart() { + } + + /** + * For disconnecting from the running app. Will be called on stop. + */ + protected void disconnectSensors() { + connectedSensors = false; + } + + /** + * Called after this entity is fully rebound (i.e. it is fully managed). + */ + protected void postRebind() { + } + + protected void callRebindHooks() { + Duration configuredMaxDelay = getConfig(MAXIMUM_REBIND_SENSOR_CONNECT_DELAY); + if (configuredMaxDelay == null || Duration.ZERO.equals(configuredMaxDelay)) { + connectSensors(); + } else { + long delay = (long) (Math.random() * configuredMaxDelay.toMilliseconds()); + log.debug("Scheduled reconnection of sensors on {} in {}ms", this, delay); + Timer timer = new Timer(); + timer.schedule(new TimerTask() { + @Override public void run() { + try { + if (getManagementSupport().isNoLongerManaged()) { + log.debug("Entity {} no longer managed; ignoring scheduled connect sensors on rebind", SoftwareProcessImpl.this); + return; + } + connectSensors(); + } catch (Throwable e) { + log.warn("Problem connecting sensors on rebind of "+SoftwareProcessImpl.this, e); + Exceptions.propagateIfFatal(e); + } + } + }, delay); + } + // don't wait here - it may be long-running, e.g. if remote entity has died, and we don't want to block rebind waiting or cause it to fail + // the service will subsequently show service not up and thus failure +// waitForServiceUp(); + } + + @Override + public void onManagementStarting() { + super.onManagementStarting(); + + Lifecycle state = getAttribute(SERVICE_STATE_ACTUAL); + if (state == null || state == Lifecycle.CREATED) { + // Expect this is a normal start() sequence (i.e. start() will subsequently be called) + setAttribute(SERVICE_UP, false); + ServiceStateLogic.setExpectedState(this, Lifecycle.CREATED); + // force actual to be created because this is expected subsequently + setAttribute(SERVICE_STATE_ACTUAL, Lifecycle.CREATED); + } + } + + @Override + public void onManagementStarted() { + super.onManagementStarted(); + + Lifecycle state = getAttribute(SERVICE_STATE_ACTUAL); + if (state != null && state != Lifecycle.CREATED) { + postRebind(); + } + } + + @Override + public void rebind() { + //SERVICE_STATE_ACTUAL might be ON_FIRE due to a temporary condition (problems map non-empty) + //Only if the expected state is ON_FIRE then the entity has permanently failed. + Transition expectedState = getAttribute(SERVICE_STATE_EXPECTED); + if (expectedState == null || expectedState.getState() != Lifecycle.RUNNING) { + log.warn("On rebind of {}, not calling software process rebind hooks because expected state is {}", this, expectedState); + return; + } + + Lifecycle actualState = getAttribute(SERVICE_STATE_ACTUAL); + if (actualState == null || actualState != Lifecycle.RUNNING) { + log.warn("Rebinding entity {}, even though actual state is {}. Expected state is {}", new Object[] {this, actualState, expectedState}); + } + + // e.g. rebinding to a running instance + // FIXME For rebind, what to do about things in STARTING or STOPPING state? + // FIXME What if location not set? + log.info("Rebind {} connecting to pre-running service", this); + + MachineLocation machine = getMachineOrNull(); + if (machine != null) { + initDriver(machine); + driver.rebind(); + if (log.isDebugEnabled()) log.debug("On rebind of {}, re-created driver {}", this, driver); + } else { + log.info("On rebind of {}, no MachineLocation found (with locations {}) so not generating driver", + this, getLocations()); + } + + callRebindHooks(); + } + + public void waitForServiceUp() { + Duration timeout = getConfig(BrooklynConfigKeys.START_TIMEOUT); + waitForServiceUp(timeout); + } + public void waitForServiceUp(Duration duration) { + Entities.waitForServiceUp(this, duration); + } + public void waitForServiceUp(TimeDuration duration) { + waitForServiceUp(duration.toMilliseconds(), TimeUnit.MILLISECONDS); + } + public void waitForServiceUp(long duration, TimeUnit units) { + Entities.waitForServiceUp(this, Duration.of(duration, units)); + } + + protected Map<String,Object> obtainProvisioningFlags(MachineProvisioningLocation location) { + ConfigBag result = ConfigBag.newInstance(location.getProvisioningFlags(ImmutableList.of(getClass().getName()))); + result.putAll(getConfig(PROVISIONING_PROPERTIES)); + if (result.get(CloudLocationConfig.INBOUND_PORTS) == null) { + Collection<Integer> ports = getRequiredOpenPorts(); + Object requiredPorts = result.get(CloudLocationConfig.ADDITIONAL_INBOUND_PORTS); + if (requiredPorts instanceof Integer) { + ports.add((Integer) requiredPorts); + } else if (requiredPorts instanceof Iterable) { + for (Object o : (Iterable<?>) requiredPorts) { + if (o instanceof Integer) ports.add((Integer) o); + } + } + if (ports != null && ports.size() > 0) result.put(CloudLocationConfig.INBOUND_PORTS, ports); + } + result.put(LocationConfigKeys.CALLER_CONTEXT, this); + return result.getAllConfigMutable(); + } + + /** returns the ports that this entity wants to use; + * default implementation returns {@link SoftwareProcess#REQUIRED_OPEN_LOGIN_PORTS} plus first value + * for each {@link org.apache.brooklyn.sensor.core.PortAttributeSensorAndConfigKey} config key {@link PortRange} + * plus any ports defined with a config keys ending in {@code .port}. + */ + protected Collection<Integer> getRequiredOpenPorts() { + Set<Integer> ports = MutableSet.copyOf(getConfig(REQUIRED_OPEN_LOGIN_PORTS)); + Map<ConfigKey<?>, ?> allConfig = config().getBag().getAllConfigAsConfigKeyMap(); + Set<ConfigKey<?>> configKeys = Sets.newHashSet(allConfig.keySet()); + configKeys.addAll(getEntityType().getConfigKeys()); + + /* TODO: This won't work if there's a port collision, which will cause the corresponding port attribute + to be incremented until a free port is found. In that case the entity will use the free port, but the + firewall will open the initial port instead. Mostly a problem for SameServerEntity, localhost location. + */ + for (ConfigKey<?> k: configKeys) { + Object value; + if (PortRange.class.isAssignableFrom(k.getType()) || k.getName().matches(".*\\.port")) { + value = config().get(k); + } else { + // config().get() will cause this to block until all config has been resolved + // using config().getRaw(k) means that we won't be able to use e.g. 'http.port: $brooklyn:component("x").attributeWhenReady("foo")' + // but that's unlikely to be used + Maybe<Object> maybeValue = config().getRaw(k); + value = maybeValue.isPresent() ? maybeValue.get() : null; + } + + Maybe<PortRange> maybePortRange = TypeCoercions.tryCoerce(value, new TypeToken<PortRange>() {}); + + if (maybePortRange.isPresentAndNonNull()) { + PortRange p = maybePortRange.get(); + if (p != null && !p.isEmpty()) ports.add(p.iterator().next()); + } + } + + log.debug("getRequiredOpenPorts detected default {} for {}", ports, this); + return ports; + } + + protected void initDriver(MachineLocation machine) { + SoftwareProcessDriver newDriver = doInitDriver(machine); + if (newDriver == null) { + throw new UnsupportedOperationException("cannot start "+this+" on "+machine+": no driver available"); + } + driver = newDriver; + } + + /** + * Creates the driver (if does not already exist or needs replaced for some reason). Returns either the existing driver + * or a new driver. Must not return null. + */ + protected SoftwareProcessDriver doInitDriver(MachineLocation machine) { + if (driver!=null) { + if ((driver instanceof AbstractSoftwareProcessDriver) && machine.equals(((AbstractSoftwareProcessDriver)driver).getLocation())) { + return driver; //just reuse + } else { + log.warn("driver/location change is untested for {} at {}; changing driver and continuing", this, machine); + return newDriver(machine); + } + } else { + return newDriver(machine); + } + } + + // TODO Find a better way to detect early death of process. + public void waitForEntityStart() { + if (log.isDebugEnabled()) log.debug("waiting to ensure {} doesn't abort prematurely", this); + Duration startTimeout = getConfig(START_TIMEOUT); + CountdownTimer timer = startTimeout.countdownTimer(); + boolean isRunningResult = false; + long delay = 100; + Exception firstFailure = null; + while (!isRunningResult && !timer.isExpired()) { + Time.sleep(delay); + try { + isRunningResult = driver.isRunning(); + if (log.isDebugEnabled()) log.debug("checked {}, 'is running' returned: {}", this, isRunningResult); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + + isRunningResult = false; + if (driver != null) { + String msg = "checked " + this + ", 'is running' threw an exception; logging subsequent exceptions at debug level"; + if (firstFailure == null) { + log.error(msg, e); + } else { + log.debug(msg, e); + } + } else { + // provide extra context info, as we're seeing this happen in strange circumstances + log.error(this+" concurrent start and shutdown detected", e); + } + if (firstFailure == null) { + firstFailure = e; + } + } + // slow exponential delay -- 1.1^N means after 40 tries and 50s elapsed, it reaches the max of 5s intervals + // TODO use Repeater + delay = Math.min(delay*11/10, 5000); + } + if (!isRunningResult) { + String msg = "Software process entity "+this+" did not pass is-running check within "+ + "the required "+startTimeout+" limit ("+timer.getDurationElapsed().toStringRounded()+" elapsed)"; + if (firstFailure != null) { + msg += "; check failed at least once with exception: " + firstFailure.getMessage() + ", see logs for details"; + } + log.warn(msg+" (throwing)"); + ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING); + throw new IllegalStateException(msg, firstFailure); + } + } + + /** + * If custom behaviour is required by sub-classes, consider overriding {@link #preStart()} or {@link #postStart()})}. + * Also consider adding additional work via tasks, executed using {@link DynamicTasks#queue(String, Callable)}. + */ + @Override + public final void start(final Collection<? extends Location> locations) { + if (DynamicTasks.getTaskQueuingContext() != null) { + getLifecycleEffectorTasks().start(locations); + } else { + Task<?> task = Tasks.builder().name("start (sequential)").body(new Runnable() { public void run() { getLifecycleEffectorTasks().start(locations); } }).build(); + Entities.submit(this, task).getUnchecked(); + } + } + + /** + * If custom behaviour is required by sub-classes, consider overriding {@link #preStop()} or {@link #postStop()}. + * Also consider adding additional work via tasks, executed using {@link DynamicTasks#queue(String, Callable)}. + */ + @Override + public final void stop() { + // TODO There is a race where we set SERVICE_UP=false while sensor-adapter threads may still be polling. + // The other thread might reset SERVICE_UP to true immediately after we set it to false here. + // Deactivating adapters before setting SERVICE_UP reduces the race, and it is reduced further by setting + // SERVICE_UP to false at the end of stop as well. + + // Perhaps we should wait until all feeds have completed here, + // or do a SERVICE_STATE check before setting SERVICE_UP to true in a feed (?). + + if (DynamicTasks.getTaskQueuingContext() != null) { + getLifecycleEffectorTasks().stop(ConfigBag.EMPTY); + } else { + Task<?> task = Tasks.builder().name("stop").body(new Runnable() { public void run() { getLifecycleEffectorTasks().stop(ConfigBag.EMPTY); } }).build(); + Entities.submit(this, task).getUnchecked(); + } + } + + /** + * If custom behaviour is required by sub-classes, consider overriding {@link #preRestart()} or {@link #postRestart()}. + * Also consider adding additional work via tasks, executed using {@link DynamicTasks#queue(String, Callable)}. + */ + @Override + public final void restart() { + if (DynamicTasks.getTaskQueuingContext() != null) { + getLifecycleEffectorTasks().restart(ConfigBag.EMPTY); + } else { + Task<?> task = Tasks.builder().name("restart").body(new Runnable() { public void run() { getLifecycleEffectorTasks().restart(ConfigBag.EMPTY); } }).build(); + Entities.submit(this, task).getUnchecked(); + } + } + + protected SoftwareProcessDriverLifecycleEffectorTasks getLifecycleEffectorTasks() { + return getConfig(LIFECYCLE_EFFECTOR_TASKS); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcess.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcess.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcess.java new file mode 100644 index 0000000..1c55ddd --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcess.java @@ -0,0 +1,62 @@ +/* + * 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.software.base; + +import org.apache.brooklyn.api.catalog.Catalog; +import org.apache.brooklyn.api.entity.ImplementedBy; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; + +/** + * A {@link SoftwareProcess} entity that runs commands from an archive. + * <p> + * Downloads and unpacks the archive indicated (optionally) then runs the management commands (scripts) indicated + * (relative to the root of the archive if supplied, otherwise in a tmp working dir) to manage. Uses config keys + * to identify the files or commands to use. + * <p> + * In the simplest mode, simply provide either: + * <ul> + * <li> an archive in {@link #DOWNLOAD_URL} containing a <code>./start.sh</code> + * <li> a start command to invoke in {@link #LAUNCH_COMMAND} + * </ul> + * The only constraint is that the start command must write the PID into the file pointed to by the injected environment + * variable {@code PID_FILE} unless one of the options below is supported. + * <p> + * The start command can be a complex bash command, downloading and unpacking files, and handling the {@code PID_FILE} requirement. + * For example {@code export MY_PID_FILE=$PID_FILE ; ./my_start.sh} or {@code nohup ./start.sh & ; echo $! > $PID_FILE ; sleep 5}. + * </pre> + * You can supply both {@link #DOWNLOAD_URL} and {@link #LAUNCH_COMMAND} configuration as well.. + * <p> + * In addition, you can supply an {@link #INSTALL_COMMAND} and / or a {@link #CUSTOMIZE_COMMAND} to reduce the complexity + * of the {@link #LAUNCH_COMMAND}, and to avoid repeating actions that are unnecessary in subsequent launches. + * <p> + * By default the PID is used to stop the process using {@code kill} followed by {@code kill -9} if needed and restart + * is implemented by stopping the process and then running {@link VanillaSoftwareProcessSshDriver#launch()}, but it is + * possible to override this behavior through config keys: + * <ul> + * <li> A custom {@link #CHECK_RUNNING_COMMAND} + * <li> A custom {@link #STOP_COMMAND} + * <li> A different {@link SoftwareProcess#PID_FILE} to use + * <li> + */ +@Catalog(name="Vanilla Software Process", description="A software process configured with scripts, e.g. for launch, check-running and stop") +@ImplementedBy(VanillaSoftwareProcessImpl.class) +public interface VanillaSoftwareProcess extends AbstractVanillaProcess { + ConfigKey<String> LAUNCH_COMMAND = ConfigKeys.newConfigKeyWithDefault(AbstractVanillaProcess.LAUNCH_COMMAND, "./start.sh"); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessDriver.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessDriver.java new file mode 100644 index 0000000..6fee1d2 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessDriver.java @@ -0,0 +1,23 @@ +/* + * 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.software.base; + +public interface VanillaSoftwareProcessDriver extends SoftwareProcessDriver { + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessImpl.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessImpl.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessImpl.java new file mode 100644 index 0000000..6f1aec0 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessImpl.java @@ -0,0 +1,37 @@ +/* + * 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.software.base; + + +public class VanillaSoftwareProcessImpl extends SoftwareProcessImpl implements VanillaSoftwareProcess { + @Override + public Class<?> getDriverInterface() { + return VanillaSoftwareProcessDriver.class; + } + @Override + protected void connectSensors() { + super.connectSensors(); + connectServiceUpIsRunning(); + } + @Override + protected void disconnectSensors() { + disconnectServiceUpIsRunning(); + super.disconnectSensors(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java new file mode 100644 index 0000000..7edb047 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessSshDriver.java @@ -0,0 +1,162 @@ +/* + * 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.software.base; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolver; +import org.apache.brooklyn.api.internal.EntityLocal; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.entity.software.base.lifecycle.ScriptHelper; +import org.apache.brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.file.ArchiveUtils; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.net.Urls; +import org.apache.brooklyn.util.os.Os; +import org.apache.brooklyn.util.ssh.BashCommands; +import org.apache.brooklyn.util.text.Identifiers; +import org.apache.brooklyn.util.text.Strings; + +public class VanillaSoftwareProcessSshDriver extends AbstractSoftwareProcessSshDriver implements VanillaSoftwareProcessDriver { + + public VanillaSoftwareProcessSshDriver(EntityLocal entity, SshMachineLocation machine) { + super(entity, machine); + } + + String downloadedFilename = null; + + /** needed because the download url might be different! */ + @Override + protected String getInstallLabelExtraSalt() { + Maybe<Object> url = getEntity().getConfigRaw(SoftwareProcess.DOWNLOAD_URL, true); + if (url.isAbsent()) return null; + // TODO a user-friendly hash would be nice, but tricky since we don't want it to be too long or contain path chars + return Identifiers.makeIdFromHash( url.get().hashCode() ); + } + + @Override + public void install() { + Maybe<Object> url = getEntity().getConfigRaw(SoftwareProcess.DOWNLOAD_URL, true); + if (url.isPresentAndNonNull()) { + DownloadResolver resolver = Entities.newDownloader(this); + List<String> urls = resolver.getTargets(); + downloadedFilename = resolver.getFilename(); + + List<String> commands = new LinkedList<String>(); + commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, downloadedFilename)); + commands.addAll(ArchiveUtils.installCommands(downloadedFilename)); + + int result = newScript(INSTALLING) + .failOnNonZeroResultCode(false) + .body.append(commands) + .execute(); + + if (result!=0) { + // could not install at remote machine; try resolving URL here and copying across + for (String urlI: urls) { + result = ArchiveUtils.install(getMachine(), urlI, Urls.mergePaths(getInstallDir(), downloadedFilename)); + if (result==0) + break; + } + if (result != 0) + throw new IllegalStateException("Error installing archive: " + downloadedFilename); + } + } + + String installCommand = getEntity().getConfig(VanillaSoftwareProcess.INSTALL_COMMAND); + + if (Strings.isNonBlank(installCommand)) { + newScript(INSTALLING) + .failOnNonZeroResultCode() + .environmentVariablesReset(getShellEnvironment()) + .body.append(installCommand) + .execute(); + } + } + + @Override + public void customize() { + if (downloadedFilename != null) { + newScript(CUSTOMIZING) + .failOnNonZeroResultCode() + // don't set vars yet -- it resolves dependencies (e.g. DB) which we don't want until we start + .environmentVariablesReset() + .body.append(ArchiveUtils.extractCommands(downloadedFilename, getInstallDir())) + .execute(); + } + + String customizeCommand = getEntity().getConfig(VanillaSoftwareProcess.CUSTOMIZE_COMMAND); + + if (Strings.isNonBlank(customizeCommand)) { + newScript(CUSTOMIZING) + .failOnNonZeroResultCode() + .body.append(customizeCommand) + .execute(); + } + } + + @Override + public Map<String, String> getShellEnvironment() { + return MutableMap.copyOf(super.getShellEnvironment()).add("PID_FILE", getPidFile()); + } + + public String getPidFile() { + // TODO see note in VanillaSoftwareProcess about PID_FILE as a config key + // if (getEntity().getConfigRaw(PID_FILE, includeInherited)) ... + return Os.mergePathsUnix(getRunDir(), PID_FILENAME); + } + + @Override + public void launch() { + newScript(LAUNCHING) + .failOnNonZeroResultCode() + .body.append(getEntity().getConfig(VanillaSoftwareProcess.LAUNCH_COMMAND)) + .execute(); + } + + @Override + public boolean isRunning() { + String customCommand = getEntity().getConfig(VanillaSoftwareProcess.CHECK_RUNNING_COMMAND); + ScriptHelper script = null; + if (customCommand == null) { + script = newScript(MutableMap.of(USE_PID_FILE, getPidFile()), CHECK_RUNNING); + } else { + // TODO: template substitutions? + script = newScript(CHECK_RUNNING).body.append(customCommand); + } + return script.execute() == 0; + } + + @Override + public void stop() { + String customCommand = getEntity().getConfig(VanillaSoftwareProcess.STOP_COMMAND); + ScriptHelper script = null; + if (customCommand == null) { + script = newScript(MutableMap.of(USE_PID_FILE, getPidFile()), STOPPING); + } else { + // TODO: template substitutions? + script = newScript(STOPPING).body.append(customCommand); + } + script.execute(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcess.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcess.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcess.java new file mode 100644 index 0000000..1354b03 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcess.java @@ -0,0 +1,64 @@ +/* + * 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.software.base; + +import java.util.Collection; + +import org.apache.brooklyn.api.entity.ImplementedBy; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.sensor.core.Sensors; +import org.apache.brooklyn.util.time.Duration; + +import com.google.common.collect.ImmutableSet; + +@ImplementedBy(VanillaWindowsProcessImpl.class) +public interface VanillaWindowsProcess extends AbstractVanillaProcess { + // 3389 is RDP; 5985 is WinRM (3389 isn't used by Brooklyn, but useful for the end-user subsequently) + ConfigKey<Collection<Integer>> REQUIRED_OPEN_LOGIN_PORTS = ConfigKeys.newConfigKeyWithDefault( + SoftwareProcess.REQUIRED_OPEN_LOGIN_PORTS, + ImmutableSet.of(5985, 3389)); + ConfigKey<String> PRE_INSTALL_POWERSHELL_COMMAND = ConfigKeys.newStringConfigKey("pre.install.powershell.command", + "powershell command to run during the pre-install phase"); + ConfigKey<Boolean> PRE_INSTALL_REBOOT_REQUIRED = ConfigKeys.newBooleanConfigKey("pre.install.reboot.required", + "indicates that a reboot should be performed after the pre-install command is run", false); + ConfigKey<Boolean> INSTALL_REBOOT_REQUIRED = ConfigKeys.newBooleanConfigKey("install.reboot.required", + "indicates that a reboot should be performed after the install command is run", false); + ConfigKey<Boolean> CUSTOMIZE_REBOOT_REQUIRED = ConfigKeys.newBooleanConfigKey("customize.reboot.required", + "indicates that a reboot should be performed after the customize command is run", false); + ConfigKey<String> LAUNCH_POWERSHELL_COMMAND = ConfigKeys.newStringConfigKey("launch.powershell.command", + "command to run to launch the process"); + ConfigKey<String> CHECK_RUNNING_POWERSHELL_COMMAND = ConfigKeys.newStringConfigKey("checkRunning.powershell.command", + "command to determine whether the process is running"); + ConfigKey<String> STOP_POWERSHELL_COMMAND = ConfigKeys.newStringConfigKey("stop.powershell.command", + "command to run to stop the process"); + ConfigKey<String> CUSTOMIZE_POWERSHELL_COMMAND = ConfigKeys.newStringConfigKey("customize.powershell.command", + "powershell command to run during the customization phase"); + ConfigKey<String> INSTALL_POWERSHELL_COMMAND = ConfigKeys.newStringConfigKey("install.powershell.command", + "powershell command to run during the install phase"); + ConfigKey<Duration> REBOOT_BEGUN_TIMEOUT = ConfigKeys.newDurationConfigKey("reboot.begun.timeout", + "duration to wait whilst waiting for a machine to begin rebooting, and thus become unavailable", Duration.TWO_MINUTES); + // TODO If automatic updates are enabled and there are updates waiting to be installed, thirty minutes may not be sufficient... + ConfigKey<Duration> REBOOT_COMPLETED_TIMEOUT = ConfigKeys.newDurationConfigKey("reboot.completed.timeout", + "duration to wait whilst waiting for a machine to finish rebooting, and thus to become available again", Duration.minutes(30)); + + AttributeSensor<Integer> RDP_PORT = Sensors.newIntegerSensor("rdpPort"); + AttributeSensor<Integer> WINRM_PORT = Sensors.newIntegerSensor("winrmPort"); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessDriver.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessDriver.java new file mode 100644 index 0000000..a6083ff --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessDriver.java @@ -0,0 +1,23 @@ +/* + * 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.software.base; + +public interface VanillaWindowsProcessDriver extends SoftwareProcessDriver { + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/64c2b2e5/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessImpl.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessImpl.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessImpl.java new file mode 100644 index 0000000..b3b9f83 --- /dev/null +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessImpl.java @@ -0,0 +1,47 @@ +/* + * 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.software.base; + + +public class VanillaWindowsProcessImpl extends SoftwareProcessImpl implements VanillaWindowsProcess { + @Override + public Class getDriverInterface() { + return VanillaWindowsProcessDriver.class; + } + + @Override + protected void preStart() { + super.preStart(); + setAttribute(RDP_PORT, 3389); + setAttribute(WINRM_PORT, 5985); + } + + @Override + protected void connectSensors() { + super.connectSensors(); + connectServiceUpIsRunning(); + } + + @Override + protected void disconnectSensors() { + disconnectServiceUpIsRunning(); + super.disconnectSensors(); + } + +}
