Merge pull request #578 from gustavomzw/nodepool2

base implementation of nodepool (work in progress)
This commit is contained in:
Adrian Cole 2012-04-19 19:16:35 -07:00
commit 3672e7c762
4 changed files with 670 additions and 0 deletions

93
labs/nodepool/pom.xml Normal file
View File

@ -0,0 +1,93 @@
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>jclouds-project</artifactId>
<groupId>org.jclouds</groupId>
<version>1.5.0-SNAPSHOT</version>
<relativePath>../../project/pom.xml</relativePath>
</parent>
<groupId>org.jclouds.labs</groupId>
<artifactId>nodepool</artifactId>
<name>jclouds nodepool</name>
<packaging>bundle</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jsch.version>0.1.46</jsch.version>
<test.aws-ec2.identity>${test.aws.identity}</test.aws-ec2.identity>
<test.aws-ec2.credential>${test.aws.credential}</test.aws-ec2.credential>
</properties>
<dependencies>
<dependency>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-compute</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-core</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-compute</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jclouds.provider</groupId>
<artifactId>aws-ec2</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jclouds.driver</groupId>
<artifactId>jclouds-enterprise</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jclouds.driver</groupId>
<artifactId>jclouds-sshj</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-scriptbuilder</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jclouds.driver</groupId>
<artifactId>jclouds-jsch</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Export-Package>org.jclouds.nodepool*;version="${project.version}"</Export-Package>
<Import-Package>org.jclouds*;version="${project.version}",*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,28 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.jclouds.nodepool;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.RunNodesException;
public interface PooledComputeService extends ComputeService {
void startPool() throws RunNodesException;
}

View File

@ -0,0 +1,265 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.jclouds.nodepool.internal;
import java.util.Map;
import java.util.Set;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.RunNodesException;
import org.jclouds.compute.RunScriptOnNodesException;
import org.jclouds.compute.domain.ComputeMetadata;
import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeMetadataBuilder;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.domain.TemplateBuilder;
import org.jclouds.compute.options.RunScriptOptions;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.compute.predicates.NodePredicates;
import org.jclouds.domain.Location;
import org.jclouds.nodepool.PooledComputeService;
import org.jclouds.scriptbuilder.domain.Statement;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ListenableFuture;
public class BasePooledComputeService implements PooledComputeService {
private final ComputeService backingComputeService;
private final String backingGroup;
private final Template backingTemplate;
private final int minPoolSize;
private Map<NodeMetadata, String> groupMapping;
public BasePooledComputeService(ComputeService backingComputeService, String backingGroup, Template backingTemplate, int minPoolSize) {
this.backingComputeService = backingComputeService;
this.backingGroup = backingGroup;
this.backingTemplate = backingTemplate;
this.minPoolSize = minPoolSize;
}
@Override
public void startPool() throws RunNodesException {
Set<? extends NodeMetadata> backingNodes =
backingComputeService.createNodesInGroup(backingGroup, minPoolSize, backingTemplate);
groupMapping = Maps.newHashMap();
for (NodeMetadata node : backingNodes) {
groupMapping.put(node, "unassigned");
}
}
@Override
public ComputeServiceContext getContext() {
return backingComputeService.getContext();
}
@Override
public TemplateBuilder templateBuilder() {
return backingComputeService.templateBuilder();
}
@Override
public TemplateOptions templateOptions() {
return backingComputeService.templateOptions();
}
@Override
public Set<? extends Hardware> listHardwareProfiles() {
return ImmutableSet.<Hardware>of(backingTemplate.getHardware());
}
@Override
public Set<? extends Image> listImages() {
return ImmutableSet.<Image>of(backingTemplate.getImage());
}
@Override
public Set<? extends ComputeMetadata> listNodes() {
Set<NodeMetadata> allocatedNodes = Sets.newLinkedHashSet();
for (ComputeMetadata node : backingComputeService.listNodes()) {
NodeMetadata metadata = backingComputeService.getNodeMetadata(node.getId());
String group = groupMapping.get(node);
if ("unassigned".equals(group))
continue;
NodeMetadata nodeWithUpdatedGroup =
NodeMetadataBuilder.fromNodeMetadata(metadata).group(group).build();
allocatedNodes.add(nodeWithUpdatedGroup);
}
return allocatedNodes;
}
@Override
public Set<? extends Location> listAssignableLocations() {
return ImmutableSet.<Location>of(backingTemplate.getLocation());
}
@Override
public Set<? extends NodeMetadata> createNodesInGroup(String group,
int count, Template template) throws RunNodesException {
throw new RuntimeException("not implemented");
}
@Override
public Set<? extends NodeMetadata> createNodesInGroup(String group,
int count, TemplateOptions templateOptions)
throws RunNodesException {
throw new RuntimeException("not implemented");
}
@Override
public Set<? extends NodeMetadata> createNodesInGroup(String group,
int count) throws RunNodesException {
int allocatedCount = 0;
Set<NodeMetadata> allocatedNodes = Sets.newLinkedHashSet();
for (NodeMetadata metadata : groupMapping.keySet()) {
if (groupMapping.get(metadata).equals("unassigned")) {
groupMapping.put(metadata, "group");
NodeMetadata nodeWithUpdatedGroup =
NodeMetadataBuilder.fromNodeMetadata(metadata).group(group).build();
allocatedNodes.add(nodeWithUpdatedGroup);
allocatedCount += 1;
if (allocatedCount == count) break;
}
}
return allocatedNodes;
}
@Override
public void resumeNode(String id) {
backingComputeService.resumeNode(id);
}
@Override
public void resumeNodesMatching(Predicate<NodeMetadata> filter) {
backingComputeService.resumeNodesMatching(filter);
}
@Override
public void suspendNode(String id) {
backingComputeService.suspendNode(id);
}
@Override
public void suspendNodesMatching(Predicate<NodeMetadata> filter) {
backingComputeService.suspendNodesMatching(filter);
}
@Override
public void destroyNode(String id) {
backingComputeService.destroyNode(id);
}
@Override
public Set<? extends NodeMetadata> destroyNodesMatching(
Predicate<NodeMetadata> filter) {
return backingComputeService.destroyNodesMatching(filter);
}
@Override
public void rebootNode(String id) {
backingComputeService.rebootNode(id);
}
@Override
public void rebootNodesMatching(Predicate<NodeMetadata> filter) {
backingComputeService.rebootNodesMatching(filter);
}
@Override
public NodeMetadata getNodeMetadata(String id) {
return backingComputeService.getNodeMetadata(id);
}
@Override
public Set<? extends NodeMetadata> listNodesDetailsMatching(
Predicate<ComputeMetadata> filter) {
return backingComputeService.listNodesDetailsMatching(filter);
}
@Override
public Map<? extends NodeMetadata, ExecResponse> runScriptOnNodesMatching(
Predicate<NodeMetadata> filter, String runScript)
throws RunScriptOnNodesException {
// TODO Auto-generated method stub
return backingComputeService.runScriptOnNodesMatching(filter, runScript);
}
@Override
public Map<? extends NodeMetadata, ExecResponse> runScriptOnNodesMatching(
Predicate<NodeMetadata> filter, Statement runScript)
throws RunScriptOnNodesException {
return backingComputeService.runScriptOnNodesMatching(filter, runScript);
}
@Override
public Map<? extends NodeMetadata, ExecResponse> runScriptOnNodesMatching(
Predicate<NodeMetadata> filter, String runScript,
RunScriptOptions options) throws RunScriptOnNodesException {
return backingComputeService.runScriptOnNodesMatching(filter, runScript, options);
}
@Override
public Map<? extends NodeMetadata, ExecResponse> runScriptOnNodesMatching(
Predicate<NodeMetadata> filter, Statement runScript,
RunScriptOptions options) throws RunScriptOnNodesException {
return backingComputeService.runScriptOnNodesMatching(filter, runScript, options);
}
@Override
public ExecResponse runScriptOnNode(String id, Statement runScript,
RunScriptOptions options) {
return backingComputeService.runScriptOnNode(id, runScript, options);
}
@Override
public ListenableFuture<ExecResponse> submitScriptOnNode(String id,
Statement runScript, RunScriptOptions options) {
return backingComputeService.submitScriptOnNode(id, runScript, options);
}
@Override
public ExecResponse runScriptOnNode(String id, Statement runScript) {
return backingComputeService.runScriptOnNode(id, runScript);
}
@Override
public ExecResponse runScriptOnNode(String id, String runScript,
RunScriptOptions options) {
return backingComputeService.runScriptOnNode(id, runScript, options);
}
@Override
public ExecResponse runScriptOnNode(String id, String runScript) {
return backingComputeService.runScriptOnNode(id, runScript);
}
}

View File

@ -0,0 +1,284 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.jclouds.nodepool;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Iterables.getOnlyElement;
import static org.jclouds.scriptbuilder.domain.Statements.newStatementList;
import java.io.File;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;
import junit.framework.TestCase;
import org.jclouds.Constants;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.ComputeServiceContextFactory;
import org.jclouds.compute.RunNodesException;
import org.jclouds.compute.domain.ComputeMetadata;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeState;
import org.jclouds.compute.domain.OsFamily;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.domain.TemplateBuilder;
import org.jclouds.enterprise.config.EnterpriseConfigurationModule;
import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
import org.jclouds.nodepool.internal.BasePooledComputeService;
import org.jclouds.scriptbuilder.domain.Statement;
import org.jclouds.scriptbuilder.domain.Statements;
import org.jclouds.scriptbuilder.statements.java.InstallJDK;
import org.jclouds.scriptbuilder.statements.login.AdminAccess;
import org.jclouds.sshj.config.SshjSshClientModule;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Module;
/**
* Unit test for simple App.
*/
public class AppTest extends TestCase {
private String identity;
private String credential;
private String providerName;
private File privateKey;
private File publicKey;
private String endPointUrl;
public String profile;
private int retentionTime;
public int instanceCap;
private String imageId;
private String osFamily;
private String osVersion;
private String hardwareId;
private int ram;
private int cores;
private String initScript;
private String name;
private boolean stopOnTerminate;
private ComputeService compute;
private PooledComputeService pooledCompute;
private Collection<NodeMetadata> nodes = new LinkedList<NodeMetadata>();
private static Logger LOGGER = Logger.getLogger("AppTest");
private long time;
@Override
protected void setUp() throws Exception {
// setup JCloudsCloud
identity = "insert-your-identity-here";
credential = "insert-your-credential-here";
providerName = "aws-ec2";
privateKey = new File("private-key");
publicKey = new File("public-key");
endPointUrl = "";
profile = "aws-slave-profile";
retentionTime = -1;
instanceCap = 3;
// cloud instance template
name = "aws-jenkins-slave";
// numExecutors = 1;
// description = ""
imageId = "us-east-1/ami-4dad7424";
osFamily = "";
osVersion = "";
hardwareId = "t1.micro";
ram = -1;
cores = -1;
// labels = "whii";
initScript = "touch /tmp/hellothere";
stopOnTerminate = true;
}
/**
* Rigourous Test :-)
*/
public void testApp() {
createCompute();
assertNotNull(compute);
createAndStartPool();
assertNotNull(pooledCompute);
for (int i = 0; i < 3; i++) {
startCounter();
provision("pool-1");
stopCounter();
}
for (int i = 0; i < 3; i++) {
startCounter();
provision("pool-2");
stopCounter();
}
for (int i = 0; i < 3; i++) {
startCounter();
provision("pool-3");
stopCounter();
}
assertEquals(9, getRunningNodesCount());
for (NodeMetadata slave : nodes) {
assertNotNull(slave);
LOGGER.info(slave.getId() + "-" + slave.getGroup());
terminate(slave);
}
assertEquals(0, getRunningNodesCount());
}
private void stopCounter() {
LOGGER.info("Elapsed time: " + (System.currentTimeMillis() - time));
}
private void startCounter() {
time = System.currentTimeMillis();
}
public AppTest getCloud() {
return this;
}
public ComputeService getCompute() {
return pooledCompute;
}
public NodeMetadata provision(String groupName) {
LOGGER.info("Provisioning new node");
NodeMetadata nodeMetadata = createNodeWithJdk(groupName);
nodes.add(nodeMetadata);
return nodeMetadata;
}
public int getRunningNodesCount() {
int nodeCount = 0;
for (ComputeMetadata cm : pooledCompute.listNodes()) {
if (NodeMetadata.class.isInstance(cm)) {
String nodeGroup = ((NodeMetadata) cm).getGroup();
if (!((NodeMetadata) cm).getState().equals(NodeState.SUSPENDED)
&& !((NodeMetadata) cm).getState().equals(NodeState.TERMINATED)) {
nodeCount++;
}
}
}
return nodeCount;
}
private void createCompute() {
Properties overrides = new Properties();
if (!Strings.isNullOrEmpty(this.endPointUrl)) {
overrides.setProperty(Constants.PROPERTY_ENDPOINT, this.endPointUrl);
}
Iterable<Module> modules = ImmutableSet.<Module> of(new SshjSshClientModule(), new SLF4JLoggingModule(),
new EnterpriseConfigurationModule());
this.compute = new ComputeServiceContextFactory().createContext(this.providerName, this.identity,
this.credential, modules, overrides).getComputeService();
}
private void createAndStartPool() {
LOGGER.info("creating jclouds nodepool");
ImmutableMap<String, String> userMetadata = ImmutableMap.of("Name", name);
TemplateBuilder templateBuilder = compute.templateBuilder();
if (!Strings.isNullOrEmpty(imageId)) {
LOGGER.info("Setting image id to " + imageId);
templateBuilder.imageId(imageId);
} else {
if (!Strings.isNullOrEmpty(osFamily)) {
LOGGER.info("Setting osFamily to " + osFamily);
templateBuilder.osFamily(OsFamily.valueOf(osFamily));
}
if (!Strings.isNullOrEmpty(osVersion)) {
LOGGER.info("Setting osVersion to " + osVersion);
templateBuilder.osVersionMatches(osVersion);
}
}
if (!Strings.isNullOrEmpty((hardwareId))) {
LOGGER.info("Setting hardware Id to " + hardwareId);
} else {
LOGGER.info("Setting minRam " + ram + " and minCores " + cores);
templateBuilder.minCores(cores).minRam(ram);
}
Template template = templateBuilder.build();
// setup the jcloudTemplate to customize the nodeMetadata with jdk, etc.
// also opening ports
AdminAccess adminAccess = AdminAccess.builder().adminUsername("jenkins").installAdminPrivateKey(false) // no
// need
.grantSudoToAdminUser(false) // no need
.adminPrivateKey(getCloud().privateKey) // temporary due to jclouds
// bug
.authorizeAdminPublicKey(true).adminPublicKey(getCloud().publicKey).build();
// Jenkins needs /jenkins dir.
Statement jenkinsDirStatement = Statements.newStatementList(Statements.exec("mkdir /jenkins"),
Statements.exec("chown jenkins /jenkins"));
Statement bootstrap = newStatementList(adminAccess, jenkinsDirStatement, Statements.exec(this.initScript),
InstallJDK.fromOpenJDK());
template.getOptions().inboundPorts(22).userMetadata(userMetadata).runScript(bootstrap);
pooledCompute = new BasePooledComputeService(compute, "jenkins-pool", template, 10);
try {
pooledCompute.startPool();
} catch (RunNodesException e) {
destroyBadNodesAndPropagate(e);
}
}
private NodeMetadata createNodeWithJdk(String groupName) {
LOGGER.info("creating jclouds node");
NodeMetadata nodeMetadata = null;
try {
nodeMetadata = getOnlyElement(pooledCompute.createNodesInGroup(groupName, 1));
} catch (RunNodesException e) {
throw destroyBadNodesAndPropagate(e);
}
// Check if nodeMetadata is null and throw
return nodeMetadata;
}
private RuntimeException destroyBadNodesAndPropagate(RunNodesException e) {
for (Map.Entry<? extends NodeMetadata, ? extends Throwable> nodeError : e.getNodeErrors().entrySet())
getCloud().getCompute().destroyNode(nodeError.getKey().getId());
throw propagate(e);
}
public void terminate(NodeMetadata nodeMetaData) {
if (stopOnTerminate) {
LOGGER.info("Suspending the Slave : " + nodeMetaData.getName());
final ComputeService compute = getCloud().getCompute();
compute.suspendNode(nodeMetaData.getId());
} else {
LOGGER.info("Terminating the Slave : " + nodeMetaData.getName());
final ComputeService compute = getCloud().getCompute();
compute.destroyNode(nodeMetaData.getId());
}
}
}