MongoDB: support disabling direct connection - Important if MongoDB is locked down such that only ssh access to the VM is available.
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/ec76f129 Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/ec76f129 Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/ec76f129 Branch: refs/heads/master Commit: ec76f129e7a977b5c326e38eabe6a2b1cfde7d73 Parents: d6311c2 Author: Aled Sage <[email protected]> Authored: Wed Nov 18 15:35:49 2015 +0000 Committer: Aled Sage <[email protected]> Committed: Tue Nov 24 13:53:45 2015 +0000 ---------------------------------------------------------------------- .../brooklyn/entity/AbstractEc2LiveTest.java | 48 +++++++++- .../nosql/mongodb/AbstractMongoDBSshDriver.java | 8 +- .../entity/nosql/mongodb/MongoDBServer.java | 3 + .../entity/nosql/mongodb/MongoDBServerImpl.java | 96 +++++++++++--------- .../nosql/mongodb/MongoDBEc2LiveTest.java | 42 +++++++-- 5 files changed, 145 insertions(+), 52 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ec76f129/software/base/src/test/java/org/apache/brooklyn/entity/AbstractEc2LiveTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractEc2LiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractEc2LiveTest.java index 0a1e9d7..3874346 100644 --- a/software/base/src/test/java/org/apache/brooklyn/entity/AbstractEc2LiveTest.java +++ b/software/base/src/test/java/org/apache/brooklyn/entity/AbstractEc2LiveTest.java @@ -18,17 +18,29 @@ */ package org.apache.brooklyn.entity; +import static org.testng.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.util.List; import java.util.Map; -import org.apache.brooklyn.util.collections.MutableMap; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.core.internal.BrooklynProperties; +import org.apache.brooklyn.core.location.Machines; import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport; import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.entity.software.base.SoftwareProcess; import org.apache.brooklyn.location.jclouds.JcloudsLocation; import org.apache.brooklyn.location.jclouds.JcloudsLocationConfig; +import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.ssh.BashCommands; +import org.apache.brooklyn.util.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -46,6 +58,8 @@ public abstract class AbstractEc2LiveTest extends BrooklynAppLiveTestSupport { // to say what combo of provider/region/flags should be used. The problem with that is the // IDE integration: one can't just select a single test to run. + private static final Logger LOG = LoggerFactory.getLogger(AbstractEc2LiveTest.class); + public static final String PROVIDER = "aws-ec2"; public static final String REGION_NAME = "us-east-1"; public static final String LOCATION_SPEC = PROVIDER + (REGION_NAME == null ? "" : ":" + REGION_NAME); @@ -136,4 +150,32 @@ public abstract class AbstractEc2LiveTest extends BrooklynAppLiveTestSupport { } protected abstract void doTest(Location loc) throws Exception; + + protected void assertExecSsh(SoftwareProcess entity, List<String> commands) { + SshMachineLocation machine = Machines.findUniqueMachineLocation(entity.getLocations(), SshMachineLocation.class).get(); + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + ByteArrayOutputStream errStream = new ByteArrayOutputStream(); + int result = machine.execScript(ImmutableMap.of("out", outStream, "err", errStream), "url-reachable", commands); + String out = new String(outStream.toByteArray()); + String err = new String(errStream.toByteArray()); + if (result == 0) { + LOG.debug("Successfully executed: cmds="+commands+"; stderr="+err+"; out="+out); + } else { + fail("Failed to execute: result="+result+"; cmds="+commands+"; stderr="+err+"; out="+out); + } + } + + protected void assertViaSshLocalPortListeningEventually(final SoftwareProcess server, final int port) { + Asserts.succeedsEventually(ImmutableMap.of("timeout", Duration.FIVE_MINUTES), new Runnable() { + public void run() { + assertExecSsh(server, ImmutableList.of("netstat -antp", "netstat -antp | grep LISTEN | grep "+port)); + }}); + } + + protected void assertViaSshLocalUrlListeningEventually(final SoftwareProcess server, final String url) { + Asserts.succeedsEventually(ImmutableMap.of("timeout", Duration.FIVE_MINUTES), new Runnable() { + public void run() { + assertExecSsh(server, ImmutableList.of(BashCommands.installPackage("curl"), "netstat -antp", "curl -k --retry 3 "+url)); + }}); + } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ec76f129/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/AbstractMongoDBSshDriver.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/AbstractMongoDBSshDriver.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/AbstractMongoDBSshDriver.java index 14c495e..ccbe470 100644 --- a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/AbstractMongoDBSshDriver.java +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/AbstractMongoDBSshDriver.java @@ -28,6 +28,7 @@ import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver; import org.apache.brooklyn.entity.software.base.lifecycle.ScriptHelper; import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.internal.ssh.SshTool; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.net.Networking; @@ -115,7 +116,12 @@ public abstract class AbstractMongoDBSshDriver extends AbstractSoftwareProcessSs @Override public boolean isRunning() { try { - return MongoDBClientSupport.forServer((AbstractMongoDBServer) entity).ping(); + if (entity instanceof MongoDBServerImpl && !((MongoDBServerImpl)entity).clientAccessEnabled()) { + // No direct access via MongoDB port; only use ssh-port + return newScript(MutableMap.of(USE_PID_FILE, getPidFile()), CHECK_RUNNING).execute() == 0; + } else { + return MongoDBClientSupport.forServer((AbstractMongoDBServer) entity).ping(); + } } catch (Exception e) { Exceptions.propagateIfFatal(e); return false; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ec76f129/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBServer.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBServer.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBServer.java index 2ec38dc..34c07d3 100644 --- a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBServer.java +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBServer.java @@ -50,6 +50,9 @@ public interface MongoDBServer extends AbstractMongoDBServer { ConfigKey<Boolean> ENABLE_REST_INTERFACE = ConfigKeys.newBooleanConfigKey( "mongodb.config.enable_rest", "Adds --rest to server startup flags when true", Boolean.FALSE); + @SetFromFlag("useClientMonitoring") + ConfigKey<Boolean> USE_CLIENT_MONITORING = ConfigKeys.newConfigKey("clientMonitoring.enabled", "Monitoring via the MongoDB client enabled", Boolean.TRUE); + AttributeSensor<String> HTTP_INTERFACE_URL = Sensors.newStringSensor( "mongodb.server.http_interface", "URL of the server's HTTP console"); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ec76f129/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBServerImpl.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBServerImpl.java b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBServerImpl.java index 2469046..35b60e8 100644 --- a/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBServerImpl.java +++ b/software/nosql/src/main/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBServerImpl.java @@ -71,54 +71,58 @@ public class MongoDBServerImpl extends SoftwareProcessImpl implements MongoDBSer sensors().set(HTTP_INTERFACE_URL, String.format("http://%s:%d", accessibleAddress.getHostText(), httpConsolePort)); - try { - client = MongoDBClientSupport.forServer(this); - } catch (UnknownHostException e) { - LOG.warn("Unable to create client connection to {}, not connecting sensors: {} ", this, e.getMessage()); - return; - } - - serviceStats = FunctionFeed.builder() - .entity(this) - .poll(new FunctionPollConfig<Object, BasicBSONObject>(STATUS_BSON) - .period(2, TimeUnit.SECONDS) - .callable(new Callable<BasicBSONObject>() { - @Override - public BasicBSONObject call() throws Exception { - return MongoDBServerImpl.this.sensors().get(SERVICE_UP) - ? client.getServerStatus() - : null; - } - }) - .onException(Functions.<BasicBSONObject>constant(null))) - .build(); - - if (isReplicaSetMember()) { - replicaSetStats = FunctionFeed.builder() + if (clientAccessEnabled()) { + try { + client = MongoDBClientSupport.forServer(this); + } catch (UnknownHostException e) { + LOG.warn("Unable to create client connection to {}, not connecting sensors: {} ", this, e.getMessage()); + return; + } + + serviceStats = FunctionFeed.builder() .entity(this) - .poll(new FunctionPollConfig<Object, ReplicaSetMemberStatus>(REPLICA_SET_MEMBER_STATUS) + .poll(new FunctionPollConfig<Object, BasicBSONObject>(STATUS_BSON) .period(2, TimeUnit.SECONDS) - .callable(new Callable<ReplicaSetMemberStatus>() { - /** - * Calls {@link MongoDBClientSupport#getReplicaSetStatus} and - * extracts <code>myState</code> from the response. - * @return - * The appropriate {@link org.apache.brooklyn.entity.nosql.mongodb.ReplicaSetMemberStatus} - * if <code>myState</code> was non-null, {@link ReplicaSetMemberStatus#UNKNOWN} otherwise. - */ + .callable(new Callable<BasicBSONObject>() { @Override - public ReplicaSetMemberStatus call() { - BasicBSONObject serverStatus = client.getReplicaSetStatus(); - int state = serverStatus.getInt("myState", -1); - return ReplicaSetMemberStatus.fromCode(state); + public BasicBSONObject call() throws Exception { + return MongoDBServerImpl.this.sensors().get(SERVICE_UP) + ? client.getServerStatus() + : null; } }) - .onException(Functions.constant(ReplicaSetMemberStatus.UNKNOWN)) - .suppressDuplicates(true)) + .onException(Functions.<BasicBSONObject>constant(null))) .build(); + + if (isReplicaSetMember()) { + replicaSetStats = FunctionFeed.builder() + .entity(this) + .poll(new FunctionPollConfig<Object, ReplicaSetMemberStatus>(REPLICA_SET_MEMBER_STATUS) + .period(2, TimeUnit.SECONDS) + .callable(new Callable<ReplicaSetMemberStatus>() { + /** + * Calls {@link MongoDBClientSupport#getReplicaSetStatus} and + * extracts <code>myState</code> from the response. + * @return + * The appropriate {@link org.apache.brooklyn.entity.nosql.mongodb.ReplicaSetMemberStatus} + * if <code>myState</code> was non-null, {@link ReplicaSetMemberStatus#UNKNOWN} otherwise. + */ + @Override + public ReplicaSetMemberStatus call() { + BasicBSONObject serverStatus = client.getReplicaSetStatus(); + int state = serverStatus.getInt("myState", -1); + return ReplicaSetMemberStatus.fromCode(state); + } + }) + .onException(Functions.constant(ReplicaSetMemberStatus.UNKNOWN)) + .suppressDuplicates(true)) + .build(); + } else { + sensors().set(IS_PRIMARY_FOR_REPLICA_SET, false); + sensors().set(IS_SECONDARY_FOR_REPLICA_SET, false); + } } else { - sensors().set(IS_PRIMARY_FOR_REPLICA_SET, false); - sensors().set(IS_SECONDARY_FOR_REPLICA_SET, false); + LOG.info("Not monitoring "+this+" to retrieve state via client API"); } // Take interesting details from STATUS. @@ -163,6 +167,10 @@ public class MongoDBServerImpl extends SoftwareProcessImpl implements MongoDBSer if (replicaSetStats != null) replicaSetStats.stop(); } + protected boolean clientAccessEnabled() { + return Boolean.TRUE.equals(config().get(MongoDBServer.USE_CLIENT_MONITORING)); + } + @Override public MongoDBReplicaSet getReplicaSet() { return config().get(MongoDBServer.REPLICA_SET); @@ -190,6 +198,9 @@ public class MongoDBServerImpl extends SoftwareProcessImpl implements MongoDBSer LOG.warn("Attempted to add {} to replica set at server that is not primary: {}", secondary, this); return false; } + if (!clientAccessEnabled()) { + throw new IllegalStateException("client-access disabled for "+this+"; cannot add to replica set member "+secondary+" -> "+id); + } return client.addMemberToReplicaSet(secondary, id); } @@ -199,6 +210,9 @@ public class MongoDBServerImpl extends SoftwareProcessImpl implements MongoDBSer LOG.warn("Attempted to remove {} from replica set at server that is not primary: {}", server, this); return false; } + if (!clientAccessEnabled()) { + throw new IllegalStateException("client-access disabled for "+this+"; cannot remove from replica set member "+server); + } return client.removeMemberFromReplicaSet(server); } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ec76f129/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBEc2LiveTest.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBEc2LiveTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBEc2LiveTest.java index 6306b4e..5ad5fa7 100644 --- a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBEc2LiveTest.java +++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBEc2LiveTest.java @@ -18,19 +18,24 @@ */ package org.apache.brooklyn.entity.nosql.mongodb; -import com.google.common.collect.ImmutableList; -import com.mongodb.DBObject; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testng.annotations.Test; - import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.entity.Attributes; +import org.apache.brooklyn.core.entity.EntityAsserts; +import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.core.location.cloud.CloudLocationConfig; import org.apache.brooklyn.entity.AbstractEc2LiveTest; import org.apache.brooklyn.test.EntityTestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.mongodb.DBObject; public class MongoDBEc2LiveTest extends AbstractEc2LiveTest { @@ -50,6 +55,29 @@ public class MongoDBEc2LiveTest extends AbstractEc2LiveTest { assertEquals(docOut.get("hello"), "world!"); } + @Test(groups = {"Live"}) + public void testWithOnlyPort22() throws Exception { + // CentOS-6.3-x86_64-GA-EBS-02-85586466-5b6c-4495-b580-14f72b4bcf51-ami-bb9af1d2.1 + jcloudsLocation = mgmt.getLocationRegistry().resolve(LOCATION_SPEC, ImmutableMap.of( + "tags", ImmutableList.of(getClass().getName()), + "imageId", "us-east-1/ami-a96b01c0", + "hardwareId", SMALL_HARDWARE_ID)); + + MongoDBServer server = app.createAndManageChild(EntitySpec.create(MongoDBServer.class) + .configure(MongoDBServer.PROVISIONING_PROPERTIES.subKey(CloudLocationConfig.INBOUND_PORTS.getName()), ImmutableList.of(22)) + .configure(MongoDBServer.USE_CLIENT_MONITORING, false)); + + app.start(ImmutableList.of(jcloudsLocation)); + + EntityAsserts.assertAttributeEqualsEventually(server, Attributes.SERVICE_UP, true); + EntityAsserts.assertAttributeEqualsEventually(server, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); + + Integer port = server.getAttribute(MongoDBServer.PORT); + assertNotNull(port); + + assertViaSshLocalPortListeningEventually(server, port); + } + @Test(enabled=false) public void testDummy() {} // Convince TestNG IDE integration that this really does have test methods
