You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@geode.apache.org by GitBox <gi...@apache.org> on 2018/12/14 19:42:14 UTC

[GitHub] smgoller closed pull request #27: Add support for running benchmarks in AWS

smgoller closed pull request #27: Add support for running benchmarks in AWS
URL: https://github.com/apache/geode-benchmarks/pull/27
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/README.md b/README.md
index 258c982..c930739 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,11 @@ For example:
 This project includes some scripts to automate running benchmarks in google cloud. See the 
 [README.md](infrastructure/google_cloud/README.md) in the infrastructure/google_cloud directory.
 
+### Running in aws
+
+This project includes some scripts to automate running benchmarks in AWS. See the 
+[README.md](infrastructure/aws/README.md) in the infrastructure/aws directory.
+
 ## Project structure
 
 The project is divided into two modules
diff --git a/build.gradle b/build.gradle
index 8e1b288..87522c0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -21,6 +21,7 @@ buildscript {
     dependencies {
         classpath 'com.bmuschko:gradle-docker-plugin:4.0.4'
         classpath "com.diffplug.spotless:spotless-plugin-gradle:3.16.0"
+        classpath "io.spring.gradle:dependency-management-plugin:1.0.3.RELEASE"
     }
 }
 
diff --git a/gradle/dependency-versions.properties b/gradle/dependency-versions.properties
index 19d53eb..017d40b 100644
--- a/gradle/dependency-versions.properties
+++ b/gradle/dependency-versions.properties
@@ -27,3 +27,4 @@ mockito-all.version = 1.10.19
 awaitility.version = 3.0.0
 sshd-core.version = 2.1.0
 assertj-core.version = 3.11.1
+software-amazon-awssdk.version = 2.1.4
diff --git a/gradle/rat.gradle b/gradle/rat.gradle
index df0b54a..89fb7d9 100644
--- a/gradle/rat.gradle
+++ b/gradle/rat.gradle
@@ -69,6 +69,7 @@ rat {
             '**/*.rej',
             '**/*.orig',
             '**/*.MF',
+            '**/*.cfg',
 
     ]
 }
diff --git a/infrastructure/build.gradle b/infrastructure/build.gradle
new file mode 100644
index 0000000..bf66802
--- /dev/null
+++ b/infrastructure/build.gradle
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+plugins {
+    id 'java'
+}
+
+version '1.0-SNAPSHOT'
+
+sourceCompatibility = 1.8
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    implementation 'software.amazon.awssdk:ec2'
+    implementation(group: 'com.hierynomus', name: 'sshj', version: project.'sshj.version')
+    runtime(group: 'org.slf4j', name: 'slf4j-simple', version: project.'slf4j-simple.version')
+}
+
+apply plugin: "io.spring.dependency-management"
+
+dependencyManagement {
+    imports {
+        mavenBom 'software.amazon.awssdk:bom:' + project.'software-amazon-awssdk.version'
+    }
+}
+
+task(launchCluster, dependsOn: 'classes', type: JavaExec) {
+    main = 'org.apache.geode.infrastructure.aws.LaunchCluster'
+    workingDir = rootDir
+    classpath = sourceSets.main.runtimeClasspath
+}
+
+task(destroyCluster, dependsOn: 'classes', type: JavaExec) {
+    main = 'org.apache.geode.infrastructure.aws.DestroyCluster'
+    workingDir = rootDir
+    classpath = sourceSets.main.runtimeClasspath
+}
diff --git a/infrastructure/scripts/aws/README.md b/infrastructure/scripts/aws/README.md
new file mode 100644
index 0000000..f3bcb77
--- /dev/null
+++ b/infrastructure/scripts/aws/README.md
@@ -0,0 +1,44 @@
+# Benchmark Utilities for AWS
+
+These utilities create instances and run tests in your AWS account
+
+# Prerequisites
+* You must have the aws cli installed.
+* You must also set your secret key for the CLI. See the [Amazon's instructions](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html)
+* To build the image, you must have packer installed
+
+# Image
+
+Before using the scripts below, build the image in the image directory using packer.
+
+# launch_cluster.sh
+`launch_cluster.sh` creates an instance group in AWS based on an image created.
+
+It takes two arguments. First, a tag to identify the cluster for use with other utilities. The
+second argument is the number of instances to create. 
+
+# run_tests.sh
+Runs benchmark tests against a single branch of `geode`. Arguments are (in order)
+
+* tag (the same as the cluster launched via `launch_cluster.sh`)
+* branch of geode (must exist in the apache geode repository)
+* output directory for results
+* branch of benchmark code to use (must exist in the apache geode-benchmarks repository)
+
+# run_against_baseline.sh
+Runs benchmark tests against two branches of geode for comparison purposes. Arguments are (in order)
+
+* tag (the same as the cluster launched via `launch_cluster.sh`)
+* branch of geode (must exist in the apache geode repository)
+* branch of benchmark code to use (must exist in the apache geode-benchmarks repository)
+
+
+# destroy_cluster.sh
+Destroys a cluster that you created. Arguments are the tag that you passed to launch_cluster.sh
+
+#Example
+```bash
+./launch_cluster.sh mycluster 4
+./run-tests mycluster
+./destroy_cluster.sh
+```
diff --git a/infrastructure/scripts/aws/destroy_cluster.sh b/infrastructure/scripts/aws/destroy_cluster.sh
new file mode 100755
index 0000000..82d98c6
--- /dev/null
+++ b/infrastructure/scripts/aws/destroy_cluster.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+
+TAG=${1}
+
+pushd ../../../
+./gradlew destroyCluster --args "${TAG}"
+popd
diff --git a/infrastructure/scripts/aws/image/files/defaults.cfg b/infrastructure/scripts/aws/image/files/defaults.cfg
new file mode 100644
index 0000000..9969787
--- /dev/null
+++ b/infrastructure/scripts/aws/image/files/defaults.cfg
@@ -0,0 +1,4 @@
+#cloud-config
+system_info:
+  default_user:
+    name: geode
diff --git a/infrastructure/scripts/aws/image/packer.json b/infrastructure/scripts/aws/image/packer.json
new file mode 100644
index 0000000..948d57e
--- /dev/null
+++ b/infrastructure/scripts/aws/image/packer.json
@@ -0,0 +1,54 @@
+{
+  "variables": {
+    "aws_access_key": "",
+    "aws_secret_key": ""
+  },
+  "builders": [
+    {
+      "type": "amazon-ebs",
+      "name": "geode-benchmarks",
+      "region": "us-west-2",
+      "source_ami_filter": {
+        "filters": {
+          "virtualization-type": "hvm",
+          "name": "ubuntu/images/*ubuntu-bionic-18.04-amd64-server-*",
+          "root-device-type": "ebs"
+        },
+        "owners": ["099720109477"],
+        "most_recent": true
+      },
+      "instance_type": "t2.small",
+      "ssh_username": "geode",
+      "ami_name": "geode-benchmarks-{{timestamp}}",
+      "communicator": "ssh",
+      "ssh_pty": "true",
+      "ami_virtualization_type": "hvm",
+      "user_data_file": "./files/defaults.cfg",
+      "tags": {
+        "OS_Version": "Ubuntu",
+        "Release": "Latest",
+        "Base_AMI_Name": "{{ .SourceAMIName }}",
+        "Extra": "{{ .SourceAMITags.TagName }}",
+        "purpose": "geode-benchmarks"
+      }
+    }
+  ],
+  "provisioners": [
+    {
+      "type": "file",
+      "source": "./files/defaults.cfg",
+      "destination": "/tmp/defaults.cfg"
+    },
+    {
+      "type": "shell",
+      "inline": [
+        "sudo apt update",
+        "sudo apt upgrade -y",
+        "sudo apt install -y openjdk-8-jdk unzip dstat",
+        "sudo update-java-alternatives -s java-1.8.0-openjdk-amd64",
+        "sudo mv /tmp/defaults.cfg /etc/cloud/cloud.cfg.d/defaults.cfg",
+        "sudo sh -c \"echo 'StrictHostKeyChecking no' >> /etc/ssh/ssh_config\""
+      ]
+    }
+  ]
+}
diff --git a/infrastructure/scripts/aws/image/scripts/add-user.sh b/infrastructure/scripts/aws/image/scripts/add-user.sh
new file mode 100644
index 0000000..bd031ba
--- /dev/null
+++ b/infrastructure/scripts/aws/image/scripts/add-user.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+
+set -x -e
+
+echo "**** PATH ****"
+printenv PATH
+echo "**** ****"
+export PATH=$PATH:/usr/sbin
+sudo mkdir -p ~geode/.ssh
+sudo chown -R geode:geode ~geode/.ssh
+
+
diff --git a/infrastructure/scripts/aws/launch_cluster.sh b/infrastructure/scripts/aws/launch_cluster.sh
new file mode 100755
index 0000000..11eb852
--- /dev/null
+++ b/infrastructure/scripts/aws/launch_cluster.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+
+set -e
+
+TAG=${1}
+COUNT=${2}
+
+pushd ../../../
+./gradlew launchCluster --args "${TAG} ${COUNT}"
+popd
diff --git a/infrastructure/google_cloud/run_against_baseline.sh b/infrastructure/scripts/aws/run_against_baseline.sh
similarity index 100%
rename from infrastructure/google_cloud/run_against_baseline.sh
rename to infrastructure/scripts/aws/run_against_baseline.sh
diff --git a/infrastructure/scripts/aws/run_tests.sh b/infrastructure/scripts/aws/run_tests.sh
new file mode 100755
index 0000000..e5bb5c5
--- /dev/null
+++ b/infrastructure/scripts/aws/run_tests.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+DATE=$(date '+%m-%d-%Y-%H-%M-%S')
+
+TAG=${1}
+BRANCH=${2:-develop}
+OUTPUT=${3:-output-${DATE}-${TAG}}
+BENCHMARK_BRANCH=${4:-develop}
+PREFIX="geode-performance-${TAG}"
+
+SSH_OPTIONS="-i ~/.ssh/geode-benchmarks/${TAG}.pem"
+HOSTS=`aws ec2 describe-instances --query 'Reservations[*].Instances[*].PrivateIpAddress' --filter "Name=tag:geode-benchmarks,Values=${TAG}" --output text`
+HOSTS=$(echo ${HOSTS} | tr ' ' ',')
+FIRST_INSTANCE=`aws ec2 describe-instances --query 'Reservations[*].Instances[*].PublicIpAddress' --filter "Name=tag:geode-benchmarks,Values=${TAG}" --output text | cut -f 1`
+
+echo "FIRST_INSTANCE=${FIRST_INSTANCE}"
+echo "HOSTS=${HOSTS}"
+
+ssh ${SSH_OPTIONS} geode@$FIRST_INSTANCE "\
+  rm -rf geode-benchmarks geode && \
+  git clone --depth=1 https://github.com/apache/geode --branch ${BRANCH} geode && \
+  git clone https://github.com/apache/geode-benchmarks --branch ${BENCHMARK_BRANCH} && \
+  cd geode-benchmarks && \
+  ./gradlew --include-build ../geode benchmark -Phosts=${HOSTS}"
+
+
+mkdir -p ${OUTPUT}
+
+scp ${SSH_OPTIONS} -r geode@${FIRST_INSTANCE}:geode-benchmarks/geode-benchmarks/build/reports ${OUTPUT}/reports
+
+scp ${SSH_OPTIONS} -r geode@${FIRST_INSTANCE}:geode-benchmarks/geode-benchmarks/build/benchmarks ${OUTPUT}
diff --git a/infrastructure/google_cloud/README.md b/infrastructure/scripts/google_cloud/README.md
similarity index 100%
rename from infrastructure/google_cloud/README.md
rename to infrastructure/scripts/google_cloud/README.md
diff --git a/infrastructure/google_cloud/build_image.sh b/infrastructure/scripts/google_cloud/build_image.sh
similarity index 100%
rename from infrastructure/google_cloud/build_image.sh
rename to infrastructure/scripts/google_cloud/build_image.sh
diff --git a/infrastructure/google_cloud/destroy_cluster.sh b/infrastructure/scripts/google_cloud/destroy_cluster.sh
similarity index 100%
rename from infrastructure/google_cloud/destroy_cluster.sh
rename to infrastructure/scripts/google_cloud/destroy_cluster.sh
diff --git a/infrastructure/google_cloud/launch_cluster.sh b/infrastructure/scripts/google_cloud/launch_cluster.sh
similarity index 100%
rename from infrastructure/google_cloud/launch_cluster.sh
rename to infrastructure/scripts/google_cloud/launch_cluster.sh
diff --git a/infrastructure/scripts/google_cloud/run_against_baseline.sh b/infrastructure/scripts/google_cloud/run_against_baseline.sh
new file mode 100755
index 0000000..63063cb
--- /dev/null
+++ b/infrastructure/scripts/google_cloud/run_against_baseline.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+
+DATE=$(date '+%m-%d-%Y-%H-%M-%S')
+TAG=${1}
+BRANCH=${2:-develop}
+BASELINE=${3:-"rel/v1.7.0"}
+BENCHMARK_BRANCH=${4:-develop}
+OUTPUT=output-${DATE}-${TAG}
+
+./run_tests.sh ${TAG} ${BRANCH} ${OUTPUT}/branch ${BENCHMARK_BRANCH}
+./run_tests.sh ${TAG} ${BASELINE} ${OUTPUT}/baseline ${BENCHMARK_BRANCH}
diff --git a/infrastructure/google_cloud/run_tests.sh b/infrastructure/scripts/google_cloud/run_tests.sh
similarity index 100%
rename from infrastructure/google_cloud/run_tests.sh
rename to infrastructure/scripts/google_cloud/run_tests.sh
diff --git a/infrastructure/src/main/java/org/apache/geode/infrastructure/BenchmarkMetadata.java b/infrastructure/src/main/java/org/apache/geode/infrastructure/BenchmarkMetadata.java
new file mode 100644
index 0000000..dc920f5
--- /dev/null
+++ b/infrastructure/src/main/java/org/apache/geode/infrastructure/BenchmarkMetadata.java
@@ -0,0 +1,41 @@
+/*
+ * 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.geode.infrastructure;
+
+/**
+ * Static methods to generate common strings used by the infrastructure.
+ */
+public class BenchmarkMetadata {
+  public static String PREFIX = "geode-benchmarks";
+  public static String SSH_DIRECTORY = ".ssh/geode-benchmarks";
+
+  public static String benchmarkPrefix(String tag) {
+    return PREFIX + "-" + tag;
+  }
+
+  public static String benchmarkString(String tag, String suffix) {
+    return benchmarkPrefix(tag) + "-" + suffix;
+  }
+
+  public static String benchmarkKeyFileDirectory() {
+    return System.getProperty("user.home") + "/" + SSH_DIRECTORY;
+  }
+
+  public static String benchmarkKeyFileName(String tag) {
+    return benchmarkKeyFileDirectory() + "/" + tag + ".pem";
+  }
+}
diff --git a/infrastructure/src/main/java/org/apache/geode/infrastructure/aws/AwsBenchmarkMetadata.java b/infrastructure/src/main/java/org/apache/geode/infrastructure/aws/AwsBenchmarkMetadata.java
new file mode 100644
index 0000000..90fb757
--- /dev/null
+++ b/infrastructure/src/main/java/org/apache/geode/infrastructure/aws/AwsBenchmarkMetadata.java
@@ -0,0 +1,65 @@
+/*
+ * 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.geode.infrastructure.aws;
+
+import software.amazon.awssdk.services.ec2.model.InstanceType;
+import software.amazon.awssdk.services.ec2.model.PlacementStrategy;
+import software.amazon.awssdk.services.ec2.model.Tenancy;
+
+import org.apache.geode.infrastructure.BenchmarkMetadata;
+
+/**
+ * Static methods to generate common strings used for AWS infrastructure.
+ */
+class AwsBenchmarkMetadata extends BenchmarkMetadata {
+  public static final String USER = "geode";
+  public static final int POLL_INTERVAL = 15000;
+  public static InstanceType INSTANCE_TYPE = InstanceType.C5_9_XLARGE;
+  public static Tenancy TENANCY = Tenancy.DEDICATED;
+
+  public static String securityGroup(String tag) {
+    return BenchmarkMetadata.benchmarkString(tag, "securityGroup");
+  }
+
+  public static String placementGroup(String tag) {
+    return BenchmarkMetadata.benchmarkString(tag, "placementGroup");
+  }
+
+  public static String launchTemplate(String tag) {
+    return BenchmarkMetadata.benchmarkString(tag, "launchTemplate");
+  }
+
+  public static String keyPair(String tag) {
+    return BenchmarkMetadata.benchmarkString(tag, "keyPair");
+  }
+
+  public static String keyPairFileName(String tag) {
+    return BenchmarkMetadata.benchmarkKeyFileName(tag);
+  }
+
+  public static InstanceType instanceType() {
+    return INSTANCE_TYPE;
+  }
+
+  public static Tenancy tenancy() {
+    return TENANCY;
+  }
+
+  public static PlacementStrategy placementGroupStrategy() {
+    return PlacementStrategy.CLUSTER;
+  }
+}
diff --git a/infrastructure/src/main/java/org/apache/geode/infrastructure/aws/DestroyCluster.java b/infrastructure/src/main/java/org/apache/geode/infrastructure/aws/DestroyCluster.java
new file mode 100644
index 0000000..5018aab
--- /dev/null
+++ b/infrastructure/src/main/java/org/apache/geode/infrastructure/aws/DestroyCluster.java
@@ -0,0 +1,157 @@
+/*
+ * 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.geode.infrastructure.aws;
+
+import static java.lang.Thread.sleep;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import software.amazon.awssdk.services.ec2.Ec2Client;
+import software.amazon.awssdk.services.ec2.model.DeleteKeyPairRequest;
+import software.amazon.awssdk.services.ec2.model.DeleteLaunchTemplateRequest;
+import software.amazon.awssdk.services.ec2.model.DeletePlacementGroupRequest;
+import software.amazon.awssdk.services.ec2.model.DeleteSecurityGroupRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse;
+import software.amazon.awssdk.services.ec2.model.Filter;
+import software.amazon.awssdk.services.ec2.model.Instance;
+import software.amazon.awssdk.services.ec2.model.TerminateInstancesRequest;
+
+import org.apache.geode.infrastructure.BenchmarkMetadata;
+
+public class DestroyCluster {
+  private static Ec2Client ec2 = Ec2Client.create();
+
+  public static void main(String[] args) throws InterruptedException {
+    if (args.length != 1) {
+      throw new IllegalStateException("This takes one argument, the cluster tag to work with.");
+    }
+    String benchmarkTag = args[0];
+
+    if (benchmarkTag == null || benchmarkTag.isEmpty()) {
+      throw new IllegalStateException("No valid tag found.");
+    }
+
+    deleteInstances(benchmarkTag);
+    deleteLaunchTemplate(benchmarkTag);
+    deleteSecurityGroup(benchmarkTag);
+    deletePlacementGroup(benchmarkTag);
+    deleteKeyPair(benchmarkTag);
+  }
+
+  private static void deleteKeyPair(String benchmarkTag) {
+    try {
+      ec2.deleteKeyPair(
+          DeleteKeyPairRequest.builder().keyName(AwsBenchmarkMetadata.keyPair(benchmarkTag))
+              .build());
+      Files.deleteIfExists(Paths.get(AwsBenchmarkMetadata.keyPairFileName(benchmarkTag)));
+      System.out.println("Key Pair for cluster'" + benchmarkTag + "' deleted.");
+    } catch (Exception e) {
+      System.out.println("We got an exception while deleting the Key pair");
+      System.out.println("Exception message: " + e);
+    }
+  }
+
+  private static void deleteInstances(String benchmarkTag) throws InterruptedException {
+    // delete instances
+    try {
+      DescribeInstancesResponse dir = ec2.describeInstances(DescribeInstancesRequest.builder()
+          .filters(
+              Filter.builder().name("tag:" + BenchmarkMetadata.PREFIX).values(benchmarkTag).build())
+          .build());
+      Stream<Instance> instanceStream = dir.reservations()
+          .stream()
+          .flatMap(reservation -> reservation.instances().stream());
+
+      List<String> instanceIds = dir
+          .reservations()
+          .stream()
+          .flatMap(reservation -> reservation
+              .instances()
+              .stream())
+          .map(Instance::instanceId)
+          .collect(Collectors.toList());
+
+      ec2.terminateInstances(TerminateInstancesRequest.builder()
+          .instanceIds(instanceIds)
+          .build());
+      System.out.println("Waiting for cluster instances to terminate.");
+      while (ec2.describeInstances(DescribeInstancesRequest.builder()
+          .instanceIds(instanceIds)
+          .filters(Filter.builder()
+              .name("instance-state-name")
+              .values("pending", "running", "shutting-down", "stopping", "stopped")
+              .build())
+          .build()).reservations().stream().flatMap(reservation -> reservation.instances().stream())
+          .count() > 0) {
+        sleep(AwsBenchmarkMetadata.POLL_INTERVAL);
+        System.out.println("Continuing to wait.");
+      }
+
+      System.out.println("Instances for cluster '" + benchmarkTag + "' deleted.");
+    } catch (Exception e) {
+      System.out.println("We got an exception while deleting the instances");
+      System.out.println("Exception message: " + e);
+    }
+  }
+
+  private static void deletePlacementGroup(String benchmarkTag) {
+
+    try {
+      ec2.deletePlacementGroup(DeletePlacementGroupRequest.builder()
+          .groupName(AwsBenchmarkMetadata.placementGroup(benchmarkTag))
+          .build());
+
+      System.out.println("Placement Group for cluster '" + benchmarkTag + "' deleted.");
+    } catch (Exception e) {
+      System.out.println("We got an exception while deleting the placement group");
+      System.out.println("Exception message: " + e);
+    }
+  }
+
+  private static void deleteSecurityGroup(String benchmarkTag) {
+
+    try {
+      ec2.deleteSecurityGroup(DeleteSecurityGroupRequest.builder()
+          .groupName(AwsBenchmarkMetadata.securityGroup(benchmarkTag))
+          .build());
+
+      System.out.println("Security Group for cluster '" + benchmarkTag + "' deleted.");
+    } catch (Exception e) {
+      System.out.println("We got an exception while deleting the security group");
+      System.out.println("Exception message: " + e);
+    }
+  }
+
+  private static void deleteLaunchTemplate(String benchmarkTag) {
+
+    try {
+      ec2.deleteLaunchTemplate(DeleteLaunchTemplateRequest.builder()
+          .launchTemplateName(AwsBenchmarkMetadata.launchTemplate(benchmarkTag))
+          .build());
+
+      System.out.println("Launch template for cluster '" + benchmarkTag + "' deleted.");
+    } catch (Exception e) {
+      System.out.println("We got an exception while deleting the launch template");
+      System.out.println("Exception message: " + e);
+    }
+  }
+}
diff --git a/infrastructure/src/main/java/org/apache/geode/infrastructure/aws/KeyInstaller.java b/infrastructure/src/main/java/org/apache/geode/infrastructure/aws/KeyInstaller.java
new file mode 100644
index 0000000..d7b9362
--- /dev/null
+++ b/infrastructure/src/main/java/org/apache/geode/infrastructure/aws/KeyInstaller.java
@@ -0,0 +1,87 @@
+/*
+ * 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.geode.infrastructure.aws;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.ConnectException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+
+import net.schmizz.sshj.Config;
+import net.schmizz.sshj.DefaultConfig;
+import net.schmizz.sshj.SSHClient;
+import net.schmizz.sshj.sftp.FileAttributes;
+import net.schmizz.sshj.sftp.SFTPClient;
+import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
+import net.schmizz.sshj.xfer.FilePermission;
+import net.schmizz.sshj.xfer.FileSystemFile;
+
+public class KeyInstaller {
+  public static final Config CONFIG = new DefaultConfig();
+  private static final int RETRIES = 30;
+  private final String user;
+  private final Path privateKey;
+
+  public KeyInstaller(String user, Path privateKey) {
+    this.user = user;
+    this.privateKey = privateKey;
+  }
+
+
+  public void installPrivateKey(Collection<String> hosts) {
+    hosts.forEach(this::installKey);
+  }
+
+  private void installKey(String host) {
+    try (SSHClient client = new SSHClient(CONFIG)) {
+      client.addHostKeyVerifier(new PromiscuousVerifier());
+      connect(host, client);
+      client.authPublickey(user, privateKey.toFile().getAbsolutePath());
+      SFTPClient sftpClient = client.newSFTPClient();
+      String dest = "/home/" + user + "/.ssh/id_rsa";
+      sftpClient.put(new FileSystemFile(privateKey.toFile()), dest);
+      sftpClient.setattr(dest, new FileAttributes.Builder()
+          .withPermissions(Collections.singleton(FilePermission.USR_R)).build());
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    } catch (InterruptedException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private void connect(String host, SSHClient client) throws IOException, InterruptedException {
+
+    int i = 0;
+    while (true) {
+      try {
+        i++;
+        client.connect(host);
+        return;
+      } catch (ConnectException e) {
+        if (i >= RETRIES) {
+          throw e;
+        }
+
+        System.out.println("Failed to connect, retrying...");
+        Thread.sleep(AwsBenchmarkMetadata.POLL_INTERVAL);
+      }
+    }
+  }
+}
diff --git a/infrastructure/src/main/java/org/apache/geode/infrastructure/aws/LaunchCluster.java b/infrastructure/src/main/java/org/apache/geode/infrastructure/aws/LaunchCluster.java
new file mode 100644
index 0000000..d523fec
--- /dev/null
+++ b/infrastructure/src/main/java/org/apache/geode/infrastructure/aws/LaunchCluster.java
@@ -0,0 +1,261 @@
+/*
+ * 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.geode.infrastructure.aws;
+
+import static java.lang.Thread.sleep;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import software.amazon.awssdk.services.ec2.Ec2Client;
+import software.amazon.awssdk.services.ec2.model.AuthorizeSecurityGroupIngressRequest;
+import software.amazon.awssdk.services.ec2.model.CreateKeyPairRequest;
+import software.amazon.awssdk.services.ec2.model.CreateKeyPairResponse;
+import software.amazon.awssdk.services.ec2.model.CreateLaunchTemplateRequest;
+import software.amazon.awssdk.services.ec2.model.CreateLaunchTemplateResponse;
+import software.amazon.awssdk.services.ec2.model.CreatePlacementGroupRequest;
+import software.amazon.awssdk.services.ec2.model.CreateSecurityGroupRequest;
+import software.amazon.awssdk.services.ec2.model.CreateSecurityGroupResponse;
+import software.amazon.awssdk.services.ec2.model.CreateTagsRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeImagesRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest;
+import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse;
+import software.amazon.awssdk.services.ec2.model.Filter;
+import software.amazon.awssdk.services.ec2.model.Image;
+import software.amazon.awssdk.services.ec2.model.Instance;
+import software.amazon.awssdk.services.ec2.model.LaunchTemplatePlacementRequest;
+import software.amazon.awssdk.services.ec2.model.LaunchTemplateSpecification;
+import software.amazon.awssdk.services.ec2.model.RequestLaunchTemplateData;
+import software.amazon.awssdk.services.ec2.model.ResourceType;
+import software.amazon.awssdk.services.ec2.model.RunInstancesRequest;
+import software.amazon.awssdk.services.ec2.model.RunInstancesResponse;
+import software.amazon.awssdk.services.ec2.model.Tag;
+import software.amazon.awssdk.services.ec2.model.TagSpecification;
+
+import org.apache.geode.infrastructure.BenchmarkMetadata;
+
+public class LaunchCluster {
+  static Ec2Client ec2 = Ec2Client.create();
+
+  public static void main(String[] args) throws IOException, InterruptedException {
+    if (args.length != 2) {
+      usage("Usage: LaunchCluster <tag> <count>");
+      return;
+    }
+    String benchmarkTag = args[0];
+    int count = Integer.parseInt(args[1]);
+
+    if (benchmarkTag == null || benchmarkTag.isEmpty()) {
+      usage("Usage: LaunchCluster <tag> <count>");
+    }
+
+    List<Tag> tags = getTags(benchmarkTag);
+    createKeyPair(benchmarkTag);
+    Image newestImage = getNewestImage();
+
+    createPlacementGroup(benchmarkTag);
+    createSecurityGroup(benchmarkTag, tags);
+    createLaunchTemplate(benchmarkTag, newestImage);
+
+    List<String> instanceIds = launchInstances(benchmarkTag, tags, count);
+    DescribeInstancesResponse instances = waitForInstances(instanceIds);
+
+    List<String> publicIps = installPrivateKey(benchmarkTag, instances);
+
+    System.out.println("Instances successfully launched! Public IPs: " + publicIps);
+  }
+
+  private static void usage(String s) {
+    throw new IllegalStateException(s);
+  }
+
+  private static List<String> launchInstances(String benchmarkTag, List<Tag> tags,
+      int instanceCount)
+      throws InterruptedException {
+    // launch instances
+
+    RunInstancesResponse rir = ec2.runInstances(RunInstancesRequest.builder()
+        .launchTemplate(LaunchTemplateSpecification.builder()
+            .launchTemplateName(AwsBenchmarkMetadata.launchTemplate(benchmarkTag))
+            .build())
+        .tagSpecifications(TagSpecification.builder()
+            .tags(tags)
+            .resourceType(ResourceType.INSTANCE)
+            .build())
+        .minCount(instanceCount)
+        .maxCount(instanceCount)
+        .build());
+
+    List<String> instanceIds = rir.instances()
+        .stream()
+        .map(Instance::instanceId)
+        .collect(Collectors.toList());
+
+    return instanceIds;
+  }
+
+  private static DescribeInstancesResponse waitForInstances(List<String> instanceIds)
+      throws InterruptedException {
+    System.out.println("Waiting for cluster instances to go fully online.");
+
+    DescribeInstancesResponse describeInstancesResponse = describeInstances(instanceIds, "running");
+    while (instanceCount(describeInstancesResponse) < instanceIds.size()) {
+      sleep(AwsBenchmarkMetadata.POLL_INTERVAL);
+      System.out.println("Continuing to wait.");
+      describeInstancesResponse = describeInstances(instanceIds, "running");
+    }
+
+    return describeInstancesResponse;
+  }
+
+  private static List<String> installPrivateKey(String benchmarkTag,
+      DescribeInstancesResponse describeInstancesResponse) {
+    List<String> publicIps =
+        describeInstancesResponse.reservations().stream()
+            .flatMap(reservation -> reservation.instances().stream())
+            .map(Instance::publicIpAddress).collect(Collectors.toList());
+
+    new KeyInstaller(AwsBenchmarkMetadata.USER,
+        Paths.get(AwsBenchmarkMetadata.keyPairFileName(benchmarkTag))).installPrivateKey(publicIps);
+
+    System.out.println("Private key installed on all instances for passwordless ssh");
+
+    return publicIps;
+  }
+
+  private static long instanceCount(DescribeInstancesResponse describeInstancesResponse) {
+    return describeInstancesResponse
+        .reservations().stream().flatMap(reservation -> reservation.instances().stream()).count();
+  }
+
+  private static DescribeInstancesResponse describeInstances(List<String> instanceIds,
+      String state) {
+    return ec2.describeInstances(DescribeInstancesRequest.builder()
+        .instanceIds(instanceIds)
+        .filters(Filter.builder()
+            .name("instance-state-name")
+            .values(state)
+            .build())
+        .build());
+  }
+
+  private static void createKeyPair(String benchmarkTag) throws IOException {
+    CreateKeyPairResponse ckpr = ec2.createKeyPair(
+        CreateKeyPairRequest.builder().keyName(AwsBenchmarkMetadata.keyPair(benchmarkTag)).build());
+    Files.createDirectories(Paths.get(BenchmarkMetadata.benchmarkKeyFileDirectory()));
+    Path privateKey = Files.write(Paths.get(AwsBenchmarkMetadata.keyPairFileName(benchmarkTag)),
+        ckpr.keyMaterial().getBytes());
+    Files.setPosixFilePermissions(privateKey, PosixFilePermissions.fromString("rw-------"));
+  }
+
+  private static void createLaunchTemplate(String benchmarkTag, Image newestImage) {
+    ArrayList<String> securityGroupList = new ArrayList<>();
+    securityGroupList.add(AwsBenchmarkMetadata.securityGroup(benchmarkTag));
+
+    // Create the launch template
+    CreateLaunchTemplateResponse cltresponse =
+        ec2.createLaunchTemplate(CreateLaunchTemplateRequest.builder()
+            .launchTemplateName(AwsBenchmarkMetadata.launchTemplate(benchmarkTag))
+            .launchTemplateData(RequestLaunchTemplateData.builder()
+                .imageId(newestImage.imageId())
+                .instanceType(AwsBenchmarkMetadata.instanceType())
+                .placement(LaunchTemplatePlacementRequest.builder()
+                    .groupName(AwsBenchmarkMetadata.placementGroup(benchmarkTag))
+                    .tenancy(AwsBenchmarkMetadata.tenancy())
+                    .build())
+                .keyName(AwsBenchmarkMetadata.keyPair(benchmarkTag))
+                .securityGroups(securityGroupList)
+                .build())
+            .build());
+
+    System.out.println("Launch Template for cluster '" + benchmarkTag + "' created.");
+  }
+
+  private static void createSecurityGroup(String benchmarkTag, List<Tag> tags) {
+    // Make a security group for the launch template
+    CreateSecurityGroupResponse csgr = ec2.createSecurityGroup(CreateSecurityGroupRequest.builder()
+        .groupName(AwsBenchmarkMetadata.securityGroup(benchmarkTag))
+        .description(AwsBenchmarkMetadata.securityGroup(benchmarkTag))
+        .build());
+    String groupId = csgr.groupId();
+    ec2.createTags(CreateTagsRequest.builder().resources(groupId).tags(tags).build());
+    System.out.println("Security Group for cluster '" + benchmarkTag + "' created.");
+
+    // Allow all members of the security group to freely talk to each other
+    ec2.authorizeSecurityGroupIngress(AuthorizeSecurityGroupIngressRequest.builder()
+        .groupName(AwsBenchmarkMetadata.securityGroup(benchmarkTag))
+        .sourceSecurityGroupName(AwsBenchmarkMetadata.securityGroup(benchmarkTag))
+        .build());
+    ec2.authorizeSecurityGroupIngress(AuthorizeSecurityGroupIngressRequest.builder()
+        .groupName(AwsBenchmarkMetadata.securityGroup(benchmarkTag))
+        .cidrIp("0.0.0.0/0")
+        .ipProtocol("tcp")
+        .toPort(22)
+        .fromPort(22)
+        .build());
+    System.out.println("Security Group permissions for cluster '" + benchmarkTag + "' set.");
+  }
+
+  private static void createPlacementGroup(String benchmarkTag) {
+    ec2.createPlacementGroup(CreatePlacementGroupRequest.builder()
+        .groupName(AwsBenchmarkMetadata.placementGroup(benchmarkTag))
+        .strategy(AwsBenchmarkMetadata.placementGroupStrategy())
+        .build());
+    System.out.println("Placement Group for cluster '" + benchmarkTag + "' created.");
+  }
+
+  private static List<Tag> getTags(String benchmarkTag) {
+    // Create tags for everything
+    List<Tag> tags = new ArrayList<>();
+    tags.add(Tag.builder().key("purpose").value(BenchmarkMetadata.PREFIX).build());
+    tags.add(Tag.builder().key(BenchmarkMetadata.PREFIX).value(benchmarkTag).build());
+    return tags;
+  }
+
+  private static Image getNewestImage() {
+    DateTimeFormatter inputFormatter =
+        DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH);
+
+    // Find an appropriate AMI to launch our cluster with
+    List<Image> benchmarkImages = ec2.describeImages(
+        DescribeImagesRequest
+            .builder()
+            .filters(Filter.builder().name("tag:purpose").values("geode-benchmarks").build())
+            .build())
+        .images();
+
+    // benchmarkImages is an immutable list so we have to copy it
+    List<Image> sortableImages = new ArrayList<>(benchmarkImages);
+    sortableImages.sort(
+        Comparator.comparing((Image i) -> LocalDateTime.parse(i.creationDate(), inputFormatter)));
+    if (sortableImages.size() < 1) {
+      System.out.println("No suitable AMIs available, exiting.");
+      System.exit(1);
+    }
+    return sortableImages.get(sortableImages.size() - 1);
+  }
+}
diff --git a/settings.gradle b/settings.gradle
index f32ed8d..69d363e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -19,3 +19,5 @@ rootProject.name = 'geode-benchmarks'
 
 include 'harness'
 include 'geode-benchmarks'
+include 'infrastructure'
+


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services