diff --git a/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java b/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java index 4db73ef3b7..4ca2368e1b 100644 --- a/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java +++ b/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java @@ -95,7 +95,10 @@ public static EntitySpec create(EntitySpec spec) { .configure(spec.getConfig()) .configure(spec.getFlags()) .policySpecs(spec.getPolicySpecs()) - .policies(spec.getPolicies()); + .policies(spec.getPolicies()) + .enricherSpecs(spec.getEnricherSpecs()) + .enrichers(spec.getEnrichers()) + .addInitializers(spec.getInitializers()); if (spec.getParent() != null) result.parent(spec.getParent()); if (spec.getImplementation() != null) result.impl(spec.getImplementation()); diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java index 3e48e1dd5d..9118bf67c2 100644 --- a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java +++ b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java @@ -467,6 +467,7 @@ public String getIconUrl() { public void setDisplayName(String newDisplayName) { displayName.set(newDisplayName); displayNameAutoGenerated = false; + getManagementSupport().getEntityChangeListener().onChanged(); } /** allows subclasses to set the default display name to use if none is provided */ diff --git a/core/src/main/java/brooklyn/entity/basic/BrooklynConfigKeys.java b/core/src/main/java/brooklyn/entity/basic/BrooklynConfigKeys.java index 825bf215aa..3e86a91e45 100644 --- a/core/src/main/java/brooklyn/entity/basic/BrooklynConfigKeys.java +++ b/core/src/main/java/brooklyn/entity/basic/BrooklynConfigKeys.java @@ -39,7 +39,12 @@ public class BrooklynConfigKeys { + "apps/${entity.applicationId}/" + "entities/${entity.entityType.simpleName}_" + "${entity.id}"); - + + public static final BasicAttributeSensorAndConfigKey EXPANDED_INSTALL_DIR = new TemplatedStringAttributeSensorAndConfigKey( + "expandedinstall.dir", + "Directory for installed artifacts (e.g. expanded dir after unpacking .tgz)", + null); + /** @deprecated since 0.7.0; use {@link #INSTALL_DIR} */ public static final ConfigKey SUGGESTED_INSTALL_DIR = INSTALL_DIR.getConfigKey(); /** @deprecated since 0.7.0; use {@link #RUN_DIR} */ diff --git a/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java b/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java index d1e348367c..61d510f889 100644 --- a/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java +++ b/core/src/main/java/brooklyn/entity/basic/EntityPredicates.java @@ -20,6 +20,15 @@ public boolean apply(@Nullable Entity input) { }; } + public static Predicate displayNameEqualTo(final T val) { + return new Predicate() { + @Override + public boolean apply(@Nullable Entity input) { + return Objects.equal(input.getDisplayName(), val); + } + }; + } + public static Predicate applicationIdEqualTo(final String val) { return new Predicate() { @Override diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java index aa806ffe52..c40d261f01 100644 --- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java +++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java @@ -41,6 +41,7 @@ import brooklyn.util.exceptions.Exceptions; import brooklyn.util.flags.FlagUtils; import brooklyn.util.javalang.Reflections; +import brooklyn.util.time.Duration; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; @@ -74,6 +75,10 @@ public RebindManagerImpl(ManagementContextInternal managementContext) { /** * Must be called before setPerister() */ + public void setPeriodicPersistPeriod(Duration period) { + this.periodicPersistPeriod = period.toMilliseconds(); + } + public void setPeriodicPersistPeriod(long periodMillis) { this.periodicPersistPeriod = periodMillis; } @@ -302,7 +307,6 @@ private Entity newEntity(EntityMemento memento, Reflections reflections) { // a proxy for if another entity needs to reference it during the init phase. InternalEntityFactory entityFactory = managementContext.getEntityFactory(); Entity entity = entityFactory.constructEntity(entityClazz); - FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", entityId), entity); if (entity instanceof AbstractApplication) { FlagUtils.setFieldsFromFlags(ImmutableMap.of("mgmt", managementContext), entity); @@ -327,7 +331,19 @@ private Entity newEntity(EntityMemento memento, Reflections reflections) { flags.put("id", entityId); if (AbstractApplication.class.isAssignableFrom(entityClazz)) flags.put("mgmt", managementContext); Entity entity = (Entity) invokeConstructor(reflections, entityClazz, new Object[] {flags}, new Object[] {flags, null}, new Object[] {null}, new Object[0]); + + // In case the constructor didn't take the Map arg, then also set it here. + // e.g. for top-level app instances such as WebClusterDatabaseExampleApp will (often?) not have + // interface + constructor. + // TODO On serializing the memento, we should capture which interfaces so can recreate + // the proxy+spec (including for apps where there's not an obvious interface). + FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", entityId), entity); + if (entity instanceof AbstractApplication) { + FlagUtils.setFieldsFromFlags(ImmutableMap.of("mgmt", managementContext), entity); + } ((AbstractEntity)entity).setManagementContext(managementContext); + managementContext.prePreManage(entity); + return entity; } } diff --git a/core/src/main/java/brooklyn/event/basic/TemplatedStringAttributeSensorAndConfigKey.java b/core/src/main/java/brooklyn/event/basic/TemplatedStringAttributeSensorAndConfigKey.java index 13893ddbd3..b25c0c08e7 100644 --- a/core/src/main/java/brooklyn/event/basic/TemplatedStringAttributeSensorAndConfigKey.java +++ b/core/src/main/java/brooklyn/event/basic/TemplatedStringAttributeSensorAndConfigKey.java @@ -33,7 +33,9 @@ public TemplatedStringAttributeSensorAndConfigKey(TemplatedStringAttributeSensor super(orig, defaultValue); } + @Override protected String convertConfigToSensor(String value, Entity entity) { + if (value == null) return null; return TemplateProcessor.processTemplateContents(value, (EntityInternal)entity, ImmutableMap.of()); } diff --git a/core/src/main/java/brooklyn/management/internal/EntityChangeListener.java b/core/src/main/java/brooklyn/management/internal/EntityChangeListener.java index 68f1008f56..197b5aec97 100644 --- a/core/src/main/java/brooklyn/management/internal/EntityChangeListener.java +++ b/core/src/main/java/brooklyn/management/internal/EntityChangeListener.java @@ -7,6 +7,7 @@ public interface EntityChangeListener { public static final EntityChangeListener NOOP = new EntityChangeListener() { + @Override public void onChanged() {} @Override public void onAttributeChanged(AttributeSensor attribute) {} @Override public void onConfigChanged(ConfigKey key) {} @Override public void onLocationsChanged() {} @@ -17,6 +18,8 @@ public interface EntityChangeListener { @Override public void onEffectorCompleted(Effector effector) {} }; + void onChanged(); + void onAttributeChanged(AttributeSensor attribute); void onConfigChanged(ConfigKey key); diff --git a/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java b/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java index c264bf1c52..7ba5d5b882 100644 --- a/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java +++ b/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java @@ -314,6 +314,10 @@ public EntityChangeListener getEntityChangeListener() { } private class EntityChangeListenerImpl implements EntityChangeListener { + @Override + public void onChanged() { + getManagementContext().getRebindManager().getChangeListener().onChanged(entity); + } @Override public void onChildrenChanged() { getManagementContext().getRebindManager().getChangeListener().onChanged(entity); diff --git a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5SshDriver.java b/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5SshDriver.java index 3b3220364d..d4baf9f41a 100644 --- a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5SshDriver.java +++ b/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5SshDriver.java @@ -22,8 +22,6 @@ */ public class Infinispan5SshDriver extends JavaSoftwareProcessSshDriver implements Infinispan5Driver { - private String expandedInstallDir; - public Infinispan5SshDriver(Infinispan5Server entity, SshMachineLocation machine) { super(entity, machine); } @@ -41,18 +39,13 @@ protected Integer getPort() { return entity.getAttribute(Infinispan5Server.PORT); } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); // FIXME will saveAs be "infinispan-${version}-all.zip"? - expandedInstallDir = getInstallDir(); // unpacks to current directory, rather than sub-directory + setExpandedInstallDir(getInstallDir()); // unpacks to current directory, rather than sub-directory List commands = ImmutableList.builder() .addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)) diff --git a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java index 91d420ea05..0dabedf606 100644 --- a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java +++ b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java @@ -2,6 +2,7 @@ import static brooklyn.util.GroovyJavaMethods.elvis; import static brooklyn.util.GroovyJavaMethods.truth; +import static com.google.common.base.Preconditions.checkNotNull; import java.io.File; import java.io.StringReader; @@ -51,6 +52,7 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP // we cache these in case the entity becomes unmanaged private volatile String installDir; private volatile String runDir; + private volatile String expandedInstallDir; /** include this flag in newScript creation to prevent entity-level flags from being included; * any SSH-specific flags passed to newScript override flags from the entity, @@ -120,6 +122,12 @@ protected String getEntityVersionLabel(String separator) { public String getInstallDir() { if (installDir != null) return installDir; + + String existingVal = getEntity().getAttribute(SoftwareProcess.INSTALL_DIR); + if (Strings.isNonBlank(existingVal)) { // e.g. on rebind + installDir = existingVal; + return installDir; + } // deprecated in 0.7.0 Maybe minstallDir = getEntity().getConfigRaw(SoftwareProcess.INSTALL_DIR, true); @@ -142,6 +150,12 @@ public String getInstallDir() { public String getRunDir() { if (runDir != null) return runDir; + String existingVal = getEntity().getAttribute(SoftwareProcess.RUN_DIR); + if (Strings.isNonBlank(existingVal)) { // e.g. on rebind + runDir = existingVal; + return runDir; + } + // deprecated in 0.7.0 Maybe mRunDir = getEntity().getConfigRaw(SoftwareProcess.RUN_DIR, true); if (!mRunDir.isPresent() || mRunDir.get()==null) { @@ -160,6 +174,29 @@ public String getRunDir() { return runDir; } + public void setExpandedInstallDir(String val) { + checkNotNull(val, "expandedInstallDir"); + String oldVal = getEntity().getAttribute(SoftwareProcess.EXPANDED_INSTALL_DIR); + if (Strings.isNonBlank(oldVal) && !oldVal.equals(val)) { + log.info("Resetting expandedInstallDir (to "+val+" from "+oldVal+") for "+getEntity()); + } + + getEntity().setAttribute(SoftwareProcess.EXPANDED_INSTALL_DIR, val); + } + + public String getExpandedInstallDir() { + if (expandedInstallDir != null) return expandedInstallDir; + + String untidiedVal = ConfigToAttributes.apply(getEntity(), SoftwareProcess.EXPANDED_INSTALL_DIR); + if (Strings.isNonBlank(untidiedVal)) { + expandedInstallDir = Os.tidyPath(untidiedVal); + entity.setAttribute(SoftwareProcess.INSTALL_DIR, expandedInstallDir); + return expandedInstallDir; + } else { + throw new IllegalStateException("expandedInstallDir is null; most likely install was not called for "+getEntity()); + } + } + public SshMachineLocation getMachine() { return getLocation(); } public String getHostname() { return entity.getAttribute(Attributes.HOSTNAME); } public String getAddress() { return entity.getAttribute(Attributes.ADDRESS); } diff --git a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java index cf981a4983..c6fb828467 100644 --- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java +++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java @@ -44,6 +44,9 @@ public interface SoftwareProcess extends Entity, Startable { @SetFromFlag("downloadAddonUrls") BasicAttributeSensorAndConfigKey> DOWNLOAD_ADDON_URLS = Attributes.DOWNLOAD_ADDON_URLS; + @SetFromFlag("expandedInstallDir") + BasicAttributeSensorAndConfigKey EXPANDED_INSTALL_DIR = BrooklynConfigKeys.EXPANDED_INSTALL_DIR; + @SetFromFlag("installDir") BasicAttributeSensorAndConfigKey INSTALL_DIR = BrooklynConfigKeys.INSTALL_DIR; @Deprecated diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java index 86fee36083..695b7b9637 100644 --- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java +++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java @@ -1,7 +1,7 @@ package brooklyn.entity.brooklynnode; -import static com.google.common.base.Preconditions.*; -import static java.lang.String.*; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.String.format; import java.io.ByteArrayInputStream; import java.io.File; @@ -27,8 +27,6 @@ public class BrooklynNodeSshDriver extends JavaSoftwareProcessSshDriver implements BrooklynNodeDriver { - private String expandedInstallDir; - public BrooklynNodeSshDriver(BrooklynNodeImpl entity, SshMachineLocation machine) { super(entity, machine); } @@ -51,11 +49,6 @@ private String getPidFile() { return "pid_java"; } - protected String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void install() { String uploadUrl = entity.getConfig(BrooklynNode.DISTRO_UPLOAD_URL); @@ -66,7 +59,7 @@ public void install() { DownloadResolver resolver = Entities.newDownloader(this, ImmutableMap.of("filename", "brooklyn-dist-"+getVersion()+"-dist.tar.gz")); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("brooklyn-%s", getVersion())); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("brooklyn-%s", getVersion()))); newScript("createInstallDir") .body.append("mkdir -p "+getInstallDir()) diff --git a/software/database/src/main/java/brooklyn/entity/database/mariadb/MariaDbSshDriver.java b/software/database/src/main/java/brooklyn/entity/database/mariadb/MariaDbSshDriver.java index 00d6b5930e..d2d5193ec8 100644 --- a/software/database/src/main/java/brooklyn/entity/database/mariadb/MariaDbSshDriver.java +++ b/software/database/src/main/java/brooklyn/entity/database/mariadb/MariaDbSshDriver.java @@ -43,8 +43,6 @@ public class MariaDbSshDriver extends AbstractSoftwareProcessSshDriver implement public static final Logger log = LoggerFactory.getLogger(MariaDbSshDriver.class); - private String expandedInstallDir; - public MariaDbSshDriver(MariaDbNodeImpl entity, SshMachineLocation machine) { super(entity, machine); @@ -94,17 +92,12 @@ public String getInstallFilename() { return String.format("mariadb-%s-%s.tar.gz", getVersion(), getOsTag()); } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void install() { DownloadResolver resolver = Entities.newDownloader(this, ImmutableMap.of("filename", getInstallFilename())); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir() + "/" + resolver.getUnpackedDirectoryName(format("mariadb-%s-%s", getVersion(), getOsTag())); + setExpandedInstallDir(getInstallDir() + "/" + resolver.getUnpackedDirectoryName(format("mariadb-%s-%s", getVersion(), getOsTag()))); List commands = new LinkedList(); commands.add(BashCommands.INSTALL_TAR); diff --git a/software/database/src/main/java/brooklyn/entity/database/mysql/MySqlSshDriver.java b/software/database/src/main/java/brooklyn/entity/database/mysql/MySqlSshDriver.java index 4ca1d8b532..9ddc9cc83a 100644 --- a/software/database/src/main/java/brooklyn/entity/database/mysql/MySqlSshDriver.java +++ b/software/database/src/main/java/brooklyn/entity/database/mysql/MySqlSshDriver.java @@ -1,7 +1,9 @@ package brooklyn.entity.database.mysql; import static brooklyn.util.GroovyJavaMethods.truth; -import static brooklyn.util.ssh.BashCommands.*; +import static brooklyn.util.ssh.BashCommands.commandsToDownloadUrlsAs; +import static brooklyn.util.ssh.BashCommands.installPackage; +import static brooklyn.util.ssh.BashCommands.ok; import static java.lang.String.format; import java.io.InputStream; @@ -43,8 +45,6 @@ public class MySqlSshDriver extends AbstractSoftwareProcessSshDriver implements public static final Logger log = LoggerFactory.getLogger(MySqlSshDriver.class); - private String expandedInstallDir; - public MySqlSshDriver(MySqlNodeImpl entity, SshMachineLocation machine) { super(entity, machine); @@ -92,17 +92,12 @@ public String getInstallFilename() { return String.format("mysql-%s-%s.tar.gz", getVersion(), getOsTag()); } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void install() { DownloadResolver resolver = Entities.newDownloader(this, ImmutableMap.of("filename", getInstallFilename())); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir() + "/" + resolver.getUnpackedDirectoryName(format("mysql-%s-%s", getVersion(), getOsTag())); + setExpandedInstallDir(getInstallDir() + "/" + resolver.getUnpackedDirectoryName(format("mysql-%s-%s", getVersion(), getOsTag()))); List commands = new LinkedList(); commands.add(BashCommands.INSTALL_TAR); diff --git a/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepSshDriver.java b/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepSshDriver.java index 74d1b5952e..c8de51f851 100644 --- a/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepSshDriver.java +++ b/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepSshDriver.java @@ -26,19 +26,12 @@ public class RubyRepSshDriver extends AbstractSoftwareProcessSshDriver implement public static final Logger log = LoggerFactory.getLogger(RubyRepSshDriver.class); - private String expandedInstallDir; - public RubyRepSshDriver(EntityLocal entity, SshMachineLocation machine) { super(entity, machine); entity.setAttribute(Attributes.LOG_FILE_LOCATION, getLogFileLocation()); } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - protected String getLogFileLocation() { return getRunDir() + "/log/rubyrep.log"; } @@ -60,7 +53,7 @@ public void install() { .failOnNonZeroResultCode() .execute(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("rubyrep-%s", getVersion())); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("rubyrep-%s", getVersion()))); } @Override diff --git a/software/messaging/src/main/java/brooklyn/entity/messaging/activemq/ActiveMQSshDriver.java b/software/messaging/src/main/java/brooklyn/entity/messaging/activemq/ActiveMQSshDriver.java index 7e4b681b96..b13d1538cd 100644 --- a/software/messaging/src/main/java/brooklyn/entity/messaging/activemq/ActiveMQSshDriver.java +++ b/software/messaging/src/main/java/brooklyn/entity/messaging/activemq/ActiveMQSshDriver.java @@ -19,8 +19,6 @@ public class ActiveMQSshDriver extends JavaSoftwareProcessSshDriver implements ActiveMQDriver { - private String expandedInstallDir; - public ActiveMQSshDriver(ActiveMQBrokerImpl entity, SshMachineLocation machine) { super(entity, machine); } @@ -39,17 +37,12 @@ public String getMirrorUrl() { return entity.getConfig(ActiveMQBroker.MIRROR_URL); } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("apache-activemq-%s", getVersion())); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("apache-activemq-%s", getVersion()))); List commands = new LinkedList(); commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)); diff --git a/software/messaging/src/main/java/brooklyn/entity/messaging/kafka/AbstractfKafkaSshDriver.java b/software/messaging/src/main/java/brooklyn/entity/messaging/kafka/AbstractfKafkaSshDriver.java index 60ccef02b2..9f127ffc66 100644 --- a/software/messaging/src/main/java/brooklyn/entity/messaging/kafka/AbstractfKafkaSshDriver.java +++ b/software/messaging/src/main/java/brooklyn/entity/messaging/kafka/AbstractfKafkaSshDriver.java @@ -33,7 +33,6 @@ import brooklyn.util.collections.MutableMap; import brooklyn.util.net.Networking; import brooklyn.util.ssh.BashCommands; -import brooklyn.util.text.Strings; import com.google.common.collect.ImmutableMap; @@ -56,28 +55,21 @@ public AbstractfKafkaSshDriver(EntityLocal entity, SshMachineLocation machine) { protected abstract String getProcessIdentifier(); - private String expandedInstallDir; - @Override protected String getLogFileLocation() { return getRunDir()+"/console.out"; } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("kafka-%s-src", getVersion())); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("kafka-%s-src", getVersion()))); List commands = new LinkedList(); commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)); commands.add(BashCommands.INSTALL_TAR); commands.add("tar xzfv "+saveAs); - commands.add("cd "+expandedInstallDir); + commands.add("cd "+getExpandedInstallDir()); commands.add("./sbt update"); commands.add("./sbt package"); if (isV08()) { diff --git a/software/messaging/src/main/java/brooklyn/entity/messaging/qpid/QpidSshDriver.java b/software/messaging/src/main/java/brooklyn/entity/messaging/qpid/QpidSshDriver.java index b6ecec154b..2029b84f75 100644 --- a/software/messaging/src/main/java/brooklyn/entity/messaging/qpid/QpidSshDriver.java +++ b/software/messaging/src/main/java/brooklyn/entity/messaging/qpid/QpidSshDriver.java @@ -23,8 +23,6 @@ public class QpidSshDriver extends JavaSoftwareProcessSshDriver implements QpidD private static final Logger log = LoggerFactory.getLogger(QpidSshDriver.class); - private String expandedInstallDir; - public QpidSshDriver(QpidBrokerImpl entity, SshMachineLocation machine) { super(entity, machine); } @@ -40,17 +38,12 @@ public QpidSshDriver(QpidBrokerImpl entity, SshMachineLocation machine) { public Integer getHttpManagementPort() { return entity.getAttribute(QpidBroker.HTTP_MANAGEMENT_PORT); } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("qpid-broker-%s", getVersion())); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("qpid-broker-%s", getVersion()))); List commands = new LinkedList(); commands.addAll( BashCommands.commandsToDownloadUrlsAs(urls, saveAs)); diff --git a/software/messaging/src/main/java/brooklyn/entity/messaging/rabbit/RabbitSshDriver.java b/software/messaging/src/main/java/brooklyn/entity/messaging/rabbit/RabbitSshDriver.java index 95f75543cd..d7dbd0464b 100644 --- a/software/messaging/src/main/java/brooklyn/entity/messaging/rabbit/RabbitSshDriver.java +++ b/software/messaging/src/main/java/brooklyn/entity/messaging/rabbit/RabbitSshDriver.java @@ -28,8 +28,6 @@ public class RabbitSshDriver extends AbstractSoftwareProcessSshDriver implements private static final Logger log = LoggerFactory.getLogger(RabbitSshDriver.class); - private String expandedInstallDir; - public RabbitSshDriver(RabbitBrokerImpl entity, SshMachineLocation machine) { super(entity, machine); } @@ -47,17 +45,12 @@ public RabbitBrokerImpl getEntity() { return (RabbitBrokerImpl) super.getEntity(); } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("rabbitmq_server-%s", getVersion())); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("rabbitmq_server-%s", getVersion()))); List commands = ImmutableList.builder() .add(installPackage(// NOTE only 'port' states the version of Erlang used, maybe remove this constraint? diff --git a/software/messaging/src/main/java/brooklyn/entity/messaging/storm/StormSshDriver.java b/software/messaging/src/main/java/brooklyn/entity/messaging/storm/StormSshDriver.java index 11416b9317..6200fd857a 100644 --- a/software/messaging/src/main/java/brooklyn/entity/messaging/storm/StormSshDriver.java +++ b/software/messaging/src/main/java/brooklyn/entity/messaging/storm/StormSshDriver.java @@ -33,12 +33,6 @@ public class StormSshDriver extends JavaSoftwareProcessSshDriver implements Stor private static final Logger log = LoggerFactory.getLogger(StormSshDriver.class); - protected String expandedInstallDir; - - public String getExpandedInstallDir() { - return expandedInstallDir; - } - public StormSshDriver(EntityLocal entity, SshMachineLocation machine) { super(entity, machine); } @@ -116,7 +110,8 @@ public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir() + "/" + resolver.getUnpackedDirectoryName(format("storm-%s", getVersion())); + setExpandedInstallDir(getInstallDir() + "/" + resolver.getUnpackedDirectoryName(format("storm-%s", getVersion()))); + ImmutableList.Builder commandsBuilder = ImmutableList. builder(); if (!getLocation().getOsDetails().isMac()) { commandsBuilder.add(BashCommands.installPackage(ImmutableMap.of( diff --git a/software/messaging/src/main/java/brooklyn/entity/zookeeper/ZooKeeperSshDriver.java b/software/messaging/src/main/java/brooklyn/entity/zookeeper/ZooKeeperSshDriver.java index 51d6277a5c..f20b99ac83 100644 --- a/software/messaging/src/main/java/brooklyn/entity/zookeeper/ZooKeeperSshDriver.java +++ b/software/messaging/src/main/java/brooklyn/entity/zookeeper/ZooKeeperSshDriver.java @@ -15,6 +15,12 @@ */ package brooklyn.entity.zookeeper; +import static java.lang.String.format; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + import brooklyn.entity.Entity; import brooklyn.entity.basic.Entities; import brooklyn.entity.basic.EntityInternal; @@ -28,33 +34,21 @@ import brooklyn.util.net.Networking; import brooklyn.util.ssh.BashCommands; import brooklyn.util.task.Tasks; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.reflect.TypeToken; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; - -import static java.lang.String.format; - public class ZooKeeperSshDriver extends JavaSoftwareProcessSshDriver implements ZooKeeperDriver { public ZooKeeperSshDriver(ZooKeeperNodeImpl entity, SshMachineLocation machine) { super(entity, machine); } - private String expandedInstallDir; - @Override protected String getLogFileLocation() { return getRunDir()+"/console.out"; } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - protected Map getPortMap() { return MutableMap.of("zookeeperPort", getZooKeeperPort()); } @@ -106,7 +100,7 @@ public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("zookeeper-%s", getVersion())); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("zookeeper-%s", getVersion()))); List commands = ImmutableList. builder() .addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)) diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraNodeSshDriver.java b/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraNodeSshDriver.java index 30a0c8b3d7..eb77abcb01 100644 --- a/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraNodeSshDriver.java +++ b/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraNodeSshDriver.java @@ -52,8 +52,6 @@ public class CassandraNodeSshDriver extends JavaSoftwareProcessSshDriver impleme private static final Logger log = LoggerFactory.getLogger(CassandraNodeSshDriver.class); - protected String expandedInstallDir; - public CassandraNodeSshDriver(CassandraNodeImpl entity, SshMachineLocation machine) { super(entity, machine); @@ -94,11 +92,6 @@ public String getEndpointSnitchName() { public String getMirrorUrl() { return entity.getConfig(CassandraNode.MIRROR_URL); } - protected String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - protected String getDefaultUnpackedDirectoryName() { return "apache-cassandra-"+getVersion(); } @@ -109,7 +102,7 @@ public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName(getDefaultUnpackedDirectoryName()); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName(getDefaultUnpackedDirectoryName())); List commands = ImmutableList.builder() .addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)) diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/MongoDBSshDriver.java b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/MongoDBSshDriver.java index fd4b7a651a..a5daca8748 100644 --- a/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/MongoDBSshDriver.java +++ b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/MongoDBSshDriver.java @@ -9,14 +9,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Joiner; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; - import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver; import brooklyn.entity.basic.Entities; -import brooklyn.entity.basic.EntityLocal; import brooklyn.entity.basic.lifecycle.ScriptHelper; import brooklyn.entity.drivers.downloads.DownloadResolver; import brooklyn.location.OsDetails; @@ -24,12 +18,15 @@ import brooklyn.util.net.Networking; import brooklyn.util.ssh.BashCommands; +import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + public class MongoDBSshDriver extends AbstractSoftwareProcessSshDriver implements MongoDBDriver { private static final Logger LOG = LoggerFactory.getLogger(MongoDBSshDriver.class); - private String expandedInstallDir; - public MongoDBSshDriver(MongoDBServerImpl entity, SshMachineLocation machine) { super(entity, machine); } @@ -39,11 +36,6 @@ public MongoDBServerImpl getEntity() { return MongoDBServerImpl.class.cast(super.getEntity()); } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - public String getDataDirectory() { String result = entity.getConfig(MongoDBServer.DATA_DIRECTORY); if (result!=null) return result; @@ -55,7 +47,7 @@ public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName(getBaseName()); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName(getBaseName())); List commands = new LinkedList(); commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)); diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/redis/RedisStoreSshDriver.java b/software/nosql/src/main/java/brooklyn/entity/nosql/redis/RedisStoreSshDriver.java index d8bc571ead..b2ae77f178 100644 --- a/software/nosql/src/main/java/brooklyn/entity/nosql/redis/RedisStoreSshDriver.java +++ b/software/nosql/src/main/java/brooklyn/entity/nosql/redis/RedisStoreSshDriver.java @@ -7,38 +7,28 @@ import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver; import brooklyn.entity.basic.Entities; import brooklyn.entity.drivers.downloads.DownloadResolver; -import brooklyn.entity.proxy.nginx.NginxController; import brooklyn.location.Location; import brooklyn.location.basic.SshMachineLocation; import brooklyn.util.collections.MutableMap; import brooklyn.util.ssh.BashCommands; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; /** * Start a {@link RedisStore} in a {@link Location} accessible over ssh. */ public class RedisStoreSshDriver extends AbstractSoftwareProcessSshDriver implements RedisStoreDriver { - private String expandedInstallDir; - public RedisStoreSshDriver(RedisStoreImpl entity, SshMachineLocation machine) { super(entity, machine); } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("redis-%s", getVersion())); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("redis-%s", getVersion()))); MutableMap installGccPackageFlags = MutableMap.of( "onlyifmissing", "gcc", diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/solr/SolrServerSshDriver.java b/software/nosql/src/main/java/brooklyn/entity/nosql/solr/SolrServerSshDriver.java index 1f6044a74b..a6a2892d91 100644 --- a/software/nosql/src/main/java/brooklyn/entity/nosql/solr/SolrServerSshDriver.java +++ b/software/nosql/src/main/java/brooklyn/entity/nosql/solr/SolrServerSshDriver.java @@ -41,8 +41,6 @@ public class SolrServerSshDriver extends AbstractSoftwareProcessSshDriver implem private static final Logger log = LoggerFactory.getLogger(SolrServerSshDriver.class); - private String expandedInstallDir; - public SolrServerSshDriver(SolrServerImpl entity, SshMachineLocation machine) { super(entity, machine); } @@ -57,18 +55,13 @@ public SolrServerSshDriver(SolrServerImpl entity, SshMachineLocation machine) { public String getPidFile() { return String.format("%s/solr.pid", getRunDir()); } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void install() { log.debug("Installing {}", entity); DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("solr-%s", getVersion())); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("solr-%s", getVersion()))); List commands = ImmutableList.builder() .addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)) diff --git a/software/osgi/src/main/java/brooklyn/entity/osgi/karaf/KarafSshDriver.java b/software/osgi/src/main/java/brooklyn/entity/osgi/karaf/KarafSshDriver.java index 57811efefc..31152849ee 100644 --- a/software/osgi/src/main/java/brooklyn/entity/osgi/karaf/KarafSshDriver.java +++ b/software/osgi/src/main/java/brooklyn/entity/osgi/karaf/KarafSshDriver.java @@ -18,8 +18,6 @@ public class KarafSshDriver extends JavaSoftwareProcessSshDriver implements KarafDriver { - protected String expandedInstallDir; - // TODO getJmxJavaSystemProperties(), don't set via JAVA_OPTS; set ourselves manually // (karaf reads from props files) // but do set "java.rmi.server.hostname" @@ -38,17 +36,12 @@ protected String getLogFileLocation() { return format("{%s}/data/karaf.out", getRunDir()); } - protected String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("apache-karaf-%s", getVersion())); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("apache-karaf-%s", getVersion()))); List commands = ImmutableList.builder() .addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)) diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractControllerImpl.java b/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractControllerImpl.java index fca6098970..36e1005d9b 100644 --- a/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractControllerImpl.java +++ b/software/webapp/src/main/java/brooklyn/entity/proxy/AbstractControllerImpl.java @@ -90,6 +90,21 @@ public void init() { }; } + @Override + protected void rebind() { + // TODO When persists policies, then can delete this code + Map policyFlags = MutableMap.of("name", "Controller targets tracker", + "sensorsToTrack", ImmutableSet.of(getConfig(HOSTNAME_SENSOR), getConfig(PORT_NUMBER_SENSOR))); + + serverPoolMemberTrackerPolicy = new AbstractMembershipTrackingPolicy(policyFlags) { + protected void onEntityChange(Entity member) { onServerPoolMemberChanged(member); } + protected void onEntityAdded(Entity member) { onServerPoolMemberChanged(member); } + protected void onEntityRemoved(Entity member) { onServerPoolMemberChanged(member); } + }; + + super.rebind(); + } + /** * Opportunity to do late-binding of the cluster that is being controlled. Must be called before start(). * Can pass in the 'serverPool'. diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java index 0b7cd96600..7d67511124 100644 --- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java +++ b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxSshDriver.java @@ -60,7 +60,6 @@ public class NginxSshDriver extends AbstractSoftwareProcessSshDriver implements public static final String NGINX_PID_FILE = "logs/nginx.pid"; private boolean customizationCompleted = false; - private String expandedInstallDir; public NginxSshDriver(NginxControllerImpl entity, SshMachineLocation machine) { super(entity, machine); @@ -98,11 +97,6 @@ public Integer getHttpPort() { return getEntity().getPort(); } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void rebind() { customizationCompleted = true; @@ -122,7 +116,7 @@ public void install() { DownloadResolver nginxResolver = mgmt().getEntityDownloadsManager().newDownloader(this); List nginxUrls = nginxResolver.getTargets(); String nginxSaveAs = nginxResolver.getFilename(); - expandedInstallDir = getInstallDir()+"/" + nginxResolver.getUnpackedDirectoryName(format("nginx-%s", getVersion())); + setExpandedInstallDir(getInstallDir()+"/" + nginxResolver.getUnpackedDirectoryName(format("nginx-%s", getVersion()))); boolean sticky = ((NginxController) entity).isSticky(); boolean isMac = getMachine().getOsDetails().isMac(); @@ -147,7 +141,7 @@ public void install() { this, "stickymodule", ImmutableMap.of("addonversion", stickyModuleVersion)); List stickyModuleUrls = stickyModuleResolver.getTargets(); String stickyModuleSaveAs = stickyModuleResolver.getFilename(); - String stickyModuleExpandedInstallDir = String.format("%s/src/%s", expandedInstallDir, + String stickyModuleExpandedInstallDir = String.format("%s/src/%s", getExpandedInstallDir(), stickyModuleResolver.getUnpackedDirectoryName("nginx-sticky-module-"+stickyModuleVersion)); List cmds = Lists.newArrayList(); diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss6SshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss6SshDriver.java index c02fb969cc..1cf3374489 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss6SshDriver.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss6SshDriver.java @@ -25,7 +25,6 @@ public class JBoss6SshDriver extends JavaWebAppSshDriver implements JBoss6Driver public static final String SERVER_TYPE = "standard"; public static final int DEFAULT_HTTP_PORT = 8080; private static final String PORT_GROUP_NAME = "ports-brooklyn"; - private String expandedInstallDir; public JBoss6SshDriver(JBoss6ServerImpl entity, SshMachineLocation machine) { super(entity, machine); @@ -56,13 +55,12 @@ protected String getClusterName() { return entity.getAttribute(JBoss6Server.CLUSTER_NAME); } - private String getExpandedInstallDir() { + // FIXME Should this pattern be used elsewhere? How? + @Override + public String getExpandedInstallDir() { // Ensure never returns null, so if stop called even if install/start was not then don't throw exception. - if (expandedInstallDir == null) { - return getInstallDir()+"/" + "jboss-"+getVersion(); - } else { - return expandedInstallDir; - } + String result = super.getExpandedInstallDir(); + return (result != null) ? result : getInstallDir()+"/" + "jboss-"+getVersion(); } @Override @@ -76,7 +74,7 @@ public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/" + resolver.getUnpackedDirectoryName("jboss-"+getVersion()); + setExpandedInstallDir(getInstallDir()+"/" + resolver.getUnpackedDirectoryName("jboss-"+getVersion())); // Note the -o option to unzip, to overwrite existing files without warning. // The JBoss zip file contains lgpl.txt (at least) twice and the prompt to diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7SshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7SshDriver.java index 298e89eac8..e13d8bc7b6 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7SshDriver.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7SshDriver.java @@ -1,6 +1,6 @@ package brooklyn.entity.webapp.jboss; -import static java.lang.String.*; +import static java.lang.String.format; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -43,8 +43,6 @@ public class JBoss7SshDriver extends JavaWebAppSshDriver implements JBoss7Driver private static final String MANAGEMENT_REALM = "ManagementRealm"; - private String expandedInstallDir; - public JBoss7SshDriver(JBoss7ServerImpl entity, SshMachineLocation machine) { super(entity, machine); } @@ -108,17 +106,12 @@ private Integer getDeploymentTimeoutSecs() { return entity.getConfig(JBoss7Server.DEPLOYMENT_TIMEOUT); } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("jboss-as-%s", getVersion())); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("jboss-as-%s", getVersion()))); List commands = new LinkedList(); commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)); diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/jetty/Jetty6SshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/jetty/Jetty6SshDriver.java index f3f4e99473..de21af3e18 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/jetty/Jetty6SshDriver.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/jetty/Jetty6SshDriver.java @@ -17,8 +17,6 @@ public class Jetty6SshDriver extends JavaWebAppSshDriver implements Jetty6Driver { - private String expandedInstallDir; - public Jetty6SshDriver(Jetty6ServerImpl entity, SshMachineLocation machine) { super(entity, machine); } @@ -33,17 +31,12 @@ protected String getDeploySubdir() { return "webapps"; } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName("jetty-"+getVersion()); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName("jetty-"+getVersion())); List commands = new LinkedList(); commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)); diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7SshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7SshDriver.java index 2067e8580a..bb8585a4de 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7SshDriver.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/tomcat/Tomcat7SshDriver.java @@ -1,6 +1,6 @@ package brooklyn.entity.webapp.tomcat; -import static java.lang.String.*; +import static java.lang.String.format; import java.util.LinkedList; import java.util.List; @@ -16,8 +16,6 @@ public class Tomcat7SshDriver extends JavaWebAppSshDriver implements Tomcat7Driver { - private String expandedInstallDir; - public Tomcat7SshDriver(TomcatServerImpl entity, SshMachineLocation machine) { super(entity, machine); } @@ -34,17 +32,12 @@ protected Integer getShutdownPort() { return entity.getAttribute(TomcatServerImpl.SHUTDOWN_PORT); } - private String getExpandedInstallDir() { - if (expandedInstallDir == null) throw new IllegalStateException("expandedInstallDir is null; most likely install was not called"); - return expandedInstallDir; - } - @Override public void install() { DownloadResolver resolver = Entities.newDownloader(this); List urls = resolver.getTargets(); String saveAs = resolver.getFilename(); - expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName("apache-tomcat-"+getVersion()); + setExpandedInstallDir(getInstallDir()+"/"+resolver.getUnpackedDirectoryName("apache-tomcat-"+getVersion())); List commands = new LinkedList(); commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)); diff --git a/usage/cli/src/main/java/brooklyn/cli/Main.java b/usage/cli/src/main/java/brooklyn/cli/Main.java index d968bc098b..6363a8abcc 100644 --- a/usage/cli/src/main/java/brooklyn/cli/Main.java +++ b/usage/cli/src/main/java/brooklyn/cli/Main.java @@ -2,7 +2,15 @@ import groovy.lang.GroovyClassLoader; import groovy.lang.GroovyShell; +import io.airlift.command.Cli; +import io.airlift.command.Cli.CliBuilder; +import io.airlift.command.Command; +import io.airlift.command.Help; +import io.airlift.command.Option; +import io.airlift.command.OptionType; +import io.airlift.command.ParseException; +import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.InetAddress; @@ -12,13 +20,6 @@ import javax.inject.Inject; -import io.airlift.command.Cli; -import io.airlift.command.Cli.CliBuilder; -import io.airlift.command.Command; -import io.airlift.command.Help; -import io.airlift.command.Option; -import io.airlift.command.OptionType; -import io.airlift.command.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,6 +35,7 @@ import brooklyn.entity.trait.Startable; import brooklyn.launcher.BrooklynLauncher; import brooklyn.launcher.BrooklynServerDetails; +import brooklyn.launcher.PersistMode; import brooklyn.management.ManagementContext; import brooklyn.util.ResourceUtils; import brooklyn.util.net.Networking; @@ -203,6 +205,16 @@ public static class LaunchCommand extends BrooklynCommand { description = "After the application gets started, brooklyn will wait for a key press to stop it.") public boolean stopOnKeyPress = false; + // TODO currently defaults to disabled; want it to default to on, when we're ready + @Option(name = { "--persist" }, allowedValues = { "disabled", "auto", "rebind", "clean" }, + title = "persistance mode", + description = "the persistence mode") + public String persist = "disabled"; + + @Option(name = { "--persistenceDir" }, title = "persistence dir", + description = "the directory to read/write persisted state") + public String persistenceDir; + @Override public Void call() throws Exception { if (log.isDebugEnabled()) log.debug("Invoked launch command {}", this); @@ -226,7 +238,32 @@ public Void call() throws Exception { System.err.println("Locations specified without any applications; ignoring locations"); } } + + PersistMode persistMode; + if (Strings.isBlank(persist)) { + throw new IllegalStateException("Persist mode must not be blank"); + } else if (persist.equalsIgnoreCase("disabled")) { + persistMode = PersistMode.DISABLED; + } else if (persist.equalsIgnoreCase("auto")) { + persistMode = PersistMode.AUTO; + } else if (persist.equalsIgnoreCase("rebind")) { + persistMode = PersistMode.REBIND; + } else if (persist.equalsIgnoreCase("clean")) { + persistMode = PersistMode.CLEAN; + } else { + throw new IllegalStateException("Illegal persist setting: "+persist); + } + if (persistMode == PersistMode.DISABLED) { + if (Strings.isNonBlank(persistenceDir)) { + throw new IllegalStateException("Cannot specify peristanceDir when persist is disabled"); + } + } else { + if (Strings.isBlank(persistenceDir)) { + persistenceDir = "brooklyn-persisted-state"+File.separator+"data"+File.separator; + } + } + ResourceUtils utils = ResourceUtils.create(this); ClassLoader parent = utils.getLoader(); GroovyClassLoader loader = new GroovyClassLoader(parent); @@ -261,7 +298,13 @@ public Void call() throws Exception { throw new IllegalStateException("Unexpected application type "+(loadedApp==null ? null : loadedApp.getClass())+", for app "+loadedApp); } } - + launcher.persistMode(persistMode); + if (persistMode != PersistMode.DISABLED) { + if (Strings.isBlank(persistenceDir)) { + persistenceDir = "/some/default/somewhere?"; + } + launcher.persistenceDir(persistenceDir); + } // Launch server try { @@ -394,11 +437,14 @@ public ToStringHelper string() { .add("script", script) .add("location", locations) .add("port", port) + .add("bindAddress", bindAddress) .add("noConsole", noConsole) .add("noConsoleSecurity", noConsoleSecurity) .add("noShutdownOnExit", noShutdownOnExit) .add("stopOnKeyPress", stopOnKeyPress) - .add("localBrooklynProperties", localBrooklynProperties); + .add("localBrooklynProperties", localBrooklynProperties) + .add("persist", persist) + .add("persistenceDir", persistenceDir); } } diff --git a/usage/cli/src/test/java/brooklyn/cli/CliTest.java b/usage/cli/src/test/java/brooklyn/cli/CliTest.java index 4b74995181..2f73fe6ded 100644 --- a/usage/cli/src/test/java/brooklyn/cli/CliTest.java +++ b/usage/cli/src/test/java/brooklyn/cli/CliTest.java @@ -4,6 +4,8 @@ import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import groovy.lang.GroovyClassLoader; +import io.airlift.command.Cli; +import io.airlift.command.ParseException; import java.io.File; import java.util.Collection; @@ -12,8 +14,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; -import io.airlift.command.Cli; -import io.airlift.command.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.AfterMethod; @@ -182,16 +182,46 @@ public void testWaitsForInterrupt() throws Exception { @Test public void testLaunchCommandParsesArgs() throws ParseException { - Cli cli = Main.buildCli(); - BrooklynCommand command = cli.parse("launch", "--app", "my.App", "--location", "localhost"); + BrooklynCommand command = Main.buildCli().parse("launch", + "--app", "my.App", + "--location", "localhost", + "--port", "1234", + "--bindAddress", "myhostname", + "--noConsole", "--noConsoleSecurity", "--noShutdownOnExit", "--stopOnKeyPress", + "--localBrooklynProperties", "/path/to/myprops", + "--persist", "rebind", "--persistenceDir", "/path/to/mypersist"); assertTrue(command instanceof LaunchCommand, ""+command); String details = command.toString(); assertTrue(details.contains("app=my.App"), details); assertTrue(details.contains("script=null"), details); assertTrue(details.contains("location=localhost"), details); + assertTrue(details.contains("port=1234"), details); + assertTrue(details.contains("bindAddress=myhostname"), details); + assertTrue(details.contains("noConsole=true"), details); + assertTrue(details.contains("noConsoleSecurity=true"), details); + assertTrue(details.contains("noShutdownOnExit=true"), details); + assertTrue(details.contains("stopOnKeyPress=true"), details); + assertTrue(details.contains("localBrooklynProperties=/path/to/myprops"), details); + assertTrue(details.contains("persist=rebind"), details); + assertTrue(details.contains("persistenceDir=/path/to/mypersist"), details); + } + + @Test + public void testLaunchCommandUsesDefaults() throws ParseException { + BrooklynCommand command = Main.buildCli().parse("launch"); + assertTrue(command instanceof LaunchCommand, ""+command); + String details = command.toString(); + assertTrue(details.contains("app=null"), details); + assertTrue(details.contains("script=null"), details); + assertTrue(details.contains("location=null"), details); assertTrue(details.contains("port=8081"), details); assertTrue(details.contains("noConsole=false"), details); + assertTrue(details.contains("noConsoleSecurity=false"), details); assertTrue(details.contains("noShutdownOnExit=false"), details); + assertTrue(details.contains("stopOnKeyPress=false"), details); + assertTrue(details.contains("localBrooklynProperties=null"), details); + assertTrue(details.contains("persist=disabled"), details); + assertTrue(details.contains("persistenceDir=null"), details); } @Test diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java index 9dc51a89df..275bfe3bbe 100644 --- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java +++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java @@ -4,11 +4,17 @@ import io.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer; import java.io.Closeable; +import java.io.File; +import java.io.IOException; import java.net.InetAddress; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,6 +27,9 @@ import brooklyn.entity.basic.Entities; import brooklyn.entity.basic.StartableApplication; import brooklyn.entity.proxying.EntitySpec; +import brooklyn.entity.rebind.RebindManager; +import brooklyn.entity.rebind.RebindManagerImpl; +import brooklyn.entity.rebind.persister.BrooklynMementoPersisterToMultiFile; import brooklyn.entity.trait.Startable; import brooklyn.location.Location; import brooklyn.location.PortRange; @@ -28,13 +37,18 @@ import brooklyn.management.ManagementContext; import brooklyn.management.internal.LocalManagementContext; import brooklyn.management.internal.ManagementContextInternal; +import brooklyn.mementos.BrooklynMementoPersister; import brooklyn.rest.BrooklynWebConfig; import brooklyn.rest.security.BrooklynPropertiesSecurityFilter; import brooklyn.util.exceptions.CompoundRuntimeException; import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.exceptions.RuntimeInterruptedException; import brooklyn.util.net.Networking; import brooklyn.util.stream.Streams; +import brooklyn.util.time.Duration; +import brooklyn.util.time.Time; +import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -78,6 +92,9 @@ public static BrooklynLauncher newInstance() { private Map webconsoleFlags = Maps.newLinkedHashMap(); private Boolean skipSecurityFilter = null; private boolean shutdownOnExit = true; + private PersistMode persistMode = PersistMode.DISABLED; + private File persistenceDir; + private Duration persistPeriod = Duration.ONE_SECOND; private volatile BrooklynWebServer webServer; @@ -272,6 +289,25 @@ public BrooklynLauncher webapp(String contextPath, String warUrl) { return this; } + public BrooklynLauncher persistMode(PersistMode persistMode) { + this.persistMode = persistMode; + return this; + } + + public BrooklynLauncher persistenceDir(String persistenceDir) { + return persistenceDir(new File(persistenceDir)); + } + + public BrooklynLauncher persistenceDir(File persistenceDir) { + this.persistenceDir = persistenceDir; + return this; + } + + public BrooklynLauncher persistPeriod(Duration persistPeriod) { + this.persistPeriod = persistPeriod; + return this; + } + /** * Starts the web server (with web console) and Brooklyn applications, as per the specifications configured. * @return An object containing details of the web server and the management context. @@ -310,6 +346,7 @@ public BrooklynLauncher start() { startWebApps(); } + initPersistence(); createApps(); startApps(); @@ -346,6 +383,77 @@ protected void startWebApps() { } } + protected void initPersistence() { + try { + if (persistMode == PersistMode.DISABLED) { + LOG.info("Persistence disabled"); + + } else { + if (persistenceDir == null) throw new IllegalStateException("Persistence dir must be set with persistence mode "+persistMode); + String persistencePath = persistenceDir.getAbsolutePath(); + + boolean rebinding; + switch (persistMode) { + case CLEAN: + if (persistenceDir.exists()) { + File old = moveDirectory(persistenceDir); + LOG.info("Clean start using "+persistencePath+"; moved old directory to "+old.getAbsolutePath()); + } else { + LOG.info("Clean start using "+persistencePath+"; no pre-existing persisted data"); + } + rebinding = false; + break; + case REBIND: + if (persistenceDir.exists() && persistenceDir.isDirectory() && persistenceDir.canRead() && persistenceDir.canWrite() && !isMementoDirEmpty(persistenceDir)) { + File backup = backupDirectory(persistenceDir); + LOG.info("Rebind using "+persistencePath+"; backed up directory to "+backup.getAbsolutePath()); + } else { + throw new IllegalStateException("Cannot rebind to persistence directory "+persistenceDir+" because "+ + (persistenceDir.exists() ? "does not exist" : + (!persistenceDir.isDirectory() ? "not a directory" : + (persistenceDir.canRead() ? "not readable" : + (persistenceDir.canWrite() ? "not writable" : + (isMementoDirEmpty(persistenceDir) ? "directory is empty" : "unknown reason")))))); + } + rebinding = true; + break; + case AUTO: + if (persistenceDir.exists() && !isMementoDirEmpty(persistenceDir)) { + File backup = backupDirectory(persistenceDir); + LOG.info("Auto rebind using "+persistencePath+"; backed up directory to "+backup.getAbsolutePath()); + rebinding = true; + } else { + rebinding = false; + LOG.info("Auto fresh using "+persistencePath+"; no pre-existing persisted data"); + } + break; + default: + throw new IllegalStateException("Unexpected persist mode "+persistMode+"; modified during initialization?!"); + }; + + if (!persistenceDir.exists()) { + boolean success = persistenceDir.mkdirs(); + if (!success) { + throw new IllegalStateException("Failed to create persistence directory "+persistenceDir); + } + } + + RebindManager rebindManager = managementContext.getRebindManager(); + BrooklynMementoPersister persister = new BrooklynMementoPersisterToMultiFile(persistenceDir, managementContext.getCatalog().getRootClassLoader()); + ((RebindManagerImpl)rebindManager).setPeriodicPersistPeriod(persistPeriod); + rebindManager.setPersister(persister); + + if (rebinding) { + ClassLoader classLoader = managementContext.getCatalog().getRootClassLoader(); + rebindManager.rebind(classLoader); + } + rebindManager.start(); + } + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + protected void createApps() { for (ApplicationBuilder appBuilder : appBuildersToManage) { StartableApplication app = appBuilder.manage(managementContext); @@ -396,6 +504,22 @@ public void terminate() { LOG.warn("Error stopping web-server; continuing with termination", e); } } + + // TODO Do we want to do this as part of managementContext.terminate, so after other threads are terminated etc? + // Otherwise the app can change between this persist and the terminate. + if (persistMode != PersistMode.DISABLED) { + try { + Stopwatch stopwatch = Stopwatch.createStarted(); + managementContext.getRebindManager().waitForPendingComplete(10, TimeUnit.SECONDS); + LOG.info("Finished waiting for persist; took "+Time.makeTimeStringRounded(stopwatch)); + } catch (RuntimeInterruptedException e) { + Thread.currentThread().interrupt(); // keep going with shutdown + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // keep going with shutdown + } catch (TimeoutException e) { + LOG.warn("Timeout after 10 seconds waiting for persistance to write all data; continuing"); + } + } if (managementContext instanceof ManagementContextInternal) { ((ManagementContextInternal)managementContext).terminate(); @@ -407,4 +531,46 @@ public void terminate() { } } } + + static File backupDirectory(File dir) throws IOException, InterruptedException { + File parentDir = dir.getParentFile(); + String simpleName = dir.getName(); + String timestamp = new SimpleDateFormat("yyyy-MM-dd-hhmm-ss").format(new Date()); + File backupDir = new File(parentDir, simpleName+"-"+timestamp+".bak"); + + String cmd = "cp -R "+dir.getAbsolutePath()+" "+backupDir.getAbsolutePath(); + Process proc = Runtime.getRuntime().exec(cmd); + proc.waitFor(); + if (proc.exitValue() != 0) { + throw new IllegalStateException("Error backing up directory, with command "+cmd); + } + return backupDir; + } + + static File moveDirectory(File dir) throws InterruptedException, IOException { + File parentDir = dir.getParentFile(); + String simpleName = dir.getName(); + String timestamp = new SimpleDateFormat("yyyy-MM-dd-hhmm-ss").format(new Date()); + File newDir = new File(parentDir, simpleName+"-"+timestamp+".old"); + + String cmd = "mv "+dir.getAbsolutePath()+" "+newDir.getAbsolutePath(); + Process proc = Runtime.getRuntime().exec(cmd); + proc.waitFor(); + if (proc.exitValue() != 0) { + throw new IllegalStateException("Error moving directory, with command "+cmd); + } + return newDir; + } + + /** + * Empty if directory is entirely empty, or only contains empty directories. + */ + static boolean isMementoDirEmpty(File dir) { + if (!dir.exists()) return false; + for (File sub : dir.listFiles()) { + if (sub.isFile()) return false; + if (sub.isDirectory() && sub.listFiles().length > 0) return false; + } + return true; + } } diff --git a/usage/launcher/src/main/java/brooklyn/launcher/PersistMode.java b/usage/launcher/src/main/java/brooklyn/launcher/PersistMode.java new file mode 100644 index 0000000000..405bfe7482 --- /dev/null +++ b/usage/launcher/src/main/java/brooklyn/launcher/PersistMode.java @@ -0,0 +1,8 @@ +package brooklyn.launcher; + +public enum PersistMode { + DISABLED, + AUTO, + REBIND, + CLEAN; +} diff --git a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherTest.java b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherTest.java index f18708a8af..83c67203e3 100644 --- a/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherTest.java +++ b/usage/launcher/src/test/java/brooklyn/launcher/BrooklynLauncherTest.java @@ -1,6 +1,7 @@ package brooklyn.launcher; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertSame; @@ -15,29 +16,38 @@ import brooklyn.config.BrooklynProperties; import brooklyn.entity.Application; import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.EntityPredicates; +import brooklyn.entity.basic.StartableApplication; import brooklyn.entity.proxying.EntitySpec; +import brooklyn.entity.rebind.RebindTestUtils; import brooklyn.location.Location; import brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import brooklyn.management.ManagementContext; import brooklyn.management.internal.LocalManagementContext; +import brooklyn.test.Asserts; import brooklyn.test.HttpTestUtils; import brooklyn.test.entity.TestApplication; import brooklyn.test.entity.TestApplicationImpl; +import brooklyn.util.time.Duration; import com.google.common.base.Charsets; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.io.Files; public class BrooklynLauncherTest { private BrooklynLauncher launcher; + private File persistenceDir; @AfterMethod(alwaysRun=true) public void tearDown() throws Exception { if (launcher != null) launcher.terminate(); + if (persistenceDir != null) RebindTestUtils.deleteMementoDir(persistenceDir); } - + // Integration because takes a few seconds to start web-console @Test(groups="Integration") public void testStartsWebServerOnExpectectedPort() throws Exception { @@ -177,11 +187,143 @@ public void testReloadBrooklynPropertiesFromFile() throws Exception { } } + @Test + public void testRebindsToExistingApp() throws Exception { + persistenceDir = Files.createTempDir(); + populatePersistenceDir(persistenceDir, EntitySpec.create(TestApplication.class).displayName("myorig")); + assertFalse(BrooklynLauncher.isMementoDirEmpty(persistenceDir)); + + // Rebind to the app we started last time + launcher = BrooklynLauncher.newInstance() + .webconsole(false) + .persistMode(PersistMode.REBIND) + .persistenceDir(persistenceDir) + .persistPeriod(Duration.millis(10)) + .start(); + + ManagementContext managementContext = launcher.getServerDetails().getManagementContext(); + assertOnlyApp(managementContext, TestApplication.class); + assertNotNull(Iterables.find(managementContext.getApplications(), EntityPredicates.displayNameEqualTo("myorig"), null), "apps="+managementContext.getApplications()); + } + + @Test + public void testRebindCanAddNewApps() throws Exception { + persistenceDir = Files.createTempDir(); + populatePersistenceDir(persistenceDir, EntitySpec.create(TestApplication.class).displayName("myorig")); + assertFalse(BrooklynLauncher.isMementoDirEmpty(persistenceDir)); + + // Rebind to the app we started last time + launcher = BrooklynLauncher.newInstance() + .webconsole(false) + .persistMode(PersistMode.REBIND) + .persistenceDir(persistenceDir) + .persistPeriod(Duration.millis(10)) + .application(EntitySpec.create(TestApplication.class).displayName("mynew")) + .start(); + + // New app was added, and orig app was rebound + ManagementContext managementContext = launcher.getServerDetails().getManagementContext(); + assertEquals(managementContext.getApplications().size(), 2, "apps="+managementContext.getApplications()); + assertNotNull(Iterables.find(managementContext.getApplications(), EntityPredicates.displayNameEqualTo("mynew"), null), "apps="+managementContext.getApplications()); + + // And subsequently can create new apps + StartableApplication app3 = launcher.getServerDetails().getManagementContext().getEntityManager().createEntity( + EntitySpec.create(TestApplication.class).displayName("mynew2")); + app3.start(ImmutableList.of()); + } + + @Test + public void testAutoRebindsToExistingApp() throws Exception { + EntitySpec appSpec = EntitySpec.create(TestApplication.class); + persistenceDir = Files.createTempDir(); + populatePersistenceDir(persistenceDir, appSpec); + + // Auto will rebind if the dir exists + launcher = BrooklynLauncher.newInstance() + .webconsole(false) + .persistMode(PersistMode.AUTO) + .persistenceDir(persistenceDir) + .persistPeriod(Duration.millis(10)) + .persistPeriod(Duration.millis(10)) + .start(); + + assertOnlyApp(launcher.getServerDetails().getManagementContext(), TestApplication.class); + } + + @Test + public void testCleanDoesNotRebindToExistingApp() throws Exception { + EntitySpec appSpec = EntitySpec.create(TestApplication.class); + persistenceDir = Files.createTempDir(); + populatePersistenceDir(persistenceDir, appSpec); + + // Auto will rebind if the dir exists + launcher = BrooklynLauncher.newInstance() + .webconsole(false) + .persistMode(PersistMode.CLEAN) + .persistenceDir(persistenceDir) + .persistPeriod(Duration.millis(10)) + .start(); + + ManagementContext managementContext = launcher.getServerDetails().getManagementContext(); + assertTrue(managementContext.getApplications().isEmpty(), "apps="+managementContext.getApplications()); + } + + @Test + public void testAutoCreatesNewIfEmptyDir() throws Exception { + persistenceDir = Files.createTempDir(); + + // Auto will rebind if the dir exists + launcher = BrooklynLauncher.newInstance() + .webconsole(false) + .persistMode(PersistMode.AUTO) + .persistenceDir(persistenceDir) + .persistPeriod(Duration.millis(10)) + .application(EntitySpec.create(TestApplication.class)) + .start(); + + assertOnlyApp(launcher.getServerDetails().getManagementContext(), TestApplication.class); + assertMementoDirNonEmptyEventually(persistenceDir); + } + + private void assertMementoDirNonEmptyEventually(final File dir) { + Asserts.succeedsEventually(ImmutableMap.of("timeout", Duration.TEN_SECONDS), new Runnable() { + @Override public void run() { + assertFalse(BrooklynLauncher.isMementoDirEmpty(dir)); + }}); + } + + @Test(expectedExceptions=IllegalStateException.class) + public void testRebindFailsIfNoDir() throws Exception { + launcher = BrooklynLauncher.newInstance() + .webconsole(false) + .persistMode(PersistMode.REBIND) + .persistenceDir("/path/does/not/exist") + .start(); + } + + private void populatePersistenceDir(File dir, EntitySpec appSpec) throws Exception { + launcher = BrooklynLauncher.newInstance() + .webconsole(false) + .persistMode(PersistMode.CLEAN) + .persistenceDir(dir) + .persistPeriod(Duration.millis(10)) + .application(appSpec) + .start(); + launcher.terminate(); + launcher = null; + assertFalse(BrooklynLauncher.isMementoDirEmpty(dir)); + } + private void assertOnlyApp(BrooklynLauncher launcher, Class expectedType) { assertEquals(launcher.getApplications().size(), 1, "apps="+launcher.getApplications()); assertNotNull(Iterables.find(launcher.getApplications(), Predicates.instanceOf(TestApplication.class), null), "apps="+launcher.getApplications()); } + private void assertOnlyApp(ManagementContext managementContext, Class expectedType) { + assertEquals(managementContext.getApplications().size(), 1, "apps="+managementContext.getApplications()); + assertNotNull(Iterables.find(managementContext.getApplications(), Predicates.instanceOf(TestApplication.class), null), "apps="+managementContext.getApplications()); + } + private void assertOnlyLocation(Application app, Class expectedType) { assertEquals(app.getLocations().size(), 1, "locs="+app.getLocations()); assertNotNull(Iterables.find(app.getLocations(), Predicates.instanceOf(LocalhostMachineProvisioningLocation.class), null), "locs="+app.getLocations());