You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/08/19 13:09:39 UTC
[21/72] [abbrv] incubator-brooklyn git commit: BROOKLYN-162 - apply
org.apache package prefix to software-base, tidying package names,
and moving a few sensory things to core
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();
+ }
+
+}