http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/test/java/org/apache/brooklyn/location/core/MultiLocationTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/core/MultiLocationTest.java b/core/src/test/java/org/apache/brooklyn/location/core/MultiLocationTest.java new file mode 100644 index 0000000..ab527f2 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/location/core/MultiLocationTest.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.core; + +import static org.testng.Assert.assertTrue; + +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.net.Networking; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.api.location.NoMachinesAvailableException; +import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation; +import org.apache.brooklyn.location.cloud.AvailabilityZoneExtension; +import org.apache.brooklyn.location.core.MultiLocation; +import org.apache.brooklyn.location.ssh.SshMachineLocation; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class MultiLocationTest { + + private static final Logger log = LoggerFactory.getLogger(MultiLocationTest.class); + + private LocalManagementContext managementContext; + private SshMachineLocation mac1a; + private SshMachineLocation mac1b; + private SshMachineLocation mac2a; + private SshMachineLocation mac2b; + private FixedListMachineProvisioningLocation<SshMachineLocation> loc1; + private FixedListMachineProvisioningLocation<SshMachineLocation> loc2; + private MultiLocation<SshMachineLocation> multiLoc; + + @SuppressWarnings("unchecked") + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + managementContext = LocalManagementContextForTests.newInstance(); + mac1a = managementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class) + .displayName("mac1a") + .configure("address", Networking.getInetAddressWithFixedName("1.1.1.1"))); + mac1b = managementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class) + .displayName("mac1b") + .configure("address", Networking.getInetAddressWithFixedName("1.1.1.2"))); + mac2a = managementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class) + .displayName("mac2a") + .configure("address", Networking.getInetAddressWithFixedName("1.1.1.3"))); + mac2b = managementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class) + .displayName("mac2b") + .configure("address", Networking.getInetAddressWithFixedName("1.1.1.4"))); + loc1 = managementContext.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class) + .displayName("loc1") + .configure("machines", MutableSet.of(mac1a, mac1b))); + loc2 = managementContext.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class) + .displayName("loc2") + .configure("machines", MutableSet.of(mac2a, mac2b))); + multiLoc = managementContext.getLocationManager().createLocation(LocationSpec.create(MultiLocation.class) + .displayName("multiLoc") + .configure("subLocations", ImmutableList.of(loc1, loc2))); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (managementContext != null) Entities.destroyAll(managementContext); + } + + @Test + public void testHasAvailabilityZonesAsSubLocations() throws Exception { + multiLoc.hasExtension(AvailabilityZoneExtension.class); + AvailabilityZoneExtension extension = multiLoc.getExtension(AvailabilityZoneExtension.class); + Asserts.assertEqualsIgnoringOrder(extension.getAllSubLocations(), ImmutableList.of(loc1, loc2)); + Asserts.assertEqualsIgnoringOrder(extension.getSubLocations(2), ImmutableList.of(loc1, loc2)); + assertTrue(ImmutableList.of(loc1, loc2).containsAll(extension.getSubLocations(1))); + } + + @Test + public void testObtainAndReleaseDelegateToSubLocation() throws Exception { + SshMachineLocation obtained = multiLoc.obtain(ImmutableMap.of()); + assertTrue(ImmutableList.of(mac1a, mac1b, mac2a, mac2b).contains(obtained)); + multiLoc.release(obtained); + } + + @Test + public void testObtainsMovesThroughSubLocations() throws Exception { + Assert.assertEquals(multiLoc.obtain().getAddress().getHostAddress(), "1.1.1.1"); + Assert.assertEquals(multiLoc.obtain().getAddress().getHostAddress(), "1.1.1.2"); + Assert.assertEquals(multiLoc.obtain().getAddress().getHostAddress(), "1.1.1.3"); + Assert.assertEquals(multiLoc.obtain().getAddress().getHostAddress(), "1.1.1.4"); + try { + multiLoc.obtain(); + Assert.fail(); + } catch (NoMachinesAvailableException e) { + log.info("Error when no machines available across locations is: "+e); + Assert.assertTrue(e.toString().contains("loc1"), "Message should have referred to sub-location message: "+e); + } + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/test/java/org/apache/brooklyn/location/core/PaasLocationTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/core/PaasLocationTest.java b/core/src/test/java/org/apache/brooklyn/location/core/PaasLocationTest.java new file mode 100644 index 0000000..a58c0a1 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/location/core/PaasLocationTest.java @@ -0,0 +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 org.apache.brooklyn.location.core; + +import org.apache.brooklyn.core.test.location.TestPaasLocation; +import org.apache.brooklyn.location.paas.PaasLocation; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class PaasLocationTest { + + private PaasLocation location; + + @Test + public void testProviderName(){ + location = new TestPaasLocation(); + Assert.assertEquals(location.getPaasProviderName(), "TestPaas"); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/test/java/org/apache/brooklyn/location/core/PortRangesTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/core/PortRangesTest.java b/core/src/test/java/org/apache/brooklyn/location/core/PortRangesTest.java new file mode 100644 index 0000000..e4d7036 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/location/core/PortRangesTest.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.core; + +import static org.testng.Assert.assertEquals; + +import java.util.Iterator; + +import org.testng.Assert; +import org.testng.annotations.Test; +import org.apache.brooklyn.api.location.PortRange; +import org.apache.brooklyn.location.core.PortRanges; +import org.apache.brooklyn.util.core.flags.TypeCoercions; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +public class PortRangesTest { + + @Test + public void testSingleRange() { + PortRange r = PortRanges.fromInteger(1234); + assertContents(r, 1234); + } + + @Test + public void testFromCollection() { + PortRange r = PortRanges.fromCollection(ImmutableList.of(1234, 2345)); + assertContents(r, 1234, 2345); + } + + @Test + public void testFromString() { + PortRange r = PortRanges.fromString("80,8080,8000,8080-8099"); + assertContents(r, 80, 8080, 8000, + 8080, 8081, 8082, 8083, 8084, 8085, 8086, 8087, 8088, 8089, + 8090, 8091, 8092, 8093, 8094, 8095, 8096, 8097, 8098, 8099); + } + + @Test + public void testFromStringWithSpaces() { + PortRange r = PortRanges.fromString(" 80 , 8080 , 8000 , 8080 - 8099 "); + assertContents(r, 80, 8080, 8000, + 8080, 8081, 8082, 8083, 8084, 8085, 8086, 8087, 8088, 8089, + 8090, 8091, 8092, 8093, 8094, 8095, 8096, 8097, 8098, 8099); + } + + @Test + public void testFromStringWithSpacesToString() { + PortRange r = PortRanges.fromString(" 80 , 8080 , 8000 , 8080 - 8099 "); + Assert.assertEquals(r.toString(), "80,8080,8000,8080-8099"); + } + + @Test + public void testFromStringThrowsIllegalArgumentException() { + assertFromStringThrowsIllegalArgumentException("80-100000"); + assertFromStringThrowsIllegalArgumentException("0-80"); + } + + @Test + public void testCoercion() { + PortRanges.init(); + PortRange r = TypeCoercions.coerce("80", PortRange.class); + assertContents(r, 80); + } + + @Test + public void testCoercionInt() { + PortRanges.init(); + PortRange r = TypeCoercions.coerce(80, PortRange.class); + assertContents(r, 80); + } + + @Test + public void testLinearRangeOfSizeOne() throws Exception { + PortRanges.LinearPortRange range = new PortRanges.LinearPortRange(80, 80); + assertEquals(Lists.newArrayList(range), ImmutableList.of(80)); + } + + @Test + public void testLinearRangeCountingUpwards() throws Exception { + PortRanges.LinearPortRange range = new PortRanges.LinearPortRange(80, 81); + assertEquals(Lists.newArrayList(range), ImmutableList.of(80, 81)); + } + + @Test + public void testLinearRangeCountingDownwards() throws Exception { + PortRanges.LinearPortRange range = new PortRanges.LinearPortRange(80, 79); + assertEquals(Lists.newArrayList(range), ImmutableList.of(80, 79)); + } + + protected void assertFromStringThrowsIllegalArgumentException(String range) { + try { + PortRanges.fromString(range); + Assert.fail(); + } catch (IllegalArgumentException e) { + // success + } + } + + private static <T> void assertContents(Iterable<T> actual, T ...expected) { + Iterator<T> i = actual.iterator(); + int c = 0; + while (i.hasNext()) { + if (expected.length<=c) { + Assert.fail("Iterable contained more than the "+c+" expected elements"); + } + Assert.assertEquals(i.next(), expected[c++]); + } + if (expected.length>c) { + Assert.fail("Iterable contained only "+c+" elements, "+expected.length+" expected"); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/test/java/org/apache/brooklyn/location/core/RecordingMachineLocationCustomizer.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/core/RecordingMachineLocationCustomizer.java b/core/src/test/java/org/apache/brooklyn/location/core/RecordingMachineLocationCustomizer.java new file mode 100644 index 0000000..efaa94f --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/location/core/RecordingMachineLocationCustomizer.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.core; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.List; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.api.location.MachineLocationCustomizer; + +public class RecordingMachineLocationCustomizer implements MachineLocationCustomizer { + public static class Call { + public final String methodName; + public final List<?> args; + + public Call(String methodName, List<?> args) { + this.methodName = checkNotNull(methodName); + this.args = checkNotNull(args); + } + + @Override + public String toString() { + return methodName+args; + } + + @Override + public int hashCode() { + return Objects.hashCode(methodName, args); + } + + @Override + public boolean equals(Object other) { + return (other instanceof RecordingMachineLocationCustomizer.Call) && + methodName.equals(((RecordingMachineLocationCustomizer.Call)other).methodName) && + args.equals(((RecordingMachineLocationCustomizer.Call)other).args); + } + } + + public final List<RecordingMachineLocationCustomizer.Call> calls = Lists.newCopyOnWriteArrayList(); + + @Override + public void customize(MachineLocation machine) { + calls.add(new Call("customize", ImmutableList.of(machine))); + } + + @Override + public void preRelease(MachineLocation machine) { + calls.add(new Call("preRelease", ImmutableList.of(machine))); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/test/java/org/apache/brooklyn/location/core/SimulatedLocation.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/core/SimulatedLocation.java b/core/src/test/java/org/apache/brooklyn/location/core/SimulatedLocation.java new file mode 100644 index 0000000..e9f572c --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/location/core/SimulatedLocation.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.core; + +import java.net.InetAddress; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.api.location.HardwareDetails; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.api.location.MachineDetails; +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.api.location.MachineProvisioningLocation; +import org.apache.brooklyn.api.location.OsDetails; +import org.apache.brooklyn.api.location.PortRange; +import org.apache.brooklyn.api.location.PortSupplier; +import org.apache.brooklyn.location.core.AbstractLocation; +import org.apache.brooklyn.location.core.BasicHardwareDetails; +import org.apache.brooklyn.location.core.BasicMachineDetails; +import org.apache.brooklyn.location.core.BasicOsDetails; +import org.apache.brooklyn.location.core.PortRanges; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.net.Networking; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; + + +/** Location for use in dev/test, defining custom start/stop support, and/or tweaking the ports which are permitted to be available + * (using setPermittedPorts(Iterable)) + */ +public class SimulatedLocation extends AbstractLocation implements MachineProvisioningLocation<MachineLocation>, MachineLocation, PortSupplier { + + private static final long serialVersionUID = 1L; + + private static final InetAddress address; + static { + address = Networking.getLocalHost(); + } + + Iterable<Integer> permittedPorts = PortRanges.fromString("1+"); + Set<Integer> usedPorts = Sets.newLinkedHashSet(); + + public SimulatedLocation() { + this(MutableMap.<String,Object>of()); + } + public SimulatedLocation(Map<String,? extends Object> flags) { + super(flags); + } + + @Override + public SimulatedLocation newSubLocation(Map<?,?> newFlags) { + // TODO shouldn't have to copy config bag as it should be inherited (but currently it is not used inherited everywhere; just most places) + return getManagementContext().getLocationManager().createLocation(LocationSpec.create(getClass()) + .parent(this) + .configure(config().getLocalBag().getAllConfig()) // FIXME Should this just be inherited? + .configure(newFlags)); + } + + public MachineLocation obtain(Map<?,?> flags) { + return this; + } + + public void release(MachineLocation machine) { + } + + public Map<String,Object> getProvisioningFlags(Collection<String> tags) { + return MutableMap.<String,Object>of(); + } + + public InetAddress getAddress() { + return address; + } + + @Override + public String getHostname() { + String hostname = address.getHostName(); + return (hostname == null || hostname.equals(address.getHostAddress())) ? null : hostname; + } + + @Override + public Set<String> getPublicAddresses() { + return ImmutableSet.of(address.getHostAddress()); + } + + @Override + public Set<String> getPrivateAddresses() { + return ImmutableSet.of(); + } + + public synchronized boolean obtainSpecificPort(int portNumber) { + if (!Iterables.contains(permittedPorts, portNumber)) return false; + if (usedPorts.contains(portNumber)) return false; + usedPorts.add(portNumber); + return true; + } + + public synchronized int obtainPort(PortRange range) { + for (int p: range) + if (obtainSpecificPort(p)) return p; + return -1; + } + + public synchronized void releasePort(int portNumber) { + usedPorts.remove(portNumber); + } + + public synchronized void setPermittedPorts(Iterable<Integer> ports) { + permittedPorts = ports; + } + + @Override + public OsDetails getOsDetails() { + return getMachineDetails().getOsDetails(); + } + + @Override + public MachineDetails getMachineDetails() { + HardwareDetails hardwareDetails = new BasicHardwareDetails(null, null); + OsDetails osDetails = BasicOsDetails.Factory.ANONYMOUS_LINUX; + return new BasicMachineDetails(hardwareDetails, osDetails); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/test/java/org/apache/brooklyn/location/core/TestPortSupplierLocation.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/core/TestPortSupplierLocation.java b/core/src/test/java/org/apache/brooklyn/location/core/TestPortSupplierLocation.java new file mode 100644 index 0000000..df37585 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/location/core/TestPortSupplierLocation.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.core; + +import static org.testng.Assert.assertEquals; + +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.location.core.PortRanges; +import org.apache.brooklyn.sensor.core.PortAttributeSensorAndConfigKey; +import org.apache.brooklyn.sensor.feed.ConfigToAttributes; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; + +public class TestPortSupplierLocation extends BrooklynAppUnitTestSupport { + + SimulatedLocation loc; + PortAttributeSensorAndConfigKey ps; + TestEntity entity; + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + super.setUp(); + loc = app.newSimulatedLocation(); + entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)); + app.start(ImmutableList.of(loc)); + + ps = new PortAttributeSensorAndConfigKey("some.port", "for testing", "1234+"); + } + + @Test + public void testObtainsPort() throws Exception { + ConfigToAttributes.apply(entity, ps); + + int p = entity.getAttribute(ps); + assertEquals(p, 1234); + + //sensor access should keep the same value + p = entity.getAttribute(ps); + assertEquals(p, 1234); + } + + @Test + public void testRepeatedConvertAccessIncrements() throws Exception { + int p = ps.getAsSensorValue(entity); + assertEquals(p, 1234); + + //but direct access should see it as being reserved (not required behaviour, but it is the current behaviour) + int p2 = ps.getAsSensorValue(entity); + assertEquals(p2, 1235); + } + + @Test + public void testNullBeforeSetting() throws Exception { + // currently getting the attribute before explicitly setting return null; i.e. no "auto-set" -- + // but this behaviour may be changed + Integer p = entity.getAttribute(ps); + assertEquals(p, null); + } + + @Test + public void testSimulatedRestrictedPermitted() throws Exception { + loc.setPermittedPorts(PortRanges.fromString("1240+")); + + ConfigToAttributes.apply(entity, ps); + int p = entity.getAttribute(ps); + assertEquals((int)p, 1240); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/test/java/org/apache/brooklyn/location/core/localhost/LocalhostLocationResolverTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/core/localhost/LocalhostLocationResolverTest.java b/core/src/test/java/org/apache/brooklyn/location/core/localhost/LocalhostLocationResolverTest.java new file mode 100644 index 0000000..bb781a4 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/location/core/localhost/LocalhostLocationResolverTest.java @@ -0,0 +1,269 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.core.localhost; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.NoMachinesAvailableException; +import org.apache.brooklyn.core.internal.BrooklynProperties; +import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation; +import org.apache.brooklyn.location.core.BasicLocationRegistry; +import org.apache.brooklyn.location.core.internal.LocationInternal; +import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation; +import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; + +public class LocalhostLocationResolverTest { + + private BrooklynProperties brooklynProperties; + private LocalManagementContext managementContext; + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + managementContext = LocalManagementContextForTests.newInstance(); + brooklynProperties = managementContext.getBrooklynProperties(); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (managementContext != null) Entities.destroyAll(managementContext); + } + + @Test + public void testTakesLocalhostScopedProperties() { + brooklynProperties.put("brooklyn.location.localhost.privateKeyFile", "myprivatekeyfile"); + brooklynProperties.put("brooklyn.location.localhost.publicKeyFile", "mypublickeyfile"); + brooklynProperties.put("brooklyn.location.localhost.privateKeyData", "myprivateKeyData"); + brooklynProperties.put("brooklyn.location.localhost.publicKeyData", "myPublicKeyData"); + brooklynProperties.put("brooklyn.location.localhost.privateKeyPassphrase", "myprivateKeyPassphrase"); + + Map<String, Object> conf = resolve("localhost").config().getBag().getAllConfig(); + + assertEquals(conf.get("privateKeyFile"), "myprivatekeyfile"); + assertEquals(conf.get("publicKeyFile"), "mypublickeyfile"); + assertEquals(conf.get("privateKeyData"), "myprivateKeyData"); + assertEquals(conf.get("publicKeyData"), "myPublicKeyData"); + assertEquals(conf.get("privateKeyPassphrase"), "myprivateKeyPassphrase"); + } + + @Test + public void testTakesLocalhostDeprecatedScopedProperties() { + brooklynProperties.put("brooklyn.localhost.privateKeyFile", "myprivatekeyfile"); + brooklynProperties.put("brooklyn.localhost.publicKeyFile", "mypublickeyfile"); + brooklynProperties.put("brooklyn.localhost.privateKeyData", "myprivateKeyData"); + brooklynProperties.put("brooklyn.localhost.publicKeyData", "myPublicKeyData"); + brooklynProperties.put("brooklyn.localhost.privateKeyPassphrase", "myprivateKeyPassphrase"); + + Map<String, Object> conf = resolve("localhost").config().getBag().getAllConfig(); + + assertEquals(conf.get("privateKeyFile"), "myprivatekeyfile"); + assertEquals(conf.get("publicKeyFile"), "mypublickeyfile"); + assertEquals(conf.get("privateKeyData"), "myprivateKeyData"); + assertEquals(conf.get("publicKeyData"), "myPublicKeyData"); + assertEquals(conf.get("privateKeyPassphrase"), "myprivateKeyPassphrase"); + } + + @Test + public void testTakesDeprecatedProperties() { + brooklynProperties.put("brooklyn.localhost.private-key-file", "myprivatekeyfile"); + brooklynProperties.put("brooklyn.localhost.public-key-file", "mypublickeyfile"); + brooklynProperties.put("brooklyn.localhost.private-key-data", "myprivateKeyData"); + brooklynProperties.put("brooklyn.localhost.public-key-data", "myPublicKeyData"); + brooklynProperties.put("brooklyn.localhost.private-key-passphrase", "myprivateKeyPassphrase"); + Map<String, Object> conf = resolve("localhost").config().getBag().getAllConfig(); + + assertEquals(conf.get("privateKeyFile"), "myprivatekeyfile"); + assertEquals(conf.get("publicKeyFile"), "mypublickeyfile"); + assertEquals(conf.get("privateKeyData"), "myprivateKeyData"); + assertEquals(conf.get("publicKeyData"), "myPublicKeyData"); + assertEquals(conf.get("privateKeyPassphrase"), "myprivateKeyPassphrase"); + } + + @Test + public void testPropertyScopePrescedence() { + brooklynProperties.put("brooklyn.location.named.mynamed", "localhost"); + + // prefer those in "named" over everything else + brooklynProperties.put("brooklyn.location.named.mynamed.privateKeyFile", "privateKeyFile-inNamed"); + brooklynProperties.put("brooklyn.location.localhost.privateKeyFile", "privateKeyFile-inProviderSpecific"); + brooklynProperties.put("brooklyn.localhost.privateKeyFile", "privateKeyFile-inGeneric"); + + // prefer those in provider-specific over generic + brooklynProperties.put("brooklyn.location.localhost.publicKeyFile", "publicKeyFile-inProviderSpecific"); + brooklynProperties.put("brooklyn.location.publicKeyFile", "publicKeyFile-inGeneric"); + + // prefer location-generic if nothing else + brooklynProperties.put("brooklyn.location.privateKeyData", "privateKeyData-inGeneric"); + + Map<String, Object> conf = resolve("named:mynamed").config().getBag().getAllConfig(); + + assertEquals(conf.get("privateKeyFile"), "privateKeyFile-inNamed"); + assertEquals(conf.get("publicKeyFile"), "publicKeyFile-inProviderSpecific"); + assertEquals(conf.get("privateKeyData"), "privateKeyData-inGeneric"); + } + + @Test + public void testLocalhostLoads() { + Assert.assertTrue(resolve("localhost") instanceof LocalhostMachineProvisioningLocation); + } + + @Test + public void testThrowsOnInvalid() throws Exception { + assertThrowsNoSuchElement("wrongprefix"); + assertThrowsIllegalArgument("localhost(name=abc"); // no closing bracket + assertThrowsIllegalArgument("localhost(name)"); // no value for name + assertThrowsIllegalArgument("localhost(name=)"); // no value for name + } + + + @Test + public void testAcceptsList() { + List<Location> l = getLocationResolver().resolve(ImmutableList.of("localhost")); + assertEquals(l.size(), 1, "l="+l); + assertTrue(l.get(0) instanceof LocalhostMachineProvisioningLocation, "l="+l); + } + + @SuppressWarnings("unchecked") + @Test + public void testRegistryCommaResolution() throws NoMachinesAvailableException { + List<Location> l; + l = getLocationResolver().resolve(JavaStringEscapes.unwrapJsonishListIfPossible("localhost,localhost,localhost")); + assertEquals(l.size(), 3, "l="+l); + assertTrue(l.get(0) instanceof LocalhostMachineProvisioningLocation, "l="+l); + assertTrue(l.get(1) instanceof LocalhostMachineProvisioningLocation, "l="+l); + assertTrue(l.get(2) instanceof LocalhostMachineProvisioningLocation, "l="+l); + + // And check works if comma in brackets + l = getLocationResolver().resolve(JavaStringEscapes.unwrapJsonishListIfPossible( + "[ \"byon:(hosts=\\\"192.168.0.1\\\",user=bob)\", \"byon:(hosts=\\\"192.168.0.2\\\",user=bob2)\" ]")); + assertEquals(l.size(), 2, "l="+l); + assertTrue(l.get(0) instanceof FixedListMachineProvisioningLocation, "l="+l); + assertTrue(l.get(1) instanceof FixedListMachineProvisioningLocation, "l="+l); + assertEquals(((FixedListMachineProvisioningLocation<SshMachineLocation>)l.get(0)).obtain().getUser(), "bob"); + assertEquals(((FixedListMachineProvisioningLocation<SshMachineLocation>)l.get(1)).obtain().getUser(), "bob2"); + } + + @Test(expectedExceptions={NoSuchElementException.class}) + public void testRegistryCommaResolutionInListNotAllowed1() throws NoMachinesAvailableException { + // disallowed since 0.7.0 + getLocationResolver().resolve(ImmutableList.of("localhost,localhost,localhost")); + } + + @Test(expectedExceptions={IllegalArgumentException.class}) + public void testRegistryCommaResolutionInListNotAllowed2() throws NoMachinesAvailableException { + // disallowed since 0.7.0 + // fails because it interprets the entire string as a single spec, which does not parse + getLocationResolver().resolve(ImmutableList.of("localhost(),localhost()")); + } + + @Test(expectedExceptions={IllegalArgumentException.class}) + public void testRegistryCommaResolutionInListNotAllowed3() throws NoMachinesAvailableException { + // disallowed since 0.7.0 + // fails because it interprets the entire string as a single spec, which does not parse + getLocationResolver().resolve(ImmutableList.of("localhost(name=a),localhost(name=b)")); + } + + @Test(expectedExceptions={IllegalArgumentException.class}) + public void testDoesNotAcceptsListOLists() { + ((BasicLocationRegistry)managementContext.getLocationRegistry()).resolve(ImmutableList.of(ImmutableList.of("localhost"))); + } + + @Test + public void testResolvesExplicitName() throws Exception { + Location location = resolve("localhost(name=myname)"); + assertTrue(location instanceof LocalhostMachineProvisioningLocation); + assertEquals(location.getDisplayName(), "myname"); + } + + @Test + public void testWithOldStyleColon() throws Exception { + Location location = resolve("localhost:(name=myname)"); + assertTrue(location instanceof LocalhostMachineProvisioningLocation); + assertEquals(location.getDisplayName(), "myname"); + } + + @Test + public void testResolvesPropertiesInSpec() throws Exception { + LocationInternal location = resolve("localhost(privateKeyFile=myprivatekeyfile,name=myname)"); + assertTrue(location instanceof LocalhostMachineProvisioningLocation); + assertEquals(location.getDisplayName(), "myname"); + assertEquals(location.config().getBag().getStringKey("privateKeyFile"), "myprivatekeyfile"); + } + + @Test + public void testResolvesDefaultName() throws Exception { + Location location = resolve("localhost"); + assertTrue(location instanceof LocalhostMachineProvisioningLocation); + assertEquals(location.getDisplayName(), "localhost"); + + Location location2 = resolve("localhost()"); + assertTrue(location2 instanceof LocalhostMachineProvisioningLocation); + assertEquals(location2.getDisplayName(), "localhost"); + } + + private BasicLocationRegistry getLocationResolver() { + return (BasicLocationRegistry) managementContext.getLocationRegistry(); + } + + private LocationInternal resolve(String val) { + Location l = managementContext.getLocationRegistry().resolve(val); + Assert.assertNotNull(l); + return (LocationInternal) l; + } + + private void assertThrowsNoSuchElement(String val) { + try { + resolve(val); + fail(); + } catch (NoSuchElementException e) { + // success + } + + // and check the long form returns an Absent (not throwing) + Assert.assertTrue(managementContext.getLocationRegistry().resolve(val, false, null).isAbsent()); + } + + private void assertThrowsIllegalArgument(String val) { + try { + resolve(val); + fail(); + } catch (IllegalArgumentException e) { + // success + } + + // and check the long form returns an Absent (not throwing) + Assert.assertTrue(managementContext.getLocationRegistry().resolve(val, false, null).isAbsent()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/test/java/org/apache/brooklyn/location/core/localhost/LocalhostMachineProvisioningLocationTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/core/localhost/LocalhostMachineProvisioningLocationTest.java b/core/src/test/java/org/apache/brooklyn/location/core/localhost/LocalhostMachineProvisioningLocationTest.java new file mode 100644 index 0000000..4735350 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/location/core/localhost/LocalhostMachineProvisioningLocationTest.java @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.core.localhost; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.fail; + +import java.net.ServerSocket; + +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.net.Networking; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.api.location.MachineProvisioningLocation; +import org.apache.brooklyn.api.location.NoMachinesAvailableException; +import org.apache.brooklyn.api.location.PortRange; +import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.location.core.PortRanges; +import org.apache.brooklyn.location.geo.HostGeoInfo; +import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation; +import org.apache.brooklyn.location.ssh.SshMachineLocation; + +public class LocalhostMachineProvisioningLocationTest { + + private static final Logger log = LoggerFactory.getLogger(LocalhostMachineProvisioningLocationTest.class); + + private LocalManagementContext mgmt; + + @BeforeMethod + @AfterClass + protected void clearStatics() { + LocalhostMachineProvisioningLocation.clearStaticData(); + } + + @BeforeClass + protected void setup() { + mgmt = LocalManagementContextForTests.newInstance(); + } + + @AfterClass + protected void teardown() { + Entities.destroyAll(mgmt); + } + + protected LocalhostMachineProvisioningLocation newLocalhostProvisioner() { + return mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)); + } + + protected LocalhostMachineProvisioningLocation newLocalhostProvisionerWithAddress(String address) { + return mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class) + .configure("address", address)); + } + + @Test + public void defaultInvocationCanProvisionALocalhostInstance() throws Exception { + LocalhostMachineProvisioningLocation provisioner = mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)); + SshMachineLocation machine = provisioner.obtain(); + assertNotNull(machine); + assertEquals(machine.getAddress(), Networking.getLocalHost()); + } + + @Test + public void testUsesLocationNameProvided() throws Exception { + LocalhostMachineProvisioningLocation provisioner = newLocalhostProvisionerWithAddress("localhost"); + assertEquals(((SshMachineLocation)provisioner.obtain()).getAddress().getHostName(), "localhost"); + + LocalhostMachineProvisioningLocation provisioner2 = newLocalhostProvisionerWithAddress("1.2.3.4"); + assertEquals(((SshMachineLocation)provisioner2.obtain()).getAddress().getHostName(), "1.2.3.4"); + + LocalhostMachineProvisioningLocation provisioner3 = newLocalhostProvisionerWithAddress("127.0.0.1"); + assertEquals(((SshMachineLocation)provisioner3.obtain()).getAddress().getHostName(), "127.0.0.1"); + } + + public void provisionWithASpecificNumberOfInstances() throws NoMachinesAvailableException { + LocalhostMachineProvisioningLocation provisioner = mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class) + .configure("count", 2)); + + // first machine + SshMachineLocation first = provisioner.obtain(); + assertNotNull(first); + assertEquals(first.getAddress(), Networking.getLocalHost()); + + // second machine + SshMachineLocation second = provisioner.obtain(); + assertNotNull(second); + assertEquals(second.getAddress(), Networking.getLocalHost()); + + // third machine - fails + try { + SshMachineLocation third = provisioner.obtain(); + fail("did not throw expected exception; got "+third); + } catch (NoMachinesAvailableException e) { + /* expected */ + } + } + + @Test + public void obtainTwoAddressesInRangeThenDontObtain() throws Exception { + LocalhostMachineProvisioningLocation p = newLocalhostProvisioner(); + SshMachineLocation m = p.obtain(); + + // Find two ports that are free, rather than risk false-negatives if a port was left open by something else. + int start = 48311; + while (true) { + if (Networking.isPortAvailable(m.getAddress(), start) && Networking.isPortAvailable(m.getAddress(), start+1)) { + break; + } else { + start++; + } + } + PortRange r = PortRanges.fromString(""+start+"-"+(start+1)); + + try { + int i1 = m.obtainPort(r); + Assert.assertEquals(i1, start); + int i2 = m.obtainPort(r); + Assert.assertEquals(i2, start+1); + + //should fail + int i3 = m.obtainPort(r); + Assert.assertEquals(i3, -1); + + //releasing and reapplying should succed + m.releasePort(i2); + int i4 = m.obtainPort(r); + Assert.assertEquals(i4, i2); + + } finally { + m.releasePort(start); + m.releasePort(start+1); + } + } + + @Test + public void obtainLowNumberedPortsAutomatically() throws Exception { + LocalhostMachineProvisioningLocation p = newLocalhostProvisioner(); + SshMachineLocation m = p.obtain(); + int start = 983; //random rarely used port, not that it matters + try { + int actual = m.obtainPort(PortRanges.fromInteger(start)); + Assert.assertEquals(actual, start); + } finally { + m.releasePort(start); + } + + } + + @Test + public void obtainPortFailsIfInUse() throws Exception { + LocalhostMachineProvisioningLocation p = newLocalhostProvisioner(); + SshMachineLocation m = p.obtain(); + + // Find two ports that are free, rather than risk false-negatives if a port was left open by something else. + int start = 48311; + while (true) { + if (Networking.isPortAvailable(m.getAddress(), start) && Networking.isPortAvailable(m.getAddress(), start+1)) { + break; + } else { + start++; + } + } + PortRange r = PortRanges.fromString(""+start+"-"+(start+1)); + + ServerSocket ss = null; + try { + ss = new ServerSocket(start, 0, m.getAddress()); + int i1 = m.obtainPort(r); + Assert.assertEquals(i1, start+1); + } finally { + if (ss!=null) ss.close(); + m.releasePort(start); + m.releasePort(start+1); + } + } + + @Test + public void obtainLocationWithGeography() throws Exception { + mgmt.getBrooklynProperties().put("brooklyn.location.named.lhx", "localhost"); + // bogus location so very little chance of it being what maxmind returns! + mgmt.getBrooklynProperties().put("brooklyn.location.named.lhx.latitude", 42d); + mgmt.getBrooklynProperties().put("brooklyn.location.named.lhx.longitude", -20d); + MachineProvisioningLocation<?> p = (MachineProvisioningLocation<?>) mgmt.getLocationRegistry().resolve("named:lhx"); + SshMachineLocation m = (SshMachineLocation) p.obtain(MutableMap.of()); + HostGeoInfo geo = HostGeoInfo.fromLocation(m); + log.info("Geo info for "+m+" is: "+geo); + Assert.assertEquals(geo.latitude, 42d, 0.00001); + Assert.assertEquals(geo.longitude, -20d, 0.00001); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/test/java/org/apache/brooklyn/location/core/localhost/LocalhostProvisioningAndAccessTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/core/localhost/LocalhostProvisioningAndAccessTest.java b/core/src/test/java/org/apache/brooklyn/location/core/localhost/LocalhostProvisioningAndAccessTest.java new file mode 100644 index 0000000..3bb594e --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/location/core/localhost/LocalhostProvisioningAndAccessTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.core.localhost; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.util.Arrays; + +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.internal.BrooklynProperties; +import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation; +import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class LocalhostProvisioningAndAccessTest { + + private LocalManagementContext mgmt; + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + mgmt = new LocalManagementContext(BrooklynProperties.Factory.newDefault()); + } + + @AfterMethod(alwaysRun = true) + public void tearDown(){ + if (mgmt != null) Entities.destroyAll(mgmt); + } + + @Test(groups="Integration") + public void testProvisionAndConnect() throws Exception { + Location location = mgmt.getLocationRegistry().resolve("localhost"); + assertTrue(location instanceof LocalhostMachineProvisioningLocation); + SshMachineLocation m = ((LocalhostMachineProvisioningLocation)location).obtain(); + int result = m.execCommands("test", Arrays.asList("echo hello world")); + assertEquals(result, 0); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoInfoTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoInfoTest.java b/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoInfoTest.java index 2ca5807..56c1b41 100644 --- a/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoInfoTest.java +++ b/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoInfoTest.java @@ -22,7 +22,7 @@ import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertNotNull; import org.apache.brooklyn.api.location.Location; -import org.apache.brooklyn.location.basic.SimulatedLocation; +import org.apache.brooklyn.location.core.SimulatedLocation; import org.apache.brooklyn.util.collections.MutableMap; import org.testng.annotations.Test; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoLookupIntegrationTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoLookupIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoLookupIntegrationTest.java index c80534f..1990905 100644 --- a/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoLookupIntegrationTest.java +++ b/core/src/test/java/org/apache/brooklyn/location/geo/HostGeoLookupIntegrationTest.java @@ -24,8 +24,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.Test; -import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation; -import org.apache.brooklyn.location.basic.SshMachineLocation; +import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation; +import org.apache.brooklyn.location.ssh.SshMachineLocation; import org.apache.brooklyn.util.time.Duration; import com.google.common.base.Objects; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationIntegrationTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationIntegrationTest.java new file mode 100644 index 0000000..cd9f724 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationIntegrationTest.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.ssh; + +import java.io.ByteArrayOutputStream; +import java.security.KeyPair; +import java.util.Arrays; +import java.util.Map; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.entity.core.Entities; +import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation; +import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.crypto.SecureKeys; +import org.apache.brooklyn.util.core.internal.ssh.SshTool; +import org.apache.brooklyn.util.core.internal.ssh.sshj.SshjTool; +import org.apache.brooklyn.util.core.internal.ssh.sshj.SshjTool.SshjToolBuilder; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Preconditions; + +import static org.testng.Assert.assertEquals; + +public class SshMachineLocationIntegrationTest { + + protected TestApplication app; + protected ManagementContext mgmt; + + @BeforeMethod(alwaysRun=true) + public void setup() throws Exception { + mgmt = LocalManagementContextForTests.builder(true) + .useDefaultProperties() + .build(); + app = TestApplication.Factory.newManagedInstanceForTests(mgmt); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (mgmt != null) Entities.destroyAll(mgmt); + mgmt = null; + } + + // Note: requires `named:localhost-passphrase` set up with a key whose passphrase is "localhost" + // * create the key with: + // ssh-keygen -t rsa -N "brooklyn" -f ~/.ssh/id_rsa_passphrase + // ssh-copy-id localhost + // * create brooklyn.properties, containing: + // brooklyn.location.named.localhost-passphrase=localhost + // brooklyn.location.named.localhost-passphrase.privateKeyFile=~/.ssh/id_rsa_passphrase + // brooklyn.location.named.localhost-passphrase.privateKeyPassphrase=brooklyn + @Test(groups = "Integration") + public void testExtractingConnectablePassphraselessKey() throws Exception { + LocalhostMachineProvisioningLocation lhp = (LocalhostMachineProvisioningLocation) mgmt.getLocationRegistry().resolve("named:localhost-passphrase", true, null).orNull(); + Preconditions.checkNotNull(lhp, "This test requires a localhost named location called 'localhost-passphrase' (which should have a passphrase set)"); + SshMachineLocation sm = lhp.obtain(); + + SshjToolBuilder builder = SshjTool.builder().host(sm.getAddress().getHostName()).user(sm.getUser()); + + KeyPair data = sm.findKeyPair(); + if (data!=null) builder.privateKeyData(SecureKeys.toPem(data)); + String password = sm.findPassword(); + if (password!=null) builder.password(password); + SshjTool tool = builder.build(); + tool.connect(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int result = tool.execCommands(MutableMap.<String,Object>of("out", out), Arrays.asList("date")); + Assert.assertTrue(out.toString().contains(" 20"), "out="+out); + assertEquals(result, 0); + } + + @Test(groups = "Integration") + public void testExecScriptScriptDirFlagIsRespected() throws Exception { + // For explanation of (some of) the magic behind this command, see http://stackoverflow.com/a/229606/68898 + final String command = "if [[ \"$0\" == \"/var/tmp/\"* ]]; then true; else false; fi"; + + LocalhostMachineProvisioningLocation lhp = (LocalhostMachineProvisioningLocation) mgmt.getLocationRegistry().resolve("localhost", true, null).orNull(); + SshMachineLocation sm = lhp.obtain(); + + Map<String, Object> props = ImmutableMap.<String, Object>builder() + .put(SshTool.PROP_SCRIPT_DIR.getName(), "/var/tmp") + .build(); + int rc = sm.execScript(props, "Test script directory execution", ImmutableList.of(command)); + assertEquals(rc, 0); + } + + @Test(groups = "Integration") + public void testLocationScriptDirConfigIsRespected() throws Exception { + // For explanation of (some of) the magic behind this command, see http://stackoverflow.com/a/229606/68898 + final String command = "if [[ \"$0\" == \"/var/tmp/\"* ]]; then true; else false; fi"; + + Map<String, Object> locationConfig = ImmutableMap.<String, Object>builder() + .put(SshMachineLocation.SCRIPT_DIR.getName(), "/var/tmp") + .build(); + + LocalhostMachineProvisioningLocation lhp = (LocalhostMachineProvisioningLocation) mgmt.getLocationRegistry().resolve("localhost", locationConfig); + SshMachineLocation sm = lhp.obtain(); + + int rc = sm.execScript("Test script directory execution", ImmutableList.of(command)); + assertEquals(rc, 0); + } + + @Test(groups = "Integration") + public void testMissingLocationScriptDirIsAlsoOkay() throws Exception { + final String command = "echo hello"; + + Map<String, Object> locationConfig = ImmutableMap.<String, Object>builder() +// .put(SshMachineLocation.SCRIPT_DIR.getName(), "/var/tmp") + .build(); + + LocalhostMachineProvisioningLocation lhp = (LocalhostMachineProvisioningLocation) mgmt.getLocationRegistry().resolve("localhost", locationConfig); + SshMachineLocation sm = lhp.obtain(); + + int rc = sm.execScript("Test script directory execution", ImmutableList.of(command)); + assertEquals(rc, 0); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationPerformanceTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationPerformanceTest.java b/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationPerformanceTest.java new file mode 100644 index 0000000..27ca938 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationPerformanceTest.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.ssh; + +import java.io.ByteArrayOutputStream; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.apache.brooklyn.test.PerformanceTestUtils; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.internal.ssh.SshTool; +import org.apache.brooklyn.util.net.Networking; +import org.apache.brooklyn.util.stream.Streams; +import org.apache.brooklyn.util.text.Identifiers; +import org.apache.brooklyn.util.time.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Stopwatch; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; + +/** + * Test the performance of different variants of invoking the sshj tool. + * + * Intended for human-invocation and inspection, to see which parts are most expensive. + */ +public class SshMachineLocationPerformanceTest { + + private static final Logger LOG = LoggerFactory.getLogger(SshMachineLocationPerformanceTest.class); + + private SshMachineLocation machine; + private ListeningExecutorService executor; + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + machine = new SshMachineLocation(MutableMap.of("address", "localhost")); + executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); + } + + @AfterMethod(alwaysRun=true) + public void afterMethod() throws Exception { + if (executor != null) executor.shutdownNow(); + Streams.closeQuietly(machine); + } + + @Test(groups = {"Integration"}) + public void testConsecutiveSmallCommands() throws Exception { + runExecManyCommands(ImmutableList.of("true"), "small-cmd", 10); + } + + // Mimics SshSensorAdapter's polling + @Test(groups = {"Integration"}) + public void testConsecutiveSmallCommandsWithCustomStdoutAndErr() throws Exception { + final ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + final ByteArrayOutputStream stderr = new ByteArrayOutputStream(); + + Runnable task = new Runnable() { + @Override public void run() { + machine.execScript(ImmutableMap.of("out", stdout, "err", stderr), "test", ImmutableList.of("true")); + }}; + runMany(task, "small-cmd-custom-stdout", 1, 10); + } + + @Test(groups = {"Integration"}) + public void testConcurrentSmallCommands() throws Exception { + runExecManyCommands(ImmutableList.of("true"), "small-cmd", 10, 10); + } + + @Test(groups = {"Integration"}) + public void testConsecutiveBigStdoutCommands() throws Exception { + runExecManyCommands(ImmutableList.of("head -c 100000 /dev/urandom"), "big-stdout", 10); + } + + @Test(groups = {"Integration"}) + public void testConsecutiveBigStdinCommands() throws Exception { + String bigstr = Identifiers.makeRandomId(100000); + runExecManyCommands(ImmutableList.of("echo "+bigstr+" | wc -c"), "big-stdin", 10); + } + + @Test(groups = {"Integration"}) + public void testConsecutiveSmallCommandsWithDifferentProperties() throws Exception { + final Map<String, ?> emptyProperties = Collections.emptyMap(); + final Map<String, ?> customProperties = MutableMap.of( + "address", Networking.getLocalHost(), + SshTool.PROP_SESSION_TIMEOUT.getName(), 20000, + SshTool.PROP_CONNECT_TIMEOUT.getName(), 50000, + SshTool.PROP_SCRIPT_HEADER.getName(), "#!/bin/bash"); + + Runnable task = new Runnable() { + @Override public void run() { + if (Math.random() < 0.5) { + machine.execScript(emptyProperties, "test", ImmutableList.of("true")); + } else { + machine.execScript(customProperties, "test", ImmutableList.of("true")); + } + }}; + runMany(task, "small-cmd-custom-ssh-properties", 1, 10); + } + + private void runExecManyCommands(final List<String> cmds, String context, int iterations) throws Exception { + runExecManyCommands(cmds, context, 1, iterations); + } + + private void runExecManyCommands(final List<String> cmds, String context, int concurrentRuns, int iterations) throws Exception { + Runnable task = new Runnable() { + @Override public void run() { + execScript(cmds); + }}; + runMany(task, context, concurrentRuns, iterations); + } + + private void runMany(final Runnable task, final String context, int concurrentRuns, int iterations) throws Exception { + long preCpuTime = PerformanceTestUtils.getProcessCpuTime(); + Stopwatch stopwatch = Stopwatch.createStarted(); + + for (int i = 0; i < iterations; i++) { + List<ListenableFuture<?>> futures = Lists.newArrayList(); + for (int j = 0; j < concurrentRuns; j++) { + futures.add(executor.submit(new Runnable() { + public void run() { + try { + task.run(); + } catch (Exception e) { + LOG.error("Error for "+context+", executing "+task, e); + throw Throwables.propagate(e); + } + }})); + } + Futures.allAsList(futures).get(); + + long postCpuTime = PerformanceTestUtils.getProcessCpuTime(); + long elapsedTime = stopwatch.elapsed(TimeUnit.MILLISECONDS); + double fractionCpu = (elapsedTime > 0) ? ((double)postCpuTime-preCpuTime) / TimeUnit.MILLISECONDS.toNanos(elapsedTime) : -1; + LOG.info("Executing {}; completed {}; took {}; fraction cpu {}", + new Object[] {context, (i+1), Time.makeTimeStringRounded(elapsedTime), fractionCpu}); + } + } + + private int execScript(List<String> cmds) { + return machine.execScript("mysummary", cmds); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a1ad34d7/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationReuseIntegrationTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationReuseIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationReuseIntegrationTest.java new file mode 100644 index 0000000..e129dc1 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/location/ssh/SshMachineLocationReuseIntegrationTest.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.ssh; + +import static org.testng.Assert.assertEquals; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.entity.core.Entities; +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.core.internal.ssh.SshTool; +import org.apache.brooklyn.util.core.internal.ssh.sshj.SshjTool; +import org.apache.brooklyn.util.net.Networking; +import org.apache.brooklyn.util.stream.Streams; +import org.apache.brooklyn.util.time.Duration; + +import com.google.common.collect.ImmutableList; + +/** + * Tests the re-use of SshTools in SshMachineLocation + */ +public class SshMachineLocationReuseIntegrationTest { + + public static class RecordingSshjTool extends SshjTool { + public static final AtomicBoolean forbidden = new AtomicBoolean(false); + public static final AtomicInteger connectionCount = new AtomicInteger(0); + public static final AtomicInteger disconnectionCount = new AtomicInteger(); + + public RecordingSshjTool(Map<String, ?> map) { + super(map); + } + + @Override + public void connect() { + if (forbidden.get()) throw new IllegalStateException("forbidden at this time"); + connectionCount.incrementAndGet(); + super.connect(); + } + + @Override + public void disconnect() { + disconnectionCount.incrementAndGet(); + super.disconnect(); + } + + public static void reset() { + forbidden.set(false); + connectionCount.set(0); + disconnectionCount.set(0); + } + + @Override + public int execCommands(Map<String, ?> props, List<String> commands, Map<String, ?> env) { + if (forbidden.get()) throw new IllegalStateException("forbidden at this time"); + return super.execCommands(props, commands, env); + } + + @Override + public int execScript(Map<String, ?> props, List<String> commands, Map<String, ?> env) { + if (forbidden.get()) throw new IllegalStateException("forbidden at this time"); + return super.execScript(props, commands, env); + } + + @Override + public int execShellDirect(Map<String, ?> props, List<String> commands, Map<String, ?> env) { + if (forbidden.get()) throw new IllegalStateException("forbidden at this time"); + return super.execShellDirect(props, commands, env); + } + } + + private SshMachineLocation host; + private LocalManagementContext managementContext; + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + managementContext = new LocalManagementContext(); + host = managementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class) + .configure("address", Networking.getLocalHost()) + .configure(SshTool.PROP_TOOL_CLASS, RecordingSshjTool.class.getName())); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (host != null) Streams.closeQuietly(host); + if (managementContext != null) Entities.destroyAll(managementContext); + RecordingSshjTool.reset(); + } + + @Test(groups = "Integration") + public void testBasicReuse() throws Exception { + host.execScript("mysummary", ImmutableList.of("exit")); + host.execScript("mysummary", ImmutableList.of("exit")); + assertEquals(RecordingSshjTool.connectionCount.get(), 1, "Expected one SSH connection to have been recorded"); + } + + @Test(groups = "Integration") + public void testReuseWithInterestingProps() throws Exception { + host.execScript(customSshConfigKeys(), "mysummary", ImmutableList.of("exit")); + host.execScript(customSshConfigKeys(), "mysummary", ImmutableList.of("exit")); + assertEquals(RecordingSshjTool.connectionCount.get(), 1, "Expected one SSH connection to have been recorded"); + } + + @Test(groups = "Integration") + public void testNewConnectionForDifferentProps() throws Exception { + host.execScript("mysummary", ImmutableList.of("exit")); + host.execScript(customSshConfigKeys(), "mysummary", ImmutableList.of("exit")); + assertEquals(RecordingSshjTool.connectionCount.get(), 2, "Expected two SSH connections to have been recorded"); + } + + @Test(groups = "Integration") + public void testSshToolReusedWhenConfigDiffers() throws Exception { + Map<String, Object> props = customSshConfigKeys(); + host.execScript(props, "mysummary", ImmutableList.of("exit")); + + // Use another output stream for second request + props.put(SshTool.PROP_SCRIPT_HEADER.getName(), "#!/bin/bash -e\n"); + host.execScript(props, "mysummary", ImmutableList.of("exit")); + assertEquals(RecordingSshjTool.connectionCount.get(), 1, "Expected one SSH connection to have been recorded even though out script header differed."); + } + + @Test(groups = "Integration") + public void testSshCacheExpiresEvenIfNotUsed() throws Exception { + SshMachineLocation host2 = managementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class) + .configure("address", InetAddress.getLocalHost()) + .configure(SshMachineLocation.SSH_CACHE_EXPIRY_DURATION, Duration.ONE_SECOND) + .configure(SshTool.PROP_TOOL_CLASS, RecordingSshjTool.class.getName())); + + Map<String, Object> props = customSshConfigKeys(); + host2.execScript(props, "mysummary", ImmutableList.of("exit")); + + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + assertEquals(RecordingSshjTool.disconnectionCount.get(), 1); + }}); + } + + public Map<String, Object> customSshConfigKeys() throws UnknownHostException { + return MutableMap.<String, Object>of( + "address", Networking.getLocalHost(), + SshTool.PROP_SESSION_TIMEOUT.getName(), 20000, + SshTool.PROP_CONNECT_TIMEOUT.getName(), 50000, + SshTool.PROP_SCRIPT_HEADER.getName(), "#!/bin/bash"); + } +}
