From c5ae346c29ecf6890204adfde4491970218f1ed1 Mon Sep 17 00:00:00 2001 From: Michael Wittig Date: Fri, 6 Oct 2023 17:48:37 +0200 Subject: [PATCH] chore: migrate to GHA --- .github/workflows/acceptance-test-run.yml | 45 ++++ .github/workflows/acceptance-test.yml | 39 ++++ .github/workflows/acceptance-tests.yml | 21 ++ .github/workflows/latest.yml | 2 +- test/README.md | 63 ++++++ test/pom.xml | 98 +++++++++ .../java/de/widdix/awsinaction/AAWSTest.java | 123 +++++++++++ .../java/de/widdix/awsinaction/ACliTest.java | 79 +++++++ .../awsinaction/ACloudFormationTest.java | 199 ++++++++++++++++++ .../java/de/widdix/awsinaction/ATest.java | 90 ++++++++ .../java/de/widdix/awsinaction/Config.java | 51 +++++ .../awsinaction/chapter/Chapter02Test.java | 21 ++ .../awsinaction/chapter/Chapter04Test.java | 24 +++ .../awsinaction/chapter/Chapter05Test.java | 98 +++++++++ .../awsinaction/chapter/Chapter08Test.java | 40 ++++ .../awsinaction/chapter/Chapter09Test.java | 59 ++++++ .../awsinaction/chapter/Chapter10Test.java | 45 ++++ .../awsinaction/chapter/Chapter11Test.java | 49 +++++ .../awsinaction/chapter/Chapter13Test.java | 57 +++++ .../awsinaction/chapter/Chapter14Test.java | 21 ++ .../awsinaction/chapter/Chapter15Test.java | 29 +++ .../awsinaction/chapter/Chapter17Test.java | 139 ++++++++++++ .../awsinaction/chapter/Chapter18Test.java | 26 +++ 23 files changed, 1417 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/acceptance-test-run.yml create mode 100644 .github/workflows/acceptance-test.yml create mode 100644 .github/workflows/acceptance-tests.yml create mode 100644 test/README.md create mode 100644 test/pom.xml create mode 100644 test/src/test/java/de/widdix/awsinaction/AAWSTest.java create mode 100644 test/src/test/java/de/widdix/awsinaction/ACliTest.java create mode 100644 test/src/test/java/de/widdix/awsinaction/ACloudFormationTest.java create mode 100644 test/src/test/java/de/widdix/awsinaction/ATest.java create mode 100644 test/src/test/java/de/widdix/awsinaction/Config.java create mode 100644 test/src/test/java/de/widdix/awsinaction/chapter/Chapter02Test.java create mode 100644 test/src/test/java/de/widdix/awsinaction/chapter/Chapter04Test.java create mode 100644 test/src/test/java/de/widdix/awsinaction/chapter/Chapter05Test.java create mode 100644 test/src/test/java/de/widdix/awsinaction/chapter/Chapter08Test.java create mode 100644 test/src/test/java/de/widdix/awsinaction/chapter/Chapter09Test.java create mode 100644 test/src/test/java/de/widdix/awsinaction/chapter/Chapter10Test.java create mode 100644 test/src/test/java/de/widdix/awsinaction/chapter/Chapter11Test.java create mode 100644 test/src/test/java/de/widdix/awsinaction/chapter/Chapter13Test.java create mode 100644 test/src/test/java/de/widdix/awsinaction/chapter/Chapter14Test.java create mode 100644 test/src/test/java/de/widdix/awsinaction/chapter/Chapter15Test.java create mode 100644 test/src/test/java/de/widdix/awsinaction/chapter/Chapter17Test.java create mode 100644 test/src/test/java/de/widdix/awsinaction/chapter/Chapter18Test.java diff --git a/.github/workflows/acceptance-test-run.yml b/.github/workflows/acceptance-test-run.yml new file mode 100644 index 0000000..1a7ba69 --- /dev/null +++ b/.github/workflows/acceptance-test-run.yml @@ -0,0 +1,45 @@ +--- +name: Acceptenace Test Run +on: + workflow_call: + inputs: + tests: + required: true + type: string + deletion-policy: + required: true + type: string # delete/retain +permissions: + id-token: write + contents: read +defaults: + run: + shell: bash +jobs: + run: + runs-on: ['hyperenv', 'medium'] + steps: + - uses: actions/checkout@v4 + - uses: aws-actions/configure-aws-credentials@v4.0.1 + with: + role-to-assume: arn:aws:iam::859548834666:role/github-openid-connect + role-session-name: github-actions-aws-in-action-code3 + role-duration-seconds: 21600 + aws-region: us-east-1 + - uses: actions/setup-java@v3 + with: + distribution: corretto + java-version: '8' + cache: maven + - working-directory: test + env: + DELETION_POLICY: ${{ inputs.deletion-policy }} + TEMPLATE_DIR: ../ + run: mvn -B clean test site -Dtest=${{ inputs.tests }} -Dsurefire.reportNameSuffix=us-east-1 + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: surefire-reports-us-east-1 + path: | + test/target/surefire-reports/ + test/target/site/ diff --git a/.github/workflows/acceptance-test.yml b/.github/workflows/acceptance-test.yml new file mode 100644 index 0000000..d354afd --- /dev/null +++ b/.github/workflows/acceptance-test.yml @@ -0,0 +1,39 @@ +--- +name: Acceptenace Test +on: + workflow_dispatch: + inputs: + tests: + required: true + type: choice + default: '*' + options: # compile list with cd test/src && find . -name '*Test*' -execdir basename {} .java ';' | sort + - '*' + - Chapter02Test + - Chapter04Test + - Chapter05Test + - Chapter08Test + - Chapter09Test + - Chapter10Test + - Chapter11Test + - Chapter13Test + - Chapter14Test + - Chapter15Test + - Chapter17Test + - Chapter18Test + deletion-policy: + required: true + type: choice + default: delete + options: + - delete + - retain +defaults: + run: + shell: bash +jobs: + acceptance-test: + uses: ./.github/workflows/acceptance-test-run.yml + with: + tests: ${{ inputs.tests }} + deletion-policy: ${{ inputs.deletion-policy }} diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml new file mode 100644 index 0000000..c8ff584 --- /dev/null +++ b/.github/workflows/acceptance-tests.yml @@ -0,0 +1,21 @@ +--- +name: Acceptenace Tests +on: + workflow_dispatch: {} + push: + branches: + - master + paths: + - '.github/workflows/acceptance-tests.yml' + - '*.yaml' + - 'test/**' +concurrency: acceptenace-tests-${{ github.ref }} +defaults: + run: + shell: bash +jobs: + acceptance-test: + uses: ./.github/workflows/acceptance-test-run.yml + with: + tests: '*' + deletion-policy: delete diff --git a/.github/workflows/latest.yml b/.github/workflows/latest.yml index e15278a..1acc457 100644 --- a/.github/workflows/latest.yml +++ b/.github/workflows/latest.yml @@ -22,4 +22,4 @@ jobs: aws-region: us-east-1 - name: latest run: | - aws s3 sync --delete --exact-timestamps --exclude ".git/*" --exclude ".github/*" ./ s3://awsinaction-code3/ + aws s3 sync --delete --exact-timestamps --exclude ".git/*" --exclude ".github/*" --exclude "test/*" ./ s3://awsinaction-code3/ diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..4eab94f --- /dev/null +++ b/test/README.md @@ -0,0 +1,63 @@ +# AWS in Action (3rd edition) Code tests + +Tests for https://github.com/AWSinAction/code3 + +The goal of this tests is to ensure that our templates are always working. The test are implemented in Java 8 and run in JUnit 4. + +If you run this tests, many AWS CloudFormation tests are created and **charges will apply**! + +## Supported env variables + +* `IAM_ROLE_ARN` if the tests should assume an IAM role before they run supply the ARN of the IAM role +* `TEMPLATE_DIR` Load templates from local disk (instead of S3 bucket `widdix-aws-cf-templates`). Must end with an `/`. See `BUCKET_NAME` as well. +* `BUCKET_NAME` Some templates are to big to be passed as a string from local disk, therefore you need to supply the name of the bucket that is used to upload templates. +* `BUCKET_REGION` **required if BUCKET_NAME is set** Region of the bucket +* `DELETION_POLICY` (default `delete`, allowed values [`delete`, `retain`]) should resources be deleted? + +## Usage + +### AWS credentials + +The AWS credentials are passed in as defined by the AWS SDK for Java: http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html + +One addition is, that you can supply the env variable `IAM_ROLE_ARN` which let's the tests assume a role before they start with the default credentials. + +### Region selection + +The region selection works like defined by the AWS SDK for Java: http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/java-dg-region-selection.html + +### Run all tests + +``` +AWS_REGION="us-east-1" mvn test +``` + +### Run a single test suite + +to run the `TestJenkins` tests: + +``` +AWS_REGION="us-east-1" mvn -Dtest=TestJenkins test +``` + +### Run a single test + +to run the `TestJenkins.testHA` test: + +``` +AWS_REGION="us-east-1" mvn -Dtest=TestJenkins#testHA test +``` + +### Load templates from local file system + +``` +AWS_REGION="us-east-1" BUCKET_REGION="..." BUCKET_NAME="..." TEMPLATE_DIR="/path/to/widdix-aws-cf-templates/" mvn test +``` + +### Assume role + +This is useful if you run on a integration server like Jenkins and want to assume a different IAM role for this tests. + +``` +IAM_ROLE_ARN="arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME" mvn test +``` diff --git a/test/pom.xml b/test/pom.xml new file mode 100644 index 0000000..bbc308a --- /dev/null +++ b/test/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + de.widdix + aws-in-action-code3-tests + 1.0-SNAPSHOT + + + + com.amazonaws + aws-java-sdk-cloudformation + test + + + com.amazonaws + aws-java-sdk-ec2 + test + + + com.amazonaws + aws-java-sdk-s3 + test + + + com.amazonaws + aws-java-sdk-ecs + test + + + com.amazonaws + aws-java-sdk-sts + test + + + de.taimos + httputils + 1.10 + test + + + com.evanlennick + retry4j + 0.6.2 + test + + + com.jcraft + jsch + 0.1.54 + test + + + junit + junit + 4.12 + test + + + + + + + com.amazonaws + aws-java-sdk-bom + 1.11.883 + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.20 + + classes + 2 + + + + + + diff --git a/test/src/test/java/de/widdix/awsinaction/AAWSTest.java b/test/src/test/java/de/widdix/awsinaction/AAWSTest.java new file mode 100644 index 0000000..2183393 --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/AAWSTest.java @@ -0,0 +1,123 @@ +package de.widdix.awsinaction; + +import com.amazonaws.auth.*; +import com.amazonaws.regions.DefaultAwsRegionProviderChain; +import com.amazonaws.services.s3.model.Region; +import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.AmazonEC2ClientBuilder; +import com.amazonaws.services.ec2.model.*; +import com.amazonaws.services.ec2.model.Filter; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.*; +import com.amazonaws.services.securitytoken.AWSSecurityTokenService; +import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; +import com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest; + +import java.util.List; +import java.util.UUID; + +public abstract class AAWSTest extends ATest { + + public final static String IAM_SESSION_NAME = "aws-in-action-code3-tests"; + + protected final AWSCredentialsProvider credentialsProvider; + + private AmazonEC2 ec2; + + private final AmazonS3 s3; + + private final AWSSecurityTokenService sts; + + public AAWSTest() { + super(); + if (Config.has(Config.Key.IAM_ROLE_ARN)) { + final AWSSecurityTokenService local = AWSSecurityTokenServiceClientBuilder.standard().withCredentials(new DefaultAWSCredentialsProviderChain()).build(); + this.credentialsProvider = new STSAssumeRoleSessionCredentialsProvider.Builder(Config.get(Config.Key.IAM_ROLE_ARN), IAM_SESSION_NAME).withStsClient(local).build(); + } else { + this.credentialsProvider = new DefaultAWSCredentialsProviderChain(); + } + this.ec2 = AmazonEC2ClientBuilder.standard().withCredentials(this.credentialsProvider).build(); + this.s3 = AmazonS3ClientBuilder.standard().withCredentials(this.credentialsProvider).build(); + this.sts = AWSSecurityTokenServiceClientBuilder.standard().withCredentials(this.credentialsProvider).build(); + } + + protected final KeyPair createKey(final String keyName) { + final CreateKeyPairResult res = this.ec2.createKeyPair(new CreateKeyPairRequest().withKeyName(keyName)); + System.out.println("keypair[" + keyName + "] created: " + res.getKeyPair().getKeyMaterial()); + return res.getKeyPair(); + } + + protected final void deleteKey(final String keyName) { + if (Config.get(Config.Key.DELETION_POLICY).equals("delete")) { + this.ec2.deleteKeyPair(new DeleteKeyPairRequest().withKeyName(keyName)); + System.out.println("keypair[" + keyName + "] deleted"); + } + } + + protected final void createBucket(final String name, final String policy) { + this.s3.createBucket(new CreateBucketRequest(name, Region.fromValue(this.getRegion()))); + this.s3.setBucketPolicy(name, policy); + } + + protected final void emptyBucket(final String name) { + ObjectListing objectListing = s3.listObjects(name); + while (true) { + objectListing.getObjectSummaries().forEach((summary) -> s3.deleteObject(name, summary.getKey())); + if (objectListing.isTruncated()) { + objectListing = s3.listNextBatchOfObjects(objectListing); + } else { + break; + } + } + VersionListing versionListing = s3.listVersions(new ListVersionsRequest().withBucketName(name)); + while (true) { + versionListing.getVersionSummaries().forEach((vs) -> s3.deleteVersion(name, vs.getKey(), vs.getVersionId())); + if (versionListing.isTruncated()) { + versionListing = s3.listNextBatchOfVersions(versionListing); + } else { + break; + } + } + } + + protected final void deleteBucket(final String name) { + this.emptyBucket(name); + this.s3.deleteBucket(new DeleteBucketRequest(name)); + } + + protected final Vpc getDefaultVPC() { + final DescribeVpcsResult res = this.ec2.describeVpcs(new DescribeVpcsRequest().withFilters(new Filter().withName("isDefault").withValues("true"))); + return res.getVpcs().get(0); + } + + protected final List getDefaultSubnets() { + final DescribeSubnetsResult res = this.ec2.describeSubnets(new DescribeSubnetsRequest().withFilters(new Filter().withName("defaultForAz").withValues("true"))); + return res.getSubnets(); + } + + protected final SecurityGroup getDefaultSecurityGroup() { + final Vpc vpc = this.getDefaultVPC(); + final DescribeSecurityGroupsResult res = this.ec2.describeSecurityGroups(new DescribeSecurityGroupsRequest().withFilters( + new Filter().withName("vpc-id").withValues(vpc.getVpcId()), + new Filter().withName("group-name").withValues("default") + )); + return res.getSecurityGroups().get(0); + } + + protected final String getRegion() { + return new DefaultAwsRegionProviderChain().getRegion(); + } + + protected final String getAccount() { + return this.sts.getCallerIdentity(new GetCallerIdentityRequest()).getAccount(); + } + + protected final String random8String() { + final String uuid = UUID.randomUUID().toString().replace("-", "").toLowerCase(); + final int beginIndex = (int) (Math.random() * (uuid.length() - 7)); + final int endIndex = beginIndex + 7; + return "r" + uuid.substring(beginIndex, endIndex); // must begin [a-z] + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/ACliTest.java b/test/src/test/java/de/widdix/awsinaction/ACliTest.java new file mode 100644 index 0000000..3c460a7 --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/ACliTest.java @@ -0,0 +1,79 @@ +package de.widdix.awsinaction; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSSessionCredentials; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.concurrent.TimeUnit; + +public abstract class ACliTest extends AAWSTest { + + private String read(final InputStream is) throws Exception { + final BufferedReader br = new BufferedReader(new InputStreamReader(is)); + final StringBuilder sb = new StringBuilder(); + String s = null; + while ((s = br.readLine()) != null) { + sb.append(s); + } + return sb.toString(); + } + + private void printCommand(final String[] command) { + for (final String s : command) { + System.out.print(s); + System.out.print(" "); + } + System.out.println(); + } + + protected final String exec(final String[] command) throws Exception { + this.printCommand(command); + final ProcessBuilder pb = new ProcessBuilder(command); + if (System.getenv().containsKey("AWS_REGION")) { + pb.environment().put("AWS_DEFAULT_REGION", System.getenv("AWS_REGION")); + } + if (System.getenv().containsKey("AWS_PROFILE")) { + pb.environment().put("AWS_DEFAULT_PROFILE", System.getenv("AWS_PROFILE")); + } + final AWSCredentials credentials = this.credentialsProvider.getCredentials(); + pb.environment().put("AWS_ACCESS_KEY_ID", credentials.getAWSAccessKeyId()); + pb.environment().put("AWS_SECRET_ACCESS_KEY", credentials.getAWSSecretKey()); + if (credentials instanceof AWSSessionCredentials) { + pb.environment().put("AWS_SESSION_TOKEN", ((AWSSessionCredentials)credentials).getSessionToken()); + } + final Process p = pb.start(); + final String stdout = this.read(p.getInputStream()); + final String stderr = this.read(p.getErrorStream()); + System.out.print(stdout); + System.out.println(); + if (stderr.length() > 0) { + System.err.print(stderr); + System.out.println(); + } + final int exitCode = p.waitFor(); + if (exitCode != 0) { + throw new Error("exec terminated with exit code: " + exitCode); + } + return stdout; + } + + protected final void waitFor(final String expectedStdout, final String[] command) throws Exception { + final long start = System.currentTimeMillis(); + final long end = start + TimeUnit.MINUTES.toMillis(60); + while (System.currentTimeMillis() < end) { + try { + Thread.sleep(5000); + } catch (final InterruptedException e) { + // continue + } + final String stdout = this.exec(command); + if (stdout.equals(expectedStdout)) { + return; + } + } + throw new RuntimeException("waitFor timed out"); + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/ACloudFormationTest.java b/test/src/test/java/de/widdix/awsinaction/ACloudFormationTest.java new file mode 100644 index 0000000..f1e26fb --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/ACloudFormationTest.java @@ -0,0 +1,199 @@ +package de.widdix.awsinaction; + +import com.amazonaws.AmazonServiceException; +import com.amazonaws.services.cloudformation.AmazonCloudFormation; +import com.amazonaws.services.cloudformation.AmazonCloudFormationClientBuilder; +import com.amazonaws.services.cloudformation.model.*; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; + +public abstract class ACloudFormationTest extends AAWSTest { + + public static String readFile(String path, Charset encoding) { + try { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + return new String(encoded, encoding); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + private final AmazonCloudFormation cf = AmazonCloudFormationClientBuilder.standard().withCredentials(this.credentialsProvider).build(); + + public ACloudFormationTest() { + super(); + } + + protected final void createStack(final String stackName, final String template, final Parameter...parameters) { + CreateStackRequest req = new CreateStackRequest() + .withStackName(stackName) + .withParameters(parameters) + .withCapabilities(Capability.CAPABILITY_IAM, Capability.CAPABILITY_NAMED_IAM); + if (Config.has(Config.Key.TEMPLATE_DIR)) { + final String dir = Config.get(Config.Key.TEMPLATE_DIR); + if (Config.has(Config.Key.BUCKET_NAME)) { + final String bucketName = Config.get(Config.Key.BUCKET_NAME); + final String bucketRegion = Config.get(Config.Key.BUCKET_REGION); + final AmazonS3 s3local = AmazonS3ClientBuilder.standard().withCredentials(this.credentialsProvider).withRegion(bucketRegion).build(); + s3local.putObject(bucketName, stackName, new File(dir + template)); + req = req.withTemplateURL("https://s3-" + bucketRegion + ".amazonaws.com/" + bucketName + "/" + stackName); + } else { + final String body = readFile(dir + template, Charset.forName("UTF-8")); + req = req.withTemplateBody(body); + } + } else { + req = req.withTemplateURL("https://s3-eu-west-1.amazonaws.com/widdix-aws-cf-templates/" + template); + } + this.cf.createStack(req); + this.waitForStack(stackName, FinalStatus.CREATE_COMPLETE); + } + + protected final void updateStack(final String stackName, final String template, final Parameter...parameters) { + UpdateStackRequest req = new UpdateStackRequest() + .withStackName(stackName) + .withParameters(parameters) + .withCapabilities(Capability.CAPABILITY_IAM); + if (Config.has(Config.Key.TEMPLATE_DIR)) { + final String dir = Config.get(Config.Key.TEMPLATE_DIR); + if (Config.has(Config.Key.BUCKET_NAME)) { + final String bucketName = Config.get(Config.Key.BUCKET_NAME); + final String bucketRegion = Config.get(Config.Key.BUCKET_REGION); + final AmazonS3 s3local = AmazonS3ClientBuilder.standard().withCredentials(this.credentialsProvider).withRegion(bucketRegion).build(); + s3local.putObject(bucketName, stackName, new File(dir + template)); + req = req.withTemplateURL("https://s3-" + bucketRegion + ".amazonaws.com/" + bucketName + "/" + stackName); + } else { + final String body = readFile(dir + template, Charset.forName("UTF-8")); + req = req.withTemplateBody(body); + } + } else { + req = req.withTemplateURL("https://s3-eu-west-1.amazonaws.com/widdix-aws-cf-templates/" + template); + } + this.cf.updateStack(req); + this.waitForStack(stackName, FinalStatus.UPDATE_COMPLETE); + } + + protected enum FinalStatus { + CREATE_COMPLETE(StackStatus.CREATE_COMPLETE, false, true, StackStatus.CREATE_IN_PROGRESS), + DELETE_COMPLETE(StackStatus.DELETE_COMPLETE, true, false, StackStatus.DELETE_IN_PROGRESS), + UPDATE_COMPLETE(StackStatus.UPDATE_COMPLETE, false, false, StackStatus.UPDATE_IN_PROGRESS, StackStatus.UPDATE_COMPLETE_CLEANUP_IN_PROGRESS); + + private final StackStatus finalStatus; + private final boolean notFoundIsFinalStatus; + private final boolean notFoundIsIntermediateStatus; + private final Set intermediateStatus; + + FinalStatus(StackStatus finalStatus, boolean notFoundIsFinalStatus, boolean notFoundIsIntermediateStatus, StackStatus...intermediateStatus) { + this.finalStatus = finalStatus; + this.notFoundIsFinalStatus = notFoundIsFinalStatus; + this.notFoundIsIntermediateStatus = notFoundIsIntermediateStatus; + this.intermediateStatus = new HashSet<>(Arrays.asList(intermediateStatus)); + } + } + + private List getStackEvents(final String stackName) { + final List events = new ArrayList<>(); + String nextToken = null; + do { + try { + final DescribeStackEventsResult res = this.cf.describeStackEvents(new DescribeStackEventsRequest().withStackName(stackName).withNextToken(nextToken)); + events.addAll(res.getStackEvents()); + nextToken = res.getNextToken(); + } catch (final AmazonServiceException e) { + if (e.getErrorMessage().equals("Stack [" + stackName + "] does not exist")) { + nextToken = null; + } else { + throw e; + } + } + } while (nextToken != null); + Collections.reverse(events); + return events; + } + + private void waitForStack(final String stackName, final FinalStatus finalStackStatus) { + System.out.println("waitForStack[" + stackName + "]: to reach status " + finalStackStatus.finalStatus); + final List eventsDisplayed = new ArrayList<>(); + while (true) { + try { + Thread.sleep(5000); + } catch (final InterruptedException e) { + // continue + } + final List events = getStackEvents(stackName); + for (final StackEvent event : events) { + boolean displayed = false; + for (final StackEvent eventDisplayed : eventsDisplayed) { + if (event.getEventId().equals(eventDisplayed.getEventId())) { + displayed = true; + } + } + if (!displayed) { + System.out.println("waitForStack[" + stackName + "]: " + event.getTimestamp().toString() + " " + event.getLogicalResourceId() + " " + event.getResourceStatus() + " " + event.getResourceStatusReason()); + eventsDisplayed.add(event); + } + } + try { + final DescribeStacksResult res = this.cf.describeStacks(new DescribeStacksRequest().withStackName(stackName)); + final StackStatus currentStatus = StackStatus.fromValue(res.getStacks().get(0).getStackStatus()); + if (finalStackStatus.finalStatus == currentStatus) { + System.out.println("waitForStack[" + stackName + "]: final status reached."); + return; + } else { + if (finalStackStatus.intermediateStatus.contains(currentStatus)) { + System.out.println("waitForStack[" + stackName + "]: continue to wait (still in intermediate status " + currentStatus + ") ..."); + } else { + throw new RuntimeException("waitForStack[" + stackName + "]: reached invalid intermediate status " + currentStatus + "."); + } + } + } catch (final AmazonServiceException e) { + if (e.getErrorMessage().equals("Stack with id " + stackName + " does not exist")) { + if (finalStackStatus.notFoundIsFinalStatus) { + System.out.println("waitForStack[" + stackName + "]: final reached (not found)."); + return; + } else { + if (finalStackStatus.notFoundIsIntermediateStatus) { + System.out.println("waitForStack[" + stackName + "]: continue to wait (stack not found) ..."); + } else { + throw new RuntimeException("waitForStack[" + stackName + "]: stack not found."); + } + } + } else { + throw e; + } + } + } + } + + protected final Map getStackOutputs(final String stackName) { + final DescribeStacksResult res = this.cf.describeStacks(new DescribeStacksRequest().withStackName(stackName)); + final List outputs = res.getStacks().get(0).getOutputs(); + final Map map = new HashMap<>(outputs.size()); + for (final Output output : outputs) { + map.put(output.getOutputKey(), output.getOutputValue()); + } + return map; + } + + protected final String getStackOutputValue(final String stackName, final String outputKey) { + return this.getStackOutputs(stackName).get(outputKey); + } + + protected final void deleteStack(final String stackName) { + if (Config.get(Config.Key.DELETION_POLICY).equals("delete")) { + this.cf.deleteStack(new DeleteStackRequest().withStackName(stackName)); + if (Config.has(Config.Key.BUCKET_NAME)) { + final AmazonS3 s3local = AmazonS3ClientBuilder.standard().withCredentials(this.credentialsProvider).withRegion(Config.get(Config.Key.BUCKET_REGION)).build(); + s3local.deleteObject(Config.get(Config.Key.BUCKET_NAME), stackName); + } + this.waitForStack(stackName, FinalStatus.DELETE_COMPLETE); + } + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/ATest.java b/test/src/test/java/de/widdix/awsinaction/ATest.java new file mode 100644 index 0000000..0233bfc --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/ATest.java @@ -0,0 +1,90 @@ +package de.widdix.awsinaction; + +import com.amazonaws.services.ec2.model.KeyPair; +import com.evanlennick.retry4j.CallExecutor; +import com.evanlennick.retry4j.CallResults; +import com.evanlennick.retry4j.RetryConfig; +import com.evanlennick.retry4j.RetryConfigBuilder; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import org.junit.Assert; + +import java.time.temporal.ChronoUnit; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; + +public abstract class ATest { + + protected final T retry(final Callable callable) { + final AtomicInteger t = new AtomicInteger(0); + final Callable wrapper = () -> { + try { + return callable.call(); + } catch (final Exception e) { + System.out.println("retry[" + t.incrementAndGet() + "] exception: " + e.getMessage()); + e.printStackTrace(System.out); + System.out.println(); + throw e; + } + }; + final RetryConfig config = new RetryConfigBuilder() + .retryOnAnyException() + .withMaxNumberOfTries(30) + .withDelayBetweenTries(10, ChronoUnit.SECONDS) + .withFixedBackoff() + .build(); + final CallResults results = new CallExecutor(config).execute(wrapper); + return (T) results.getResult(); + } + + public static final class User { + public final String userName; + public final byte[] sshPrivateKeyBlob; + public final String sshPublicKeyId; + + public User(final String userName, final byte[] sshPrivateKeyBlob, final String sshPublicKeyId) { + super(); + this.userName = userName; + this.sshPrivateKeyBlob = sshPrivateKeyBlob; + this.sshPublicKeyId = sshPublicKeyId; + } + } + + protected final void probeSSH(final String host, final User user) { + final Callable callable = () -> { + final JSch jsch = new JSch(); + final Session session = jsch.getSession(user.userName, host); + jsch.addIdentity(user.userName, user.sshPrivateKeyBlob, null, null); + jsch.setConfig("StrictHostKeyChecking", "no"); // for testing this should be fine. adding the host key seems to be only possible via a file which is not very useful here + session.connect(10000); + session.disconnect(); + return true; + }; + Assert.assertTrue("successful SSH connection", this.retry(callable)); + } + + protected final void probeSSH(final String host, final KeyPair key) { + final Callable callable = () -> { + final JSch jsch = new JSch(); + final Session session = jsch.getSession("ec2-user", host); + jsch.addIdentity(key.getKeyName(), key.getKeyMaterial().getBytes(), null, null); + jsch.setConfig("StrictHostKeyChecking", "no"); // for testing this should be fine. adding the host key seems to be only possible via a file which is not very useful here + session.connect(10000); + session.disconnect(); + return true; + }; + Assert.assertTrue("successful SSH connection", this.retry(callable)); + } + + protected final Session tunnelSSH(final String host, final KeyPair key, final Integer localPort, final String remoteHost, final Integer remotePort) throws JSchException { + final JSch jsch = new JSch(); + final Session session = jsch.getSession("ec2-user", host); + jsch.addIdentity(key.getKeyName(), key.getKeyMaterial().getBytes(), null, null); + jsch.setConfig("StrictHostKeyChecking", "no"); // for testing this should be fine. adding the host key seems to be only possible via a file which is not very useful here + session.setPortForwardingL(localPort, remoteHost, remotePort); + session.connect(10000); + return session; + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/Config.java b/test/src/test/java/de/widdix/awsinaction/Config.java new file mode 100644 index 0000000..1262a35 --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/Config.java @@ -0,0 +1,51 @@ +package de.widdix.awsinaction; +public final class Config { + + public enum Key { + TEMPLATE_DIR("TEMPLATE_DIR"), + BUCKET_NAME("BUCKET_NAME"), + BUCKET_REGION("BUCKET_REGION"), + IAM_ROLE_ARN("IAM_ROLE_ARN"), + DELETION_POLICY("DELETION_POLICY", "delete"); + + private final String name; + private final String defaultValue; + + Key(String name, String defaultValue) { + this.name = name; + this.defaultValue = defaultValue; + } + + Key(String name) { + this.name = name; + this.defaultValue = null; + } + } + + public static String get(final Key key) { + final String env = System.getenv(key.name); + if (env == null) { + if (key.defaultValue == null) { + throw new RuntimeException("config not found: " + key.name); + } else { + return key.defaultValue; + } + } else { + return env; + } + } + + public static boolean has(final Key key) { + final String env = System.getenv(key.name); + if (env == null) { + if (key.defaultValue == null) { + return false; + } else { + return true; + } + } else { + return true; + } + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/chapter/Chapter02Test.java b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter02Test.java new file mode 100644 index 0000000..a3ab56f --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter02Test.java @@ -0,0 +1,21 @@ +package de.widdix.awsinaction.chapter; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awsinaction.ACloudFormationTest; +import org.junit.Test; + +public class Chapter02Test extends ACloudFormationTest { + + @Test + public void testTemplate() { + final String stackName = "chapter2-" + this.random8String(); + try { + this.createStack(stackName, "chapter02/template.yaml", + new Parameter().withParameterKey("WordpressAdminPassword").withParameterValue(this.random8String()) + ); + } finally { + this.deleteStack(stackName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/chapter/Chapter04Test.java b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter04Test.java new file mode 100644 index 0000000..db4048c --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter04Test.java @@ -0,0 +1,24 @@ +package de.widdix.awsinaction.chapter; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awsinaction.ACloudFormationTest; +import org.junit.Test; + +public class Chapter04Test extends ACloudFormationTest { + + @Test + public void test() { + final String stackName = "chapter4-" + this.random8String(); + final String vpcId = this.getDefaultVPC().getVpcId(); + final String subnetId = this.getDefaultSubnets().get(0).getSubnetId(); + try { + this.createStack(stackName, "chapter04/virtualmachine.yaml", + new Parameter().withParameterKey("VPC").withParameterValue(vpcId), + new Parameter().withParameterKey("Subnet").withParameterValue(subnetId) + ); + } finally { + this.deleteStack(stackName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/chapter/Chapter05Test.java b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter05Test.java new file mode 100644 index 0000000..800c8a9 --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter05Test.java @@ -0,0 +1,98 @@ +package de.widdix.awsinaction.chapter; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awsinaction.ACloudFormationTest; +import org.junit.Ignore; +import org.junit.Test; + +@Ignore +public class Chapter05Test extends ACloudFormationTest { + + @Test + public void testVPCTemplate() { + final String stackName = "chapter6-vpc-" + this.random8String(); + try { + this.createStack(stackName, "chapter05/vpc.yaml"); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testEc2IamRoleTemplate() { + final String stackName = "chapter6-iam-role-" + this.random8String(); + final String vpcId = this.getDefaultVPC().getVpcId(); + final String subnetId = this.getDefaultSubnets().get(0).getSubnetId(); + try { + this.createStack(stackName, "chapter05/ec2-iamrole.yaml", + new Parameter().withParameterKey("VPC").withParameterValue(vpcId), + new Parameter().withParameterKey("Subnet").withParameterValue(subnetId) + ); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testEc2YumUpdateTemplate() { + final String stackName = "chapter6-yum-update-" + this.random8String(); + final String vpcId = this.getDefaultVPC().getVpcId(); + final String subnetId = this.getDefaultSubnets().get(0).getSubnetId(); + try { + this.createStack(stackName, "chapter05/ec2-yum-update.yaml", + new Parameter().withParameterKey("VPC").withParameterValue(vpcId), + new Parameter().withParameterKey("Subnet").withParameterValue(subnetId) + ); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testEc2OsUpdateTemplate() { + final String stackName = "chapter6-os-update-" + this.random8String(); + final String vpcId = this.getDefaultVPC().getVpcId(); + final String subnetId = this.getDefaultSubnets().get(0).getSubnetId(); + try { + this.createStack(stackName, "chapter05/ec2-os-update.yaml", + new Parameter().withParameterKey("VPC").withParameterValue(vpcId), + new Parameter().withParameterKey("Subnet").withParameterValue(subnetId) + ); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testFirewallTemplate() { + final String stackName = "chapter6-firewall-" + this.random8String(); + final String vpcId = this.getDefaultVPC().getVpcId(); + final String subnetId = this.getDefaultSubnets().get(0).getSubnetId(); + try { + this.createStack(stackName, "chapter05/firewall1.yaml", + new Parameter().withParameterKey("VPC").withParameterValue(vpcId), + new Parameter().withParameterKey("Subnet").withParameterValue(subnetId) + ); + this.updateStack(stackName, "chapter05/firewall2.yaml", + new Parameter().withParameterKey("VPC").withParameterValue(vpcId), + new Parameter().withParameterKey("Subnet").withParameterValue(subnetId) + ); + this.updateStack(stackName, "chapter05/firewall3.yaml", + new Parameter().withParameterKey("VPC").withParameterValue(vpcId), + new Parameter().withParameterKey("Subnet").withParameterValue(subnetId) + ); + this.updateStack(stackName, "chapter05/firewall4.yaml", + new Parameter().withParameterKey("VPC").withParameterValue(vpcId), + new Parameter().withParameterKey("Subnet").withParameterValue(subnetId), + new Parameter().withParameterKey("WhitelistedIpAddress").withParameterValue("10.10.10.10") + ); + this.updateStack(stackName, "chapter05/firewall5.yaml", + new Parameter().withParameterKey("VPC").withParameterValue(vpcId), + new Parameter().withParameterKey("Subnet").withParameterValue(subnetId) + ); + } finally { + this.deleteStack(stackName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/chapter/Chapter08Test.java b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter08Test.java new file mode 100644 index 0000000..ee575b9 --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter08Test.java @@ -0,0 +1,40 @@ +package de.widdix.awsinaction.chapter; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awsinaction.ACloudFormationTest; +import org.junit.Ignore; +import org.junit.Test; + +@Ignore +public class Chapter08Test extends ACloudFormationTest { + + @Test + public void testEBSTemplate() { + final String stackName = "chapter08-ebs-" + this.random8String(); + final String vpcId = this.getDefaultVPC().getVpcId(); + final String subnetId = this.getDefaultSubnets().get(0).getSubnetId(); + try { + this.createStack(stackName, "chapter08/ebs.yaml", + new Parameter().withParameterKey("VPC").withParameterValue(vpcId), + new Parameter().withParameterKey("Subnet").withParameterValue(subnetId) + ); + } finally { + this.deleteStack(stackName); + } + } + + public void testInstanceStoreTemplate() { + final String stackName = "chapter08-is-" + this.random8String(); + final String vpcId = this.getDefaultVPC().getVpcId(); + final String subnetId = this.getDefaultSubnets().get(0).getSubnetId(); + try { + this.createStack(stackName, "chapter08/instancestore.yaml", + new Parameter().withParameterKey("VPC").withParameterValue(vpcId), + new Parameter().withParameterKey("Subnet").withParameterValue(subnetId) + ); + } finally { + this.deleteStack(stackName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/chapter/Chapter09Test.java b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter09Test.java new file mode 100644 index 0000000..9280095 --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter09Test.java @@ -0,0 +1,59 @@ +package de.widdix.awsinaction.chapter; + +import de.widdix.awsinaction.ACloudFormationTest; +import org.junit.Ignore; +import org.junit.Test; + +@Ignore +public class Chapter09Test extends ACloudFormationTest { + + @Test + public void testTemplateDefault() { + final String stackName = "chapter09-efs-" + this.random8String(); + final String keyName = "key-" + this.random8String(); + try { + this.createKey(keyName); + try { + this.createStack(stackName, "chapter09/efs.yaml"); + this.updateStack(stackName, "chapter09/efs-backup.yaml"); + } finally { + this.deleteStack(stackName); + } + } finally { + this.deleteKey(keyName); + } + } + + @Test + public void testTemplateBackup() { + final String stackName = "chapter09-efs-backup-" + this.random8String(); + final String keyName = "key-" + this.random8String(); + try { + this.createKey(keyName); + try { + this.createStack(stackName, "chapter09/efs-backup.yaml"); + } finally { + this.deleteStack(stackName); + } + } finally { + this.deleteKey(keyName); + } + } + + @Test + public void testTemplateProvisioned() { + final String stackName = "chapter09-efs-provisioned-" + this.random8String(); + final String keyName = "key-" + this.random8String(); + try { + this.createKey(keyName); + try { + this.createStack(stackName, "chapter09/efs-provisioned.yaml"); + } finally { + this.deleteStack(stackName); + } + } finally { + this.deleteKey(keyName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/chapter/Chapter10Test.java b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter10Test.java new file mode 100644 index 0000000..e622d10 --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter10Test.java @@ -0,0 +1,45 @@ +package de.widdix.awsinaction.chapter; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awsinaction.ACloudFormationTest; +import org.junit.Test; + +public class Chapter10Test extends ACloudFormationTest { + + @Test + public void testTemplateDefault() { + final String stackName = "chapter10-default-" + this.random8String(); + try { + this.createStack(stackName, "chapter10/template.yaml", + new Parameter().withParameterKey("WordpressAdminPassword").withParameterValue(this.random8String()) + ); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testTemplateSnapshot() { + final String stackName = "chapter10-snapshot-" + this.random8String(); + try { + this.createStack(stackName, "chapter10/template-snapshot.yaml", + new Parameter().withParameterKey("WordpressAdminPassword").withParameterValue(this.random8String()) + ); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testTemplateMultiAZ() { + final String stackName = "chapter10-multiaz-" + this.random8String(); + try { + this.createStack(stackName, "chapter10/template-multiaz.yaml", + new Parameter().withParameterKey("WordpressAdminPassword").withParameterValue(this.random8String()) + ); + } finally { + this.deleteStack(stackName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/chapter/Chapter11Test.java b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter11Test.java new file mode 100644 index 0000000..4b0d2d7 --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter11Test.java @@ -0,0 +1,49 @@ +package de.widdix.awsinaction.chapter; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awsinaction.ACloudFormationTest; +import org.junit.Test; + +public class Chapter11Test extends ACloudFormationTest { + + @Test + public void testDiscourse() { + final String stackName = "chapter11-discourse-" + this.random8String(); + final String adminEmailAddress = "team@widdix.de"; + try { + this.createStack(stackName, "chapter11/discourse.yaml", + new Parameter().withParameterKey("AdminEmailAddress").withParameterValue(adminEmailAddress) + ); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testRedisMinimal() { + final String stackName = "chapter11-redis-minimal-" + this.random8String(); + final String vpc = this.getDefaultVPC().getVpcId(); + final String subnetA = this.getDefaultSubnets().get(0).getSubnetId(); + final String subnetB = this.getDefaultSubnets().get(0).getSubnetId(); + try { + this.createStack(stackName, "chapter11/redis-minimal.yaml", + new Parameter().withParameterKey("VPC").withParameterValue(vpc), + new Parameter().withParameterKey("SubnetA").withParameterValue(subnetA), + new Parameter().withParameterKey("SubnetB").withParameterValue(subnetB) + ); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testMemoryDBMinimal() { + final String stackName = "chapter11-memorydb-minimal-" + this.random8String(); + try { + this.createStack(stackName, "chapter11/memorydb-minimal.yaml"); + } finally { + this.deleteStack(stackName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/chapter/Chapter13Test.java b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter13Test.java new file mode 100644 index 0000000..0dd88c2 --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter13Test.java @@ -0,0 +1,57 @@ +package de.widdix.awsinaction.chapter; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awsinaction.ACloudFormationTest; +import org.junit.Test; + +public class Chapter13Test extends ACloudFormationTest { + + @Test + public void testRecovery() { + final String stackName = "chapter13-recovery-" + this.random8String(); + try { + this.createStack(stackName, "chapter13/recovery.yaml", + new Parameter().withParameterKey("JenkinsAdminPassword").withParameterValue(this.random8String()) + ); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testMultiAZ() { + final String stackName = "chapter13-multiaz-" + this.random8String(); + try { + this.createStack(stackName, "chapter13/multiaz.yaml", + new Parameter().withParameterKey("JenkinsAdminPassword").withParameterValue(this.random8String()) + ); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testMultiAZEFS() { + final String stackName = "chapter13-multiaz-efs-" + this.random8String(); + try { + this.createStack(stackName, "chapter13/multiaz-efs.yaml", + new Parameter().withParameterKey("JenkinsAdminPassword").withParameterValue(this.random8String()) + ); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testMultiAZEFSEIP() { + final String stackName = "chapter13-multiaz-efs-eip-" + this.random8String(); + try { + this.createStack(stackName, "chapter13/multiaz-efs-eip.yaml", + new Parameter().withParameterKey("JenkinsAdminPassword").withParameterValue(this.random8String()) + ); + } finally { + this.deleteStack(stackName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/chapter/Chapter14Test.java b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter14Test.java new file mode 100644 index 0000000..f00148c --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter14Test.java @@ -0,0 +1,21 @@ +package de.widdix.awsinaction.chapter; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awsinaction.ACloudFormationTest; +import org.junit.Test; + +public class Chapter14Test extends ACloudFormationTest { + + @Test + public void testTemplate() { + final String stackName = "chapter14-loadbalancer-" + this.random8String(); + try { + this.createStack(stackName, "chapter14/loadbalancer.yaml", + new Parameter().withParameterKey("NumberOfVirtualMachines").withParameterValue("2") + ); + } finally { + this.deleteStack(stackName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/chapter/Chapter15Test.java b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter15Test.java new file mode 100644 index 0000000..d688094 --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter15Test.java @@ -0,0 +1,29 @@ +package de.widdix.awsinaction.chapter; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awsinaction.ACloudFormationTest; +import org.junit.Test; + +public class Chapter15Test extends ACloudFormationTest { + + @Test + public void testCloudFormationTemplate() { + final String stackName = "chapter15-cloudformation-" + this.random8String(); + try { + this.createStack(stackName, "chapter15/cloudformation.yaml"); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testCodeDeployTemplate() { + final String stackName = "chapter15-codedeploy-" + this.random8String(); + try { + this.createStack(stackName, "chapter15/codedeploy.yaml"); + } finally { + this.deleteStack(stackName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/chapter/Chapter17Test.java b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter17Test.java new file mode 100644 index 0000000..7909a77 --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter17Test.java @@ -0,0 +1,139 @@ +package de.widdix.awsinaction.chapter; + +import com.amazonaws.services.cloudformation.model.Parameter; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.*; +import com.amazonaws.services.s3.model.DeleteObjectsRequest.KeyVersion; +import de.widdix.awsinaction.ACloudFormationTest; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class Chapter17Test extends ACloudFormationTest { + + private final AmazonS3 s3 = AmazonS3ClientBuilder.standard().withCredentials(this.credentialsProvider).build(); + + private long countObjects(final AmazonS3 s3, final String bucketName, final String marker, final long agg) { + final ObjectListing res = s3.listObjects(new ListObjectsRequest().withBucketName(bucketName).withMarker(marker).withMaxKeys(50)); + final long count = agg + res.getObjectSummaries().size(); + if (res.isTruncated()) { + return countObjects(s3, bucketName, res.getNextMarker(), count); + } else { + return count; + } + } + + private void waitForS3BucketCount(final long expected, final String bucketName) { + final long start = System.currentTimeMillis(); + final long end = start + TimeUnit.MINUTES.toMillis(90); + while (System.currentTimeMillis() < end) { + try { + Thread.sleep(15000); + } catch (final InterruptedException e) { + // continue + } + final long count = countObjects(s3, bucketName, null, 0); + if (count == expected) { + return; + } + } + throw new RuntimeException("waitForS3BucketCount[" + bucketName + "] timed out"); + } + + private long deleteAllObjects(final String bucketName, final String marker) { + final ObjectListing res = s3.listObjects(new ListObjectsRequest().withBucketName(bucketName).withMarker(marker).withMaxKeys(25)); + final List keys = new ArrayList<>(25); + for (final S3ObjectSummary os: res.getObjectSummaries()) { + keys.add(new KeyVersion(os.getKey())); + } + final DeleteObjectsRequest req2 = new DeleteObjectsRequest(bucketName); + req2.setKeys(keys); + final DeleteObjectsResult res2 = s3.deleteObjects(req2); + if (res.isTruncated()) { + return res2.getDeletedObjects().size() + deleteAllObjects(bucketName, res.getNextMarker()); + } else { + return res2.getDeletedObjects().size(); + } + } + + private void waitForDeletedObjects(final long expected, final String bucketName) { + final long start = System.currentTimeMillis(); + final long end = start + TimeUnit.MINUTES.toMillis(60); + long deleted = 0; + while (System.currentTimeMillis() < end) { + try { + Thread.sleep(15000); + } catch (final InterruptedException e) { + // continue + } + deleted += this.deleteAllObjects(bucketName, null); + if (deleted == expected) { + return; + } + } + throw new RuntimeException("waitForDeletedObjects[" + bucketName + "] timed out (" + deleted + ", " + expected + ")"); + } + + @Test + public void testURL2PNGLoadTest() { + final String stackName = "chapter17-url2png-loadtest-" + this.random8String(); + final String bucketName = "ch17-url2png-" + this.getAccount(); + try { + this.createStack(stackName, "chapter17/url2png-loadtest.yaml"); + this.waitForS3BucketCount(250, bucketName); + this.waitForDeletedObjects(250, bucketName); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testURL2PNG() { + final String stackName = "chapter17-url2png-" + this.random8String(); + try { + this.createStack(stackName, "chapter17/url2png.yaml"); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testWordpress() { + final String stackName = "chapter17-wordpress-" + this.random8String(); + try { + this.createStack(stackName, "chapter17/wordpress.yaml", + new Parameter().withParameterKey("WordpressAdminPassword").withParameterValue(this.random8String()) + ); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testWordpressLoadTest() { + final String stackName = "chapter17-wordpress-loadtest-" + this.random8String(); + try { + this.createStack(stackName, "chapter17/wordpress-loadtest.yaml", + new Parameter().withParameterKey("WordpressAdminPassword").withParameterValue(this.random8String()) + ); + } finally { + this.deleteStack(stackName); + } + } + + @Test + public void testWordpressSchedule() { + final String stackName = "chapter17-wordpress-schedule-" + this.random8String(); + try { + this.createStack(stackName, "chapter17/wordpress-schedule.yaml", + new Parameter().withParameterKey("WordpressAdminPassword").withParameterValue(this.random8String()) + ); + } finally { + this.deleteStack(stackName); + } + } + +} diff --git a/test/src/test/java/de/widdix/awsinaction/chapter/Chapter18Test.java b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter18Test.java new file mode 100644 index 0000000..a10a4d1 --- /dev/null +++ b/test/src/test/java/de/widdix/awsinaction/chapter/Chapter18Test.java @@ -0,0 +1,26 @@ +package de.widdix.awsinaction.chapter; + +import com.amazonaws.services.cloudformation.model.Parameter; +import de.widdix.awsinaction.ACloudFormationTest; +import org.junit.Test; + +public class Chapter18Test extends ACloudFormationTest { + + @Test + public void test() { + final String stackName = "chapter18-" + this.random8String(); + final String applicationId = this.random8String(); + try { + this.createStack(stackName, "chapter18/notea.yaml", + new Parameter().withParameterKey("ApplicationID").withParameterValue(applicationId), + new Parameter().withParameterKey("Password").withParameterValue(this.random8String()) + ); + } finally { + this.emptyBucket("awsinaction-notea-" + applicationId); + // While the application is running, it will write to the bucket, as a workaround, we are deleting the bucket. + this.deleteBucket("awsinaction-notea-" + applicationId); + this.deleteStack(stackName); + } + } + +}