diff --git a/compute/src/main/java/org/jclouds/compute/pool/ConnectedNode.java b/compute/src/main/java/org/jclouds/compute/pool/ConnectedNode.java new file mode 100644 index 0000000000..c29df9198b --- /dev/null +++ b/compute/src/main/java/org/jclouds/compute/pool/ConnectedNode.java @@ -0,0 +1,77 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.jclouds.compute.pool; + +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.annotation.Nullable; + +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.NodeMetadataBuilder; +import org.jclouds.domain.Credentials; +import org.jclouds.ssh.SshClient; +import org.jclouds.util.Utils; + +import com.google.common.base.Function; + +/** + * Represents a node that has an active ssh connection. + * + *

+ * Inspired by work by Aslak Knutsen in the Arquillian project + * + * @author Aslak Knutsen + * @author Adrian Cole + */ +public class ConnectedNode { + private final SshClient sshClient; + private final NodeMetadata nodeMetadata; + + public ConnectedNode(Function createSshClientOncePortIsListeningOnNode, + NodeMetadata nodeMetadata, @Nullable Credentials overridingLoginCredentials) { + this.nodeMetadata = NodeMetadataBuilder + .fromNodeMetadata(checkNotNull(nodeMetadata, "nodeMetadata")) + .credentials(Utils.overrideCredentialsIfSupplied(nodeMetadata.getCredentials(), overridingLoginCredentials)) + .build(); + this.sshClient = createSshClientOncePortIsListeningOnNode.apply(nodeMetadata); + } + + /** + * @return the nodeMetadata + */ + public NodeMetadata getNodeMetadata() { + return nodeMetadata; + } + + /** + * @return the sshClient + */ + public SshClient getSshClient() { + return sshClient; + } + + protected void connect() { + sshClient.connect(); + } + + protected void disconnect() { + sshClient.disconnect(); + } +} diff --git a/compute/src/main/java/org/jclouds/compute/pool/ConnectedNodeCreator.java b/compute/src/main/java/org/jclouds/compute/pool/ConnectedNodeCreator.java new file mode 100644 index 0000000000..06e8d88952 --- /dev/null +++ b/compute/src/main/java/org/jclouds/compute/pool/ConnectedNodeCreator.java @@ -0,0 +1,76 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.jclouds.compute.pool; + +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.domain.Credentials; +import org.jclouds.pool.Creator; + +/** + * A base creator that connects to the node using SSH on create and disconnects on destroy.
+ * When the created {@link ConnectedNode} is available in the pool it contains a open ssh + * connection. + *

+ * Inspired by work by Aslak Knutsen in the Arquillian project + * + * @author Aslak Knutsen + * @author Adrian Cole + */ +public abstract class ConnectedNodeCreator implements Creator { + private ComputeServiceContext computeContext; + + private Credentials credentials; + + public ConnectedNodeCreator(ComputeServiceContext computeContext) { + this.computeContext = computeContext; + } + + /** + * @param certificate + * the certificate to set + */ + public ConnectedNodeCreator setLoginCredentials(Credentials credentials) { + this.credentials = credentials; + return this; + } + + /** + * @return the computeContext + */ + public ComputeServiceContext getComputeContext() { + return computeContext; + } + + public final ConnectedNode create() { + ConnectedNode node = new ConnectedNode(computeContext.utils().sshForNode(), createNode(), credentials); + node.connect(); + return node; + } + + public final void destroy(ConnectedNode node) { + node.disconnect(); + destroyNode(node.getNodeMetadata()); + } + + public abstract NodeMetadata createNode(); + + public abstract void destroyNode(NodeMetadata nodeMetadata); +} \ No newline at end of file diff --git a/compute/src/main/java/org/jclouds/compute/pool/ConnectedNodePool.java b/compute/src/main/java/org/jclouds/compute/pool/ConnectedNodePool.java new file mode 100644 index 0000000000..c4847c9391 --- /dev/null +++ b/compute/src/main/java/org/jclouds/compute/pool/ConnectedNodePool.java @@ -0,0 +1,44 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.jclouds.compute.pool; + +import org.jclouds.pool.ObjectPool; +import org.jclouds.pool.PooledObject; + +/** + * Inspired by work by Aslak Knutsen in the Arquillian project + * + * @author Aslak Knutsen + */ +public class ConnectedNodePool { + private ObjectPool pool; + + public ConnectedNodePool(ObjectPool pool) { + this.pool = pool; + } + + public PooledObject getNode() { + return pool.get(); + } + + public void shutdownAll() { + pool.shutdown(); + } +} diff --git a/compute/src/main/java/org/jclouds/compute/pool/strategy/CreateNodesOnDemandConnectedNodeCreator.java b/compute/src/main/java/org/jclouds/compute/pool/strategy/CreateNodesOnDemandConnectedNodeCreator.java new file mode 100644 index 0000000000..5def73f281 --- /dev/null +++ b/compute/src/main/java/org/jclouds/compute/pool/strategy/CreateNodesOnDemandConnectedNodeCreator.java @@ -0,0 +1,56 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.jclouds.compute.pool.strategy; + +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.RunNodesException; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.Template; +import org.jclouds.compute.pool.ConnectedNodeCreator; + +/** + * Inspired by work by Aslak Knutsen in the Arquillian project + * + * @author Aslak Knutsen + */ +public class CreateNodesOnDemandConnectedNodeCreator extends ConnectedNodeCreator { + private Template template; + private String tag; + + public CreateNodesOnDemandConnectedNodeCreator(ComputeServiceContext context, Template template, String tag) { + super(context); + this.template = template; + this.tag = tag; + } + + @Override + public NodeMetadata createNode() { + try { + return getComputeContext().getComputeService().runNodesWithTag(tag, 1, template).iterator().next(); + } catch (RunNodesException e) { + throw new RuntimeException(e); + } + } + + @Override + public void destroyNode(NodeMetadata nodeMetadata) { + getComputeContext().getComputeService().destroyNode(nodeMetadata.getId()); + } +} diff --git a/compute/src/main/java/org/jclouds/compute/pool/strategy/ExistingPoolConnectedNodeCreator.java b/compute/src/main/java/org/jclouds/compute/pool/strategy/ExistingPoolConnectedNodeCreator.java new file mode 100644 index 0000000000..14a389b80e --- /dev/null +++ b/compute/src/main/java/org/jclouds/compute/pool/strategy/ExistingPoolConnectedNodeCreator.java @@ -0,0 +1,79 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.jclouds.compute.pool.strategy; + +import java.util.Iterator; + +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.domain.ComputeMetadata; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.NodeState; +import org.jclouds.compute.pool.ConnectedNodeCreator; +import org.jclouds.pool.Creator; + +import com.google.common.base.Predicate; + +/** + * A {@link Creator} that can match up against + * + *

+ * Inspired by work by Aslak Knutsen in the Arquillian project + * + * @author Aslak Knutsen + */ +public class ExistingPoolConnectedNodeCreator extends ConnectedNodeCreator { + private String tag; + + private Iterator foundNodes; + + public ExistingPoolConnectedNodeCreator(ComputeServiceContext context, String tag) { + super(context); + this.tag = tag; + } + + @Override + public NodeMetadata createNode() { + synchronized (this) { + if (foundNodes == null) { + foundNodes = getComputeContext().getComputeService() + .listNodesDetailsMatching(new Predicate() { + public boolean apply(ComputeMetadata input) { + if (input instanceof NodeMetadata) { + NodeMetadata nodeMetadata = (NodeMetadata) input; + if (tag.equals(nodeMetadata.getTag()) && nodeMetadata.getState() == NodeState.RUNNING) { + return true; + } + } + return false; + } + }).iterator(); + } + if (foundNodes.hasNext()) { + return (NodeMetadata) foundNodes.next(); + } else { + throw new RuntimeException("Requested more nodes in pool then found in cloud"); + } + } + } + + @Override + public void destroyNode(NodeMetadata nodeMetadata) { + } +} diff --git a/compute/src/main/java/org/jclouds/compute/pool/strategy/SingletonExistingNodeCreator.java b/compute/src/main/java/org/jclouds/compute/pool/strategy/SingletonExistingNodeCreator.java new file mode 100644 index 0000000000..7496d0f443 --- /dev/null +++ b/compute/src/main/java/org/jclouds/compute/pool/strategy/SingletonExistingNodeCreator.java @@ -0,0 +1,48 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.jclouds.compute.pool.strategy; + +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.pool.ConnectedNodeCreator; + +/** + * Inspired by work by Aslak Knutsen in the Arquillian project + * + * @author Aslak Knutsen + */ +public class SingletonExistingNodeCreator extends ConnectedNodeCreator { + private String nodeId; + + public SingletonExistingNodeCreator(ComputeServiceContext context, String nodeId) { + super(context); + this.nodeId = nodeId; + } + + @Override + public NodeMetadata createNode() { + return getComputeContext().getComputeService().getNodeMetadata(nodeId); + } + + @Override + public void destroyNode(NodeMetadata nodeMetadata) { + // no op, don't destroy something we did not create. + } +} diff --git a/core/src/main/java/org/jclouds/pool/Creator.java b/core/src/main/java/org/jclouds/pool/Creator.java new file mode 100644 index 0000000000..cb171a8457 --- /dev/null +++ b/core/src/main/java/org/jclouds/pool/Creator.java @@ -0,0 +1,32 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.jclouds.pool; + +/** + * Inspired by work by Aslak Knutsen in the Arquillian project + * + * @author Aslak Knutsen + */ +public interface Creator extends Destroyer +{ + + T create(); + +} diff --git a/core/src/main/java/org/jclouds/pool/Destroyer.java b/core/src/main/java/org/jclouds/pool/Destroyer.java new file mode 100644 index 0000000000..4a4b156c64 --- /dev/null +++ b/core/src/main/java/org/jclouds/pool/Destroyer.java @@ -0,0 +1,29 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.jclouds.pool; + +/** + * Inspired by work by Aslak Knutsen in the Arquillian project + * + * @author Aslak Knutsen + */ +public interface Destroyer { + void destroy(T object); +} diff --git a/core/src/main/java/org/jclouds/pool/ObjectPool.java b/core/src/main/java/org/jclouds/pool/ObjectPool.java new file mode 100644 index 0000000000..6d14bc50f4 --- /dev/null +++ b/core/src/main/java/org/jclouds/pool/ObjectPool.java @@ -0,0 +1,179 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.jclouds.pool; + +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.FutureTask; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Inspired by work by Aslak Knutsen in the Arquillian project + * + * @author Aslak Knutsen + */ +public class ObjectPool { + + private final Creator creator; + private final UsedObjectStrategy usedObjectStrategy; + private final int poolSize; + private final PoolListener listener; + private final ConcurrentLinkedQueue pool = new ConcurrentLinkedQueue(); + private final ExecutorService executor; + + public enum UsedObjectStrategy { + THROW_AWAY, REUSE + } + + public interface PoolListener { + + void added(T object); + } + + public static final class EmptyListener implements PoolListener { + public void added(B object) { + } + } + + public ObjectPool(Creator creator, int poolSize, UsedObjectStrategy usedObjectStrategy) { + this(creator, poolSize, usedObjectStrategy, new EmptyListener()); + } + + public ObjectPool(Creator creator, int poolSize, UsedObjectStrategy usedObjectStrategy, PoolListener listener) { + this(creator, poolSize, usedObjectStrategy, listener, new ThreadPoolExecutor(3, Integer.MAX_VALUE, 60L, + TimeUnit.SECONDS, new SynchronousQueue())); + } + + public ObjectPool(Creator creator, int poolSize, UsedObjectStrategy usedObjectStrategy, PoolListener listener, + ExecutorService executor) { + this.creator = creator; + this.poolSize = poolSize; + this.usedObjectStrategy = usedObjectStrategy; + this.listener = listener; + this.executor = executor; + initialize(); + } + + private void initialize() { + for (int i = 0; i < poolSize; i++) { + createNewObject(); + } + } + + public synchronized void shutdown() { + while (pool.size() > 0) { + removeObject(pool.poll()); + } + executor.shutdown(); + try { + int maxShutdownTimeoutInMinutes = 5; + if (!executor.awaitTermination(maxShutdownTimeoutInMinutes, TimeUnit.MINUTES)) { + throw new RuntimeException("Not all object were destroyed, timeout accured: " + maxShutdownTimeoutInMinutes + + " min"); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + protected void removeObject(final T object) { + executor.execute(new Runnable() { + public void run() { + creator.destroy(object); + } + }); + } + + protected void createNewObject() { + FutureTask futureTask = new FutureTask(new Callable() { + public T call() throws Exception { + T object = creator.create(); + addToPool(object); + return object; + } + }); + executor.submit(futureTask); + } + + protected void addToPool(T object) { + pool.add(object); + listener.added(object); + } + + public int currentSize() { + return pool.size(); + } + + public PooledObject get() { + return new PooledObject(getFromPoolBlocking(), getDestroyer()); + } + + /** + * @return + */ + private T getFromPoolBlocking() { + T object; + while (true) { + while (pool.size() == 0) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + object = pool.poll(); + boolean returnToPool = true; + try { + if (object != null) { + returnToPool = false; + return object; + } + } catch (Exception e) { + } finally { + if (returnToPool) { + addToPool(object); + } + } + } + } + + public Destroyer getDestroyer() { + switch (usedObjectStrategy) { + case THROW_AWAY: + return new Destroyer() { + public void destroy(T object) { + createNewObject(); + removeObject(object); + }; + }; + + case REUSE: + return new Destroyer() { + public void destroy(T object) { + addToPool(object); + }; + }; + } + return null; + } +} diff --git a/core/src/main/java/org/jclouds/pool/PooledObject.java b/core/src/main/java/org/jclouds/pool/PooledObject.java new file mode 100644 index 0000000000..f5eeff301a --- /dev/null +++ b/core/src/main/java/org/jclouds/pool/PooledObject.java @@ -0,0 +1,44 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.jclouds.pool; + +/** + * Inspired by work by Aslak Knutsen in the Arquillian project + * + * @author Aslak Knutsen + */ +public class PooledObject { + private Destroyer destroyer; + + private T object; + + public PooledObject(T object, Destroyer destroyer) { + this.object = object; + this.destroyer = destroyer; + } + + public T get() { + return object; + } + + public void close() { + destroyer.destroy(object); + } +} diff --git a/core/src/test/java/org/jclouds/pool/ObjectPoolTestCase.java b/core/src/test/java/org/jclouds/pool/ObjectPoolTestCase.java new file mode 100644 index 0000000000..16ca9aa1a1 --- /dev/null +++ b/core/src/test/java/org/jclouds/pool/ObjectPoolTestCase.java @@ -0,0 +1,90 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.jclouds.pool; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import junit.framework.Assert; + +import org.jclouds.pool.ObjectPool.UsedObjectStrategy; +import org.testng.annotations.Test; + +/** + * Inspired by work by Aslak Knutsen in the Arquillian project + * + * @author Aslak Knutsen + */ +public class ObjectPoolTestCase { + @Test + public void shouldCreateInitialSize() throws Exception { + final CountDownLatch objectCreation = new CountDownLatch(2); + final CountDownLatch objectDestruction = new CountDownLatch(2); + + Creator creator = new Creator() { + public void destroy(HeavyObject object) { + objectDestruction.countDown(); + } + + public HeavyObject create() { + return new HeavyObject(); + } + }; + ObjectPool pool = new ObjectPool(creator, 2, UsedObjectStrategy.REUSE, + new ObjectPool.PoolListener() { + public void added(HeavyObject object) { + objectCreation.countDown(); + } + }); + + try { + if (!objectCreation.await(2, TimeUnit.SECONDS)) { + Assert.fail("ObjectCreation did not happen"); + } + + Assert.assertEquals(2, pool.currentSize()); + PooledObject object = pool.get(); + + Assert.assertEquals(1, pool.currentSize()); + + PooledObject object2 = pool.get(); + Assert.assertEquals(0, pool.currentSize()); + + object.close(); + Assert.assertEquals(1, pool.currentSize()); + + object2.close(); + Assert.assertEquals(2, pool.currentSize()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + pool.shutdown(); + Assert.assertEquals(0, pool.currentSize()); + + if (!objectDestruction.await(1, TimeUnit.SECONDS)) { + Assert.fail("objectDestruction did not happen"); + } + } + } + + public static class HeavyObject { + + } +}