You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by sj...@apache.org on 2015/02/10 15:03:21 UTC
[2/3] incubator-brooklyn git commit: Crate entity improvements
Crate entity improvements
* Allows configuration via file
* Exposes more sensors. Uses SERVER_OK in SERVICE_NOT_UP_INDICATORS.
* Adds integration tests
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/743fd2d0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/743fd2d0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/743fd2d0
Branch: refs/heads/master
Commit: 743fd2d008d474157a0685357a8805d64b70be78
Parents: 35588b8
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Tue Jan 27 10:54:50 2015 +0000
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Tue Jan 27 10:54:50 2015 +0000
----------------------------------------------------------------------
software/database/pom.xml | 1 +
.../entity/database/crate/CrateNode.java | 62 +++++++++++++--
.../entity/database/crate/CrateNodeDriver.java | 18 +++++
.../entity/database/crate/CrateNodeImpl.java | 82 ++++++++++++++------
.../database/crate/CrateNodeSshDriver.java | 53 ++++++++++---
.../brooklyn/entity/database/crate/crate.yaml | 28 +++++++
.../crate/CrateNodeIntegrationTest.java | 64 +++++++++++++++
7 files changed, 269 insertions(+), 39 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/743fd2d0/software/database/pom.xml
----------------------------------------------------------------------
diff --git a/software/database/pom.xml b/software/database/pom.xml
index 89090a1..593e923 100644
--- a/software/database/pom.xml
+++ b/software/database/pom.xml
@@ -46,6 +46,7 @@
the given components. These are files "without any degree of creativity" from the
perspective of the Brooklyn/Apache contribution.
-->
+ <exclude>src/main/resources/brooklyn/entity/database/crate/crate.yaml</exclude>
<exclude>src/main/resources/brooklyn/entity/database/mariadb/my.cnf</exclude>
<exclude>src/main/resources/brooklyn/entity/database/mysql/mysql.conf</exclude>
<exclude>src/main/resources/brooklyn/entity/database/postgresql/postgresql.conf</exclude>
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/743fd2d0/software/database/src/main/java/brooklyn/entity/database/crate/CrateNode.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/brooklyn/entity/database/crate/CrateNode.java b/software/database/src/main/java/brooklyn/entity/database/crate/CrateNode.java
index 4ee28c3..7f1dc3d 100644
--- a/software/database/src/main/java/brooklyn/entity/database/crate/CrateNode.java
+++ b/software/database/src/main/java/brooklyn/entity/database/crate/CrateNode.java
@@ -1,9 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
package brooklyn.entity.database.crate;
import brooklyn.config.ConfigKey;
import brooklyn.entity.basic.Attributes;
import brooklyn.entity.basic.ConfigKeys;
import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.database.DatastoreMixins.DatastoreCommon;
import brooklyn.entity.java.UsesJava;
import brooklyn.entity.java.UsesJavaMXBeans;
import brooklyn.entity.java.UsesJmx;
@@ -11,11 +30,13 @@ import brooklyn.entity.proxying.ImplementedBy;
import brooklyn.event.AttributeSensor;
import brooklyn.event.basic.AttributeSensorAndConfigKey;
import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
+import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
import brooklyn.event.basic.Sensors;
+import brooklyn.location.basic.PortRanges;
import brooklyn.util.flags.SetFromFlag;
@ImplementedBy(CrateNodeImpl.class)
-public interface CrateNode extends SoftwareProcess, UsesJava,UsesJmx, UsesJavaMXBeans {
+public interface CrateNode extends SoftwareProcess, UsesJava,UsesJmx, UsesJavaMXBeans, DatastoreCommon {
@SetFromFlag("version")
ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION,
@@ -26,15 +47,44 @@ public interface CrateNode extends SoftwareProcess, UsesJava,UsesJmx, UsesJavaMX
Attributes.DOWNLOAD_URL,
"https://cdn.crate.io/downloads/releases/crate-${version}.tar.gz");
- AttributeSensor<String> MANAGEMENT_URI = Sensors.newStringSensor(
+ @SetFromFlag("serverConfig")
+ BasicAttributeSensorAndConfigKey<String> SERVER_CONFIG_URL = new BasicAttributeSensorAndConfigKey.StringAttributeSensorAndConfigKey(
+ "crate.serverConfig", "A URL of a YAML file to use to configure the server",
+ "classpath://brooklyn/entity/database/crate/crate.yaml");
+
+ @SetFromFlag("port")
+ public static final PortAttributeSensorAndConfigKey CRATE_PORT = new PortAttributeSensorAndConfigKey(
+ "crate.port", "The port for node-to-node communication", PortRanges.fromString("4300+"));
+
+ @SetFromFlag("httpPort")
+ public static final PortAttributeSensorAndConfigKey CRATE_HTTP_PORT = new PortAttributeSensorAndConfigKey(
+ "crate.httpPort", "The port for HTTP traffic", PortRanges.fromString("4200+"));
+
+ AttributeSensor<String> MANAGEMENT_URL = Sensors.newStringSensor(
"crate.managementUri", "The address at which the Crate server listens");
AttributeSensor<String> SERVER_NAME = Sensors.newStringSensor(
- "crate.serverName", "The name of the server");
+ "crate.server.name", "The name of the server");
+
+ AttributeSensor<Boolean> SERVER_OK = Sensors.newBooleanSensor(
+ "crate.server.ok", "True if the server reports thus");
AttributeSensor<Integer> SERVER_STATUS = Sensors.newIntegerSensor(
- "create.serverStatus", "The status of the server");
+ "crate.server.status", "The status of the server");
AttributeSensor<String> SERVER_BUILD_TIMESTAMP = Sensors.newStringSensor(
- "create.serverBuildTimestamp", "The Timestamp of the server build");
-}
+ "crate.server.buildTimestamp", "The timestamp of the server build");
+
+ AttributeSensor<String> SERVER_BUILD_HASH = Sensors.newStringSensor(
+ "crate.server.buildHash", "The build hash of the server");
+
+ AttributeSensor<Boolean> SERVER_IS_BUILD_SNAPSHOT = Sensors.newBooleanSensor(
+ "crate.server.isBuildSnapshot", "True if the server reports it is a snapshot build");
+
+ AttributeSensor<String> SERVER_LUCENE_VERSION = Sensors.newStringSensor(
+ "crate.server.luceneVersion", "The Lucene version of the server");
+
+ AttributeSensor<String> SERVER_ES_VERSION = Sensors.newStringSensor(
+ "crate.server.esVersion", "The ES version of the server");
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/743fd2d0/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeDriver.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeDriver.java b/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeDriver.java
index af35858..225db07 100644
--- a/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeDriver.java
+++ b/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeDriver.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
package brooklyn.entity.database.crate;
import brooklyn.entity.java.JavaSoftwareProcessDriver;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/743fd2d0/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeImpl.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeImpl.java b/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeImpl.java
index 72a11e6..5dcfc30 100644
--- a/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeImpl.java
+++ b/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeImpl.java
@@ -1,22 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
package brooklyn.entity.database.crate;
+import brooklyn.config.render.RendererHints;
+import brooklyn.enricher.Enrichers;
+import brooklyn.entity.basic.Attributes;
import brooklyn.entity.basic.SoftwareProcessImpl;
import brooklyn.entity.java.JavaAppUtils;
import brooklyn.event.feed.http.HttpFeed;
import brooklyn.event.feed.http.HttpPollConfig;
import brooklyn.event.feed.http.HttpValueFunctions;
import brooklyn.event.feed.jmx.JmxFeed;
-
+import brooklyn.util.guava.Functionals;
public class CrateNodeImpl extends SoftwareProcessImpl implements CrateNode{
private JmxFeed jmxFeed;
private HttpFeed httpFeed;
- private static final int CRATE_PORT = 4200;
-
static {
JavaAppUtils.init();
+ RendererHints.register(MANAGEMENT_URL, RendererHints.namedActionWithUrl());
}
@Override
@@ -25,39 +45,55 @@ public class CrateNodeImpl extends SoftwareProcessImpl implements CrateNode{
}
@Override
- protected void disconnectSensors() {
- disconnectServiceUpIsRunning();
- jmxFeed.stop();
- httpFeed.stop();
- super.disconnectSensors();
- }
-
- @Override
protected void connectSensors() {
super.connectSensors();
connectServiceUpIsRunning();
- jmxFeed = getJmxFeed();
- String uri = "http://" + getAttribute(HOSTNAME) + ":" + CRATE_PORT;
- setAttribute(MANAGEMENT_URI, uri);
+ jmxFeed = JavaAppUtils.connectMXBeanSensors(this);
+ setAttribute(DATASTORE_URL, "crate://" + getAttribute(HOSTNAME) + ":" + getPort());
+ String url = "http://" + getAttribute(HOSTNAME) + ":" + getHttpPort();
+ setAttribute(MANAGEMENT_URL, url);
httpFeed = HttpFeed.builder()
.entity(this)
- .baseUri(uri)
- .poll(new HttpPollConfig<String>(CrateNode.SERVER_NAME)
+ .baseUri(url)
+ .poll(new HttpPollConfig<String>(SERVER_NAME)
.onSuccess(HttpValueFunctions.jsonContents("name", String.class)))
- .poll(new HttpPollConfig<Integer>(CrateNode.SERVER_STATUS)
+ .poll(new HttpPollConfig<Integer>(SERVER_STATUS)
.onSuccess(HttpValueFunctions.jsonContents("status", Integer.class)))
- .poll(new HttpPollConfig<String>(CrateNode.SERVER_BUILD_TIMESTAMP)
- .onSuccess(HttpValueFunctions.jsonContents(new String[] {"version", "build_timestamp"}, String.class)))
+ .poll(new HttpPollConfig<Boolean>(SERVER_OK)
+ .onSuccess(HttpValueFunctions.jsonContents("ok", Boolean.class)))
+ .poll(new HttpPollConfig<String>(SERVER_BUILD_TIMESTAMP)
+ .onSuccess(HttpValueFunctions.jsonContents(new String[]{"version", "build_timestamp"}, String.class)))
+ .poll(new HttpPollConfig<String>(SERVER_BUILD_HASH)
+ .onSuccess(HttpValueFunctions.jsonContents(new String[]{"version", "build_hash"}, String.class)))
+ .poll(new HttpPollConfig<Boolean>(SERVER_IS_BUILD_SNAPSHOT)
+ .onSuccess(HttpValueFunctions.jsonContents(new String[] {"version", "build_snapshot"}, Boolean.class)))
+ .poll(new HttpPollConfig<String>(SERVER_LUCENE_VERSION)
+ .onSuccess(HttpValueFunctions.jsonContents(new String[] {"version", "lucene_version"}, String.class)))
+ .poll(new HttpPollConfig<String>(SERVER_ES_VERSION)
+ .onSuccess(HttpValueFunctions.jsonContents(new String[] {"version", "es_version"}, String.class)))
.build();
+
+ addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
+ .from(SERVER_OK)
+ .computing(Functionals.ifNotEquals(true).value("Crate server reports it is not ok."))
+ .build());
}
@Override
- protected void postStart() {
- super.postStart();
+ protected void disconnectSensors() {
+ disconnectServiceUpIsRunning();
+ if (jmxFeed != null) jmxFeed.stop();
+ if (httpFeed != null) httpFeed.stop();
+ super.disconnectSensors();
+ }
+ public Integer getPort() {
+ return getAttribute(CRATE_PORT);
}
- private JmxFeed getJmxFeed() {
- return JavaAppUtils.connectMXBeanSensors(this);
+
+ public Integer getHttpPort() {
+ return getAttribute(CRATE_HTTP_PORT);
}
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/743fd2d0/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeSshDriver.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeSshDriver.java b/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeSshDriver.java
index 17ec637..a807cbe 100644
--- a/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeSshDriver.java
+++ b/software/database/src/main/java/brooklyn/entity/database/crate/CrateNodeSshDriver.java
@@ -1,16 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
package brooklyn.entity.database.crate;
import static java.lang.String.format;
import java.util.List;
-import brooklyn.util.collections.MutableMap;
import com.google.common.collect.ImmutableList;
import brooklyn.entity.basic.Entities;
import brooklyn.entity.basic.EntityLocal;
import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.net.Urls;
import brooklyn.util.os.Os;
import brooklyn.util.ssh.BashCommands;
@@ -34,7 +53,7 @@ public class CrateNodeSshDriver extends JavaSoftwareProcessSshDriver {
List<String> commands = ImmutableList.<String>builder()
.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs))
- .add ("tar xvfz "+saveAs)
+ .add("tar xvfz "+saveAs)
.build();
newScript(INSTALLING)
@@ -44,16 +63,19 @@ public class CrateNodeSshDriver extends JavaSoftwareProcessSshDriver {
@Override
public void customize() {
-
+ newScript(CUSTOMIZING)
+ .body.append("mkdir -p " + getDataLocation())
+ .execute();
+ copyTemplate(entity.getConfig(CrateNode.SERVER_CONFIG_URL), getConfigFileLocation());
}
@Override
public void launch() {
StringBuilder command = new StringBuilder(getExpandedInstallDir())
- .append("/bin/crate >").append(getLogFileLocation())
- .append(" 2> err.log < /dev/null")
+ .append("/bin/crate ")
.append(" -d")
- .append(" -p ").append(getPidFileLocation());
+ .append(" -p ").append(getPidFileLocation())
+ .append(" -Des.config=").append(getConfigFileLocation());
newScript(LAUNCHING)
.failOnNonZeroResultCode()
.body.append(command).execute();
@@ -68,17 +90,28 @@ public class CrateNodeSshDriver extends JavaSoftwareProcessSshDriver {
@Override
public void stop() {
- newScript (MutableMap.of("usePidFile", getPidFileLocation()), STOPPING)
+ // See https://crate.io/docs/stable/cli.html#signal-handling.
+ newScript(STOPPING)
+ .body.append("kill -USR2 `cat " + getPidFileLocation() + "`")
.execute();
+ }
+ protected String getConfigFileLocation() {
+ return Urls.mergePaths(getRunDir(), "config.yaml");
}
@Override
- protected String getLogFileLocation() {
- return getRunDir() + "/server.log";
+ public String getLogFileLocation() {
+ return Urls.mergePaths(getRunDir(), "crate.log");
}
protected String getPidFileLocation () {
- return getRunDir() + "/pid.txt";
+ return Urls.mergePaths(getRunDir(), "pid.txt");
+ }
+
+ // public for use in template too.
+ public String getDataLocation() {
+ return Urls.mergePaths(getRunDir(), "data");
}
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/743fd2d0/software/database/src/main/resources/brooklyn/entity/database/crate/crate.yaml
----------------------------------------------------------------------
diff --git a/software/database/src/main/resources/brooklyn/entity/database/crate/crate.yaml b/software/database/src/main/resources/brooklyn/entity/database/crate/crate.yaml
new file mode 100644
index 0000000..42fcee5
--- /dev/null
+++ b/software/database/src/main/resources/brooklyn/entity/database/crate/crate.yaml
@@ -0,0 +1,28 @@
+# The Crate distribution comes with comprehensive instructions on available
+# configuration. Select sections are reproduced here.
+
+
+############################## Network And HTTP ###############################
+
+# Crate, by default, binds itself to the 0.0.0.0 address, and listens
+# on port [4200-4300] for HTTP traffic and on port [4300-4400] for node-to-node
+# communication. (the range means that if the port is busy, it will automatically
+# try the next port).
+
+# Set both 'bind_host' and 'publish_host':
+network.host: ${driver.subnetHostname}
+
+# Set a custom port for the node to node communication (4300 by default):
+transport.tcp.port: ${entity.port?c}
+
+# Set a custom port to listen for HTTP traffic:
+http.port: ${entity.httpPort?c}
+
+
+#################################### Paths ####################################
+
+# Path to directory where to store table data allocated for this node.
+path.data: ${driver.dataLocation}
+
+# Path to log files:
+path.logs: ${driver.runDir}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/743fd2d0/software/database/src/test/java/brooklyn/entity/database/crate/CrateNodeIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/database/src/test/java/brooklyn/entity/database/crate/CrateNodeIntegrationTest.java b/software/database/src/test/java/brooklyn/entity/database/crate/CrateNodeIntegrationTest.java
new file mode 100644
index 0000000..839fd3f
--- /dev/null
+++ b/software/database/src/test/java/brooklyn/entity/database/crate/CrateNodeIntegrationTest.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 brooklyn.entity.database.crate;
+
+import static org.testng.Assert.assertFalse;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.test.entity.TestApplication;
+
+public class CrateNodeIntegrationTest {
+
+ private TestApplication app;
+ private LocalhostMachineProvisioningLocation localhostProvisioningLocation;
+
+ @BeforeMethod(alwaysRun = true)
+ public void setUp() throws Exception {
+ localhostProvisioningLocation = new LocalhostMachineProvisioningLocation();
+ app = ApplicationBuilder.newManagedApp(TestApplication.class);
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void tearDown() throws Exception {
+ if (app != null) Entities.destroyAll(app.getManagementContext());
+ }
+
+ @Test(groups = "Integration")
+ public void testCanStartAndStop() throws Exception {
+ CrateNode entity = app.createAndManageChild(EntitySpec.create(CrateNode.class));
+ app.start(ImmutableList.of(localhostProvisioningLocation));
+
+ EntityTestUtils.assertAttributeEqualsEventually(entity, Startable.SERVICE_UP, true);
+ EntityTestUtils.assertAttributeEventuallyNonNull(entity, CrateNode.SERVER_NAME);
+
+ entity.stop();
+ assertFalse(entity.getAttribute(Startable.SERVICE_UP));
+ }
+}