From 65b8dc5258d78eafe8bac4abc0c739c7ddab0b40 Mon Sep 17 00:00:00 2001 From: Scott Macdonald <57190223+scmacdon@users.noreply.github.com> Date: Thu, 26 Oct 2023 15:15:12 -0400 Subject: [PATCH] Java V2 Add java files Resilient Service (#5557) * add java files * add sos * add sos * add hello example * added more sos tags * added more sos tags * updated main * updated readme * add tests * fixed indent * rolled in comments --- .doc_gen/metadata/cross_metadata.yaml | 20 + .../elastic-load-balancing-v2_metadata.yaml | 63 ++ javav2/usecases/resilient_service/README.md | 36 +- javav2/usecases/resilient_service/pom.xml | 36 +- .../com/example/resilient/AutoScaler.java | 553 ++++++++++++++++++ .../java/com/example/resilient/Database.java | 153 +++++ .../java/com/example/resilient/GroupInfo.java | 39 ++ .../example/resilient/HelloLoadBalancer.java | 40 ++ .../resilient/LaunchTemplateCreator.java | 277 +++++++++ .../com/example/resilient/LoadBalancer.java | 229 ++++++++ .../main/java/com/example/resilient/Main.java | 465 +++++++++++++++ .../example/resilient/ParameterHelper.java | 42 ++ .../com/example/resilient/Recommendation.java | 56 ++ .../src/main/resources/config.properties | 17 + .../src/test/java/ResilientTests.java | 142 +++++ 15 files changed, 2134 insertions(+), 34 deletions(-) create mode 100644 javav2/usecases/resilient_service/src/main/java/com/example/resilient/AutoScaler.java create mode 100644 javav2/usecases/resilient_service/src/main/java/com/example/resilient/Database.java create mode 100644 javav2/usecases/resilient_service/src/main/java/com/example/resilient/GroupInfo.java create mode 100644 javav2/usecases/resilient_service/src/main/java/com/example/resilient/HelloLoadBalancer.java create mode 100644 javav2/usecases/resilient_service/src/main/java/com/example/resilient/LaunchTemplateCreator.java create mode 100644 javav2/usecases/resilient_service/src/main/java/com/example/resilient/LoadBalancer.java create mode 100644 javav2/usecases/resilient_service/src/main/java/com/example/resilient/Main.java create mode 100644 javav2/usecases/resilient_service/src/main/java/com/example/resilient/ParameterHelper.java create mode 100644 javav2/usecases/resilient_service/src/main/java/com/example/resilient/Recommendation.java create mode 100644 javav2/usecases/resilient_service/src/main/resources/config.properties create mode 100644 javav2/usecases/resilient_service/src/test/java/ResilientTests.java diff --git a/.doc_gen/metadata/cross_metadata.yaml b/.doc_gen/metadata/cross_metadata.yaml index ac114a1827b..92c94b9c4fb 100644 --- a/.doc_gen/metadata/cross_metadata.yaml +++ b/.doc_gen/metadata/cross_metadata.yaml @@ -640,6 +640,26 @@ cross_ResilientService: - Control web server response to requests and health checks by updating &SYSlong; parameters. category: Scenarios languages: + Java: + versions: + - sdk_version: 2 + github: javav2/usecases/resilient_service + excerpts: + - description: Run the interactive scenario at a command prompt. + snippet_tags: + - javav2.example_code.workflow.ResilientService_Runner + - description: Create a class that wraps &AS; and &EC2; actions. + snippet_tags: + - javav2.example_code.workflow.ResilientService_AutoScaler + - description: Create a class that wraps &ELB; actions. + snippet_tags: + - javav2.example_code.workflow.ResilientService_LoadBalancer + - description: Create a class that uses &DDB; to simulate a recommendation service. + snippet_tags: + - javav2.example_code.workflow.ResilientService_RecommendationService + - description: Create a class that wraps &SYS; actions. + snippet_tags: + - javav2.example_code.workflow.ResilientService_ParameterHelper Python: versions: - sdk_version: 3 diff --git a/.doc_gen/metadata/elastic-load-balancing-v2_metadata.yaml b/.doc_gen/metadata/elastic-load-balancing-v2_metadata.yaml index 6542a86f4fc..be3daa6bc5a 100644 --- a/.doc_gen/metadata/elastic-load-balancing-v2_metadata.yaml +++ b/.doc_gen/metadata/elastic-load-balancing-v2_metadata.yaml @@ -4,6 +4,15 @@ elastic-load-balancing-v2_Hello: synopsis: get started using &ELB;. category: Hello languages: + Java: + versions: + - sdk_version: 2 + github: javav2/usecases/resilient_service + sdkguide: + excerpts: + - description: + snippet_tags: + - javav2.example_code.elbv2.Hello Python: versions: - sdk_version: 3 @@ -39,6 +48,15 @@ elastic-load-balancing-v2_CreateTargetGroup: synopsis: create an ELB target group. category: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/usecases/resilient_service + sdkguide: + excerpts: + - description: + snippet_tags: + - javav2.cross_service.resilient_service.elbv2.CreateTargetGroup Python: versions: - sdk_version: 3 @@ -57,6 +75,15 @@ elastic-load-balancing-v2_DeleteTargetGroup: synopsis: delete an ELB target group. category: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/usecases/resilient_service + sdkguide: + excerpts: + - description: + snippet_tags: + - javav2.cross_service.resilient_service.elbv2.DeleteTargetGroup Python: versions: - sdk_version: 3 @@ -75,6 +102,15 @@ elastic-load-balancing-v2_CreateLoadBalancer: synopsis: create an ELB Application Load Balancer. category: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/usecases/resilient_service + sdkguide: + excerpts: + - description: + snippet_tags: + - javav2.cross_service.resilient_service.elbv2.CreateLoadBalancer Python: versions: - sdk_version: 3 @@ -93,6 +129,15 @@ elastic-load-balancing-v2_CreateListener: synopsis: create a listener that forwards requests from an ELB load balancer to a target group. category: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/usecases/resilient_service + sdkguide: + excerpts: + - description: + snippet_tags: + - javav2.cross_service.resilient_service.elbv2.CreateListener Python: versions: - sdk_version: 3 @@ -111,6 +156,15 @@ elastic-load-balancing-v2_DeleteLoadBalancer: synopsis: delete an ELB load balancer. category: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/usecases/resilient_service + sdkguide: + excerpts: + - description: + snippet_tags: + - javav2.cross_service.resilient_service.elbv2.DeleteLoadBalancer Python: versions: - sdk_version: 3 @@ -129,6 +183,15 @@ elastic-load-balancing-v2_DescribeTargetHealth: synopsis: get the health of instances in an ELB target group. category: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/usecases/resilient_service + sdkguide: + excerpts: + - description: + snippet_tags: + - javav2.cross_service.resilient_service.elbv2.DescribeTargetHealth Python: versions: - sdk_version: 3 diff --git a/javav2/usecases/resilient_service/README.md b/javav2/usecases/resilient_service/README.md index e6593b82824..2ce44f8f4b0 100644 --- a/javav2/usecases/resilient_service/README.md +++ b/javav2/usecases/resilient_service/README.md @@ -25,7 +25,7 @@ Several components are used to demonstrate the resilience of the example web ser parameters control web server response to requests and health checks to simulate failures and demonstrate resiliency. -Each of these components is created and managed with the SDK for Python as part of +Each of these components is created and managed with the SDK for Java as part of an interactive demo that runs at a command prompt. ### Amazon EC2 Auto Scaling and EC2 instances @@ -81,34 +81,24 @@ To access the load balancer endpoint, you must allow inbound traffic on port 80 from your computer's IP address to your VPC. If this rule doesn't exist, the example tries to add it. Alternately, you can [add a rule to the default security group for your VPC](https://docs.aws.amazon.com/vpc/latest/userguide/security-group-rules.html) -and specify your computer's IP address -as a source. +and specify your computer's IP address as a source. -For general prerequisites, see the [README](../../README.md#prerequisites) in the `python` folder. +To complete the tutorial, you need the following: -### Instructions - -For general instructions to run the examples, see the -[README](../../README.md#run-the-examples) in the `python` folder. ++ An AWS account ++ A Java IDE (this example uses IntelliJ) ++ Java 17 SDK and Maven -Run this example by running the following command in the folder that contains this README: -``` -python runner.py --action all -``` +### Instructions This starts an interactive scenario that walks you through several aspects of creating a resilient web service and lets you send requests to the load balancer endpoint and verify -instance health along the way. - -#### Build and manage a resilient service - -You can run the entire example by specifying `--action all`. Alternatively, run each of the sections -separately by specifying actions of `deploy`, `demo`, or `destroy`. +instance health along the way. You can run this example in the Java IDE. ##### Deploy resources -Use the SDK for Python to create the following AWS resources: +Use the SDK for Java to create the following AWS resources: 1. A DynamoDB table that acts as a service that recommends books, movies, and songs. 2. An instance profile and an associated role and policy that grants permission to @@ -165,7 +155,7 @@ The scenario takes the following steps: ##### Destroy resources -Use the SDK for Python to clean up all resources created for this example. +Use the SDK for Java to clean up all resources created for this example. 1. Delete the load balancer and target group. 2. Stop all instances and delete the Auto Scaling group. @@ -177,9 +167,9 @@ Use the SDK for Python to clean up all resources created for this example. * [Application Load Balancers user guide](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) * [Amazon EC2 Auto Scaling user guide](https://docs.aws.amazon.com/autoscaling/ec2/userguide/what-is-amazon-ec2-auto-scaling.html) * [Amazon Elastic Compute Cloud (Amazon EC2) user guide](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html) -* [SDK for Python Elastic Load Balancing v2 reference](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/elbv2.html) -* [SDK for Python Amazon EC2 Auto Scaling reference](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/autoscaling.html) -* [SDK for Python Amazon EC2 reference](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html) +* [SDK for Java Elastic Load Balancing v2 reference](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/elasticloadbalancingv2/ElasticLoadBalancingV2Client.html) +* [SDK for Java Amazon EC2 Auto Scaling reference](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/autoscaling/AutoScalingClient.html) +* [SDK for Java Amazon EC2 reference](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html) --- diff --git a/javav2/usecases/resilient_service/pom.xml b/javav2/usecases/resilient_service/pom.xml index d91864d82ac..7bec0296033 100644 --- a/javav2/usecases/resilient_service/pom.xml +++ b/javav2/usecases/resilient_service/pom.xml @@ -3,14 +3,17 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + org.example - resilient_service + create_res_usecase 1.0-SNAPSHOT + 17 17 UTF-8 + @@ -30,6 +33,26 @@ IntegrationTest + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.2.2 + + checkstyle.xml + true + true + false + + + + validate + validate + + check + + + + @@ -37,7 +60,7 @@ software.amazon.awssdk bom - 2.20.159 + 2.21.2 pom import @@ -97,10 +120,6 @@ software.amazon.awssdk iam - - software.amazon.awssdk - dynamodb - software.amazon.awssdk ssm @@ -144,11 +163,6 @@ org.junit.platform junit-platform-commons 1.9.2 - - - software.amazon.awssdk - elasticloadbalancingv2 - 2.20.138 org.junit.platform diff --git a/javav2/usecases/resilient_service/src/main/java/com/example/resilient/AutoScaler.java b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/AutoScaler.java new file mode 100644 index 00000000000..1993324ce7e --- /dev/null +++ b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/AutoScaler.java @@ -0,0 +1,553 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +/** + * This package contains classes related to XYZ functionality. + * It provides implementations for ABC and XYZ operations. + */ + +package com.example.resilient; + +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.autoscaling.model.AttachLoadBalancerTargetGroupsRequest; +import software.amazon.awssdk.services.autoscaling.model.AutoScalingException; +import software.amazon.awssdk.services.autoscaling.model.AutoScalingGroup; +import software.amazon.awssdk.services.autoscaling.model.CreateAutoScalingGroupRequest; +import software.amazon.awssdk.services.autoscaling.model.DeleteAutoScalingGroupRequest; +import software.amazon.awssdk.services.autoscaling.model.DescribeAutoScalingGroupsRequest; +import software.amazon.awssdk.services.autoscaling.model.DescribeAutoScalingGroupsResponse; +import software.amazon.awssdk.services.autoscaling.model.TerminateInstanceInAutoScalingGroupRequest; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.AuthorizeSecurityGroupIngressRequest; +import software.amazon.awssdk.services.ec2.model.DescribeAvailabilityZonesResponse; +import software.amazon.awssdk.services.ec2.model.DescribeIamInstanceProfileAssociationsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeIamInstanceProfileAssociationsResponse; +import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsResponse; +import software.amazon.awssdk.services.ec2.model.DescribeSubnetsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSubnetsResponse; +import software.amazon.awssdk.services.ec2.model.DescribeVpcsResponse; +import software.amazon.awssdk.services.ec2.model.Ec2Exception; +import software.amazon.awssdk.services.ec2.model.Filter; +import software.amazon.awssdk.services.ec2.model.IpPermission; +import software.amazon.awssdk.services.ec2.model.IpRange; +import software.amazon.awssdk.services.ec2.model.RebootInstancesRequest; +import software.amazon.awssdk.services.ec2.model.ReplaceIamInstanceProfileAssociationRequest; +import software.amazon.awssdk.services.ec2.model.SecurityGroup; +import software.amazon.awssdk.services.ec2.model.Subnet; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.model.AttachedPolicy; +import software.amazon.awssdk.services.iam.model.DeleteInstanceProfileRequest; +import software.amazon.awssdk.services.iam.model.DeletePolicyRequest; +import software.amazon.awssdk.services.iam.model.DeleteRoleRequest; +import software.amazon.awssdk.services.iam.model.DetachRolePolicyRequest; +import software.amazon.awssdk.services.iam.model.GetInstanceProfileResponse; +import software.amazon.awssdk.services.iam.model.IamException; +import software.amazon.awssdk.services.iam.model.ListAttachedRolePoliciesResponse; +import software.amazon.awssdk.services.iam.model.ListEntitiesForPolicyResponse; +import software.amazon.awssdk.services.iam.model.ListInstanceProfilesForRoleRequest; +import software.amazon.awssdk.services.iam.model.ListInstanceProfilesForRoleResponse; +import software.amazon.awssdk.services.iam.model.ListPoliciesRequest; +import software.amazon.awssdk.services.autoscaling.AutoScalingClient; +import software.amazon.awssdk.services.autoscaling.model.LaunchTemplateSpecification; +import software.amazon.awssdk.services.iam.model.ListPoliciesResponse; +import software.amazon.awssdk.services.iam.model.Policy; +import software.amazon.awssdk.services.iam.model.RemoveRoleFromInstanceProfileRequest; +import software.amazon.awssdk.services.ssm.SsmClient; +import software.amazon.awssdk.services.ssm.model.DescribeInstanceInformationResponse; +import software.amazon.awssdk.services.ssm.model.InstanceInformation; +import software.amazon.awssdk.services.ssm.model.SendCommandRequest; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +// snippet-start:[javav2.example_code.workflow.ResilientService_AutoScaler] +public class AutoScaler { + + private static Ec2Client ec2Client; + private static AutoScalingClient autoScalingClient; + private static IamClient iamClient; + + private static SsmClient ssmClient; + + private IamClient getIAMClient() { + if (iamClient == null) { + iamClient = IamClient.builder() + .region(Region.US_EAST_1) + .build(); + } + return iamClient; + } + + private SsmClient getSSMClient() { + if (ssmClient == null) { + ssmClient = SsmClient.builder() + .region(Region.US_EAST_1) + .build(); + } + return ssmClient; + } + + private Ec2Client getEc2Client() { + if (ec2Client == null) { + ec2Client = Ec2Client.builder() + .region(Region.US_EAST_1) + .build(); + } + return ec2Client; + } + + private AutoScalingClient getAutoScalingClient() { + if (autoScalingClient == null) { + autoScalingClient = AutoScalingClient.builder() + .region(Region.US_EAST_1) + .build(); + } + return autoScalingClient; + } + + // snippet-start:[javav2.cross_service.resilient_service.auto-scaling.DeleteAutoScalingGroup] + /** + * Terminates and instances in an EC2 Auto Scaling group. After an instance is + * terminated, it can no longer be accessed. + */ + public void terminateInstance(String instanceId) { + TerminateInstanceInAutoScalingGroupRequest terminateInstanceIRequest = TerminateInstanceInAutoScalingGroupRequest.builder() + .instanceId(instanceId) + .shouldDecrementDesiredCapacity(false) + .build(); + + getAutoScalingClient().terminateInstanceInAutoScalingGroup(terminateInstanceIRequest); + System.out.format("Terminated instance %s.", instanceId); + } + // snippet-end:[javav2.cross_service.resilient_service.auto-scaling.DeleteAutoScalingGroup] + + // snippet-start:[javav2.cross_service.resilient_service.ec2.ReplaceIamInstanceProfileAssociation] + /** + * Replaces the profile associated with a running instance. After the profile is + * replaced, the instance is rebooted to ensure that it uses the new profile. When + * the instance is ready, Systems Manager is used to restart the Python web server. + */ + public void replaceInstanceProfile(String instanceId, String newInstanceProfileName, String profileAssociationId) throws InterruptedException { + // Create an IAM instance profile specification. + software.amazon.awssdk.services.ec2.model.IamInstanceProfileSpecification iamInstanceProfile = software.amazon.awssdk.services.ec2.model.IamInstanceProfileSpecification.builder() + .name(newInstanceProfileName) // Make sure 'newInstanceProfileName' is a valid IAM Instance Profile name. + .build(); + + // Replace the IAM instance profile association for the EC2 instance. + ReplaceIamInstanceProfileAssociationRequest replaceRequest = ReplaceIamInstanceProfileAssociationRequest.builder() + .iamInstanceProfile(iamInstanceProfile) + .associationId(profileAssociationId) // Make sure 'profileAssociationId' is a valid association ID. + .build(); + + try { + getEc2Client().replaceIamInstanceProfileAssociation(replaceRequest); + // Handle the response as needed. + } catch (Ec2Exception e) { + // Handle exceptions, log, or report the error. + System.err.println("Error: " + e.getMessage()); + } + System.out.format("Replaced instance profile for association %s with profile %s.", profileAssociationId, newInstanceProfileName); + TimeUnit.SECONDS.sleep(15); + boolean instReady = false; + int tries = 0; + + // Reboot after 60 seconds + while (!instReady) { + if (tries % 6 == 0) { + getEc2Client().rebootInstances(RebootInstancesRequest.builder() + .instanceIds(instanceId) + .build()); + System.out.println("Rebooting instance " + instanceId + " and waiting for it to be ready."); + } + tries++; + try { + TimeUnit.SECONDS.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + DescribeInstanceInformationResponse informationResponse = getSSMClient().describeInstanceInformation(); + List instanceInformationList = informationResponse.instanceInformationList(); + for (InstanceInformation info : instanceInformationList) { + if (info.instanceId().equals(instanceId)) { + instReady = true; + break; + } + } + } + + SendCommandRequest sendCommandRequest = SendCommandRequest.builder() + .instanceIds(instanceId) + .documentName("AWS-RunShellScript") + .parameters(Collections.singletonMap("commands", Collections.singletonList("cd / && sudo python3 server.py 80"))) + .build(); + + getSSMClient().sendCommand(sendCommandRequest); + System.out.println("Restarted the Python web server on instance " + instanceId + "."); + } + // snippet-end:[javav2.cross_service.resilient_service.ec2.ReplaceIamInstanceProfileAssociation] + + + // snippet-start:[javav2.cross_service.resilient_service.ec2.AuthorizeSecurityGroupIngress] + public void openInboundPort(String secGroupId, String port, String ipAddress) { + AuthorizeSecurityGroupIngressRequest ingressRequest = AuthorizeSecurityGroupIngressRequest.builder() + .groupName(secGroupId) + .cidrIp(ipAddress) + .fromPort(Integer.parseInt(port)) + .build(); + + getEc2Client().authorizeSecurityGroupIngress(ingressRequest); + System.out.format("Authorized ingress to %s on port %s from %s.", secGroupId, port, ipAddress); + } + // snippet-end:[javav2.cross_service.resilient_service.ec2.AuthorizeSecurityGroupIngress] + + //snippet-start:[javav2.cross_service.resilient_service.iam.DeleteInstanceProfile] + /** + * Detaches a role from an instance profile, detaches policies from the role, + * and deletes all the resources. + */ + public void deleteInstanceProfile(String roleName, String profileName){ + try { + software.amazon.awssdk.services.iam.model.GetInstanceProfileRequest getInstanceProfileRequest = software.amazon.awssdk.services.iam.model.GetInstanceProfileRequest.builder() + .instanceProfileName(profileName) + .build(); + + GetInstanceProfileResponse response = getIAMClient().getInstanceProfile(getInstanceProfileRequest); + String name = response.instanceProfile().instanceProfileName(); + System.out.println(name); + + RemoveRoleFromInstanceProfileRequest profileRequest = RemoveRoleFromInstanceProfileRequest.builder() + .instanceProfileName(profileName) + .roleName(roleName) + .build(); + + getIAMClient().removeRoleFromInstanceProfile(profileRequest); + DeleteInstanceProfileRequest deleteInstanceProfileRequest = DeleteInstanceProfileRequest.builder() + .instanceProfileName(profileName) + .build(); + + getIAMClient().deleteInstanceProfile(deleteInstanceProfileRequest); + System.out.println("Deleted instance profile " + profileName); + + DeleteRoleRequest deleteRoleRequest = DeleteRoleRequest.builder() + .roleName(roleName) + .build(); + + // List attached role policies. + ListAttachedRolePoliciesResponse rolesResponse = getIAMClient().listAttachedRolePolicies(role -> role.roleName(roleName)); + List attachedPolicies = rolesResponse.attachedPolicies(); + for (AttachedPolicy attachedPolicy : attachedPolicies) { + DetachRolePolicyRequest request = DetachRolePolicyRequest.builder() + .roleName(roleName) + .policyArn(attachedPolicy.policyArn()) + .build(); + + getIAMClient().detachRolePolicy(request); + System.out.println("Detached and deleted policy " + attachedPolicy.policyName()); + } + + getIAMClient().deleteRole(deleteRoleRequest); + System.out.println("Instance profile and role deleted."); + + } catch (IamException e) { + System.err.println(e.getMessage()); + System.exit(1); + } + } + //snippet-end:[javav2.cross_service.resilient_service.iam.DeleteInstanceProfile] + + //snippet-start:[javav2.cross_service.resilient_service.ec2.DeleteLaunchTemplate] + public void deleteTemplate(String templateName) { + getEc2Client().deleteLaunchTemplate(name->name.launchTemplateName(templateName)); + System.out.format(templateName +" was deleted."); + } + //snippet-end:[javav2.cross_service.resilient_service.ec2.DeleteLaunchTemplate] + + + public void deleteAutoScaleGroup(String groupName) { + DeleteAutoScalingGroupRequest deleteAutoScalingGroupRequest = DeleteAutoScalingGroupRequest.builder() + .autoScalingGroupName(groupName) + .forceDelete(true) + .build(); + + getAutoScalingClient().deleteAutoScalingGroup(deleteAutoScalingGroupRequest); + System.out.println(groupName +" was deleted."); + } + + // snippet-start:[javav2.cross_service.resilient_service.ec2.DescribeSecurityGroups] + /* + Verify the default security group of the specified VPC allows ingress from this + computer. This can be done by allowing ingress from this computer's IP + address. In some situations, such as connecting from a corporate network, you + must instead specify a prefix list ID. You can also temporarily open the port to + any IP address while running this example. If you do, be sure to remove public + access when you're done. + + */ + public GroupInfo verifyInboundPort(String VPC, int port, String ipAddress) { + boolean portIsOpen = false; + GroupInfo groupInfo = new GroupInfo(); + try { + Filter filter = Filter.builder() + .name("group-name") + .values("default") + .build(); + + Filter filter1 = Filter.builder() + .name("vpc-id") + .values(VPC) + .build(); + + DescribeSecurityGroupsRequest securityGroupsRequest = DescribeSecurityGroupsRequest.builder() + .filters(filter, filter1) + .build(); + + DescribeSecurityGroupsResponse securityGroupsResponse = getEc2Client().describeSecurityGroups(securityGroupsRequest); + String securityGroup = securityGroupsResponse.securityGroups().get(0).groupName(); + groupInfo.setGroupName(securityGroup); + + for (SecurityGroup secGroup : securityGroupsResponse.securityGroups()) { + System.out.println("Found security group: " + secGroup.groupId()); + + for (IpPermission ipPermission : secGroup.ipPermissions()) { + if (ipPermission.fromPort() == port) { + System.out.println("Found inbound rule: " + ipPermission); + for (IpRange ipRange : ipPermission.ipRanges()) { + String cidrIp = ipRange.cidrIp(); + if (cidrIp.startsWith(ipAddress) || cidrIp.equals("0.0.0.0/0")) { + System.out.println(cidrIp +" is applicable"); + portIsOpen = true; + } + } + + if (!ipPermission.prefixListIds().isEmpty()) { + System.out.println("Prefix lList is applicable"); + portIsOpen = true; + } + + if (!portIsOpen) { + System.out.println("The inbound rule does not appear to be open to either this computer's IP," + + " all IP addresses (0.0.0.0/0), or to a prefix list ID."); + } else { + break; + } + } + } + } + + } catch (AutoScalingException e) { + System.err.println(e.awsErrorDetails().errorMessage()); + } + + groupInfo.setPortOpen(portIsOpen); + return groupInfo; + } + // snippet-end:[javav2.cross_service.resilient_service.ec2.DescribeSecurityGroups] + + // snippet-start:[javav2.cross_service.resilient_service.auto-scaling.AttachLoadBalancerTargetGroups] + /* + Attaches an Elastic Load Balancing (ELB) target group to this EC2 Auto Scaling group. + The target group specifies how the load balancer forward requests to the instances + in the group. + */ + public void attachLoadBalancerTargetGroup(String asGroupName, String targetGroupARN) { + try { + AttachLoadBalancerTargetGroupsRequest targetGroupsRequest = AttachLoadBalancerTargetGroupsRequest .builder() + .autoScalingGroupName(asGroupName) + .targetGroupARNs(targetGroupARN) + .build(); + + getAutoScalingClient().attachLoadBalancerTargetGroups(targetGroupsRequest); + System.out.println("Attached load balancer to "+asGroupName); + + } catch (AutoScalingException e) { + System.err.println(e.awsErrorDetails().errorMessage()); + System.exit(1); + } + } + // snippet-end:[javav2.cross_service.resilient_service.auto-scaling.AttachLoadBalancerTargetGroups] + + //snippet-start:[javav2.cross_service.resilient_service.auto-scaling.CreateAutoScalingGroup] + // Creates an EC2 Auto Scaling group with the specified size. + public String[] createGroup(int groupSize, String templateName, String autoScalingGroupName ) { + + // Get availability zones. + software.amazon.awssdk.services.ec2.model.DescribeAvailabilityZonesRequest zonesRequest = software.amazon.awssdk.services.ec2.model.DescribeAvailabilityZonesRequest.builder() + .build(); + + DescribeAvailabilityZonesResponse zonesResponse = getEc2Client().describeAvailabilityZones(zonesRequest); + List availabilityZoneNames = zonesResponse.availabilityZones().stream() + .map(software.amazon.awssdk.services.ec2.model.AvailabilityZone::zoneName) + .collect(Collectors.toList()); + + String availabilityZones = String.join(",", availabilityZoneNames); + LaunchTemplateSpecification specification = LaunchTemplateSpecification.builder() + .launchTemplateName(templateName) + .version("$Default") + .build(); + + String[] zones = availabilityZones.split(","); + CreateAutoScalingGroupRequest groupRequest = CreateAutoScalingGroupRequest.builder() + .launchTemplate(specification) + .availabilityZones(zones) + .maxSize(groupSize) + .minSize(groupSize) + .autoScalingGroupName(autoScalingGroupName) + .build(); + + try { + getAutoScalingClient().createAutoScalingGroup(groupRequest); + + } catch (AutoScalingException e) { + System.err.println(e.awsErrorDetails().errorMessage()); + System.exit(1); + } + System.out.println("Created an EC2 Auto Scaling group named "+ autoScalingGroupName); + return zones; + } + //snippet-end:[javav2.cross_service.resilient_service.auto-scaling.CreateAutoScalingGroup] + + public String getDefaultVPC() { + // Define the filter. + Filter defaultFilter = Filter.builder() + .name("is-default") + .values("true") + .build(); + + software.amazon.awssdk.services.ec2.model.DescribeVpcsRequest request = software.amazon.awssdk.services.ec2.model.DescribeVpcsRequest.builder() + .filters(defaultFilter) + .build(); + + DescribeVpcsResponse response = getEc2Client().describeVpcs(request); + return response.vpcs().get(0).vpcId(); + } + + // Gets the default subnets in a VPC for a specified list of Availability Zones. + public List getSubnets(String vpcId, String[] availabilityZones) { + List subnets = null; + Filter vpcFilter = Filter.builder() + .name("vpc-id") + .values(vpcId) + .build(); + + Filter azFilter = Filter.builder() + .name("availability-zone") + .values(availabilityZones) + .build(); + + Filter defaultForAZ = Filter.builder() + .name("default-for-az") + .values("true") + .build(); + + DescribeSubnetsRequest request = DescribeSubnetsRequest.builder() + .filters(vpcFilter, azFilter, defaultForAZ) + .build(); + + DescribeSubnetsResponse response = getEc2Client().describeSubnets(request); + subnets = response.subnets(); + return subnets; + } + + // Gets data about the instances in the EC2 Auto Scaling group. + public String getBadInstance(String groupName) { + DescribeAutoScalingGroupsRequest request = DescribeAutoScalingGroupsRequest.builder() + .autoScalingGroupNames(groupName) + .build(); + + DescribeAutoScalingGroupsResponse response = getAutoScalingClient().describeAutoScalingGroups(request); + AutoScalingGroup autoScalingGroup = response.autoScalingGroups().get(0); + List instanceIds = autoScalingGroup.instances().stream() + .map(instance -> instance.instanceId()) + .collect(Collectors.toList()); + + String[] instanceIdArray = instanceIds.toArray(new String[0]); + for (String instanceId : instanceIdArray) { + System.out.println("Instance ID: " + instanceId); + return instanceId; + } + return ""; + } + + // snippet-start:[javav2.cross_service.resilient_service.ec2.DescribeIamInstanceProfileAssociations] + // Gets data about the profile associated with an instance. + public String getInstanceProfile(String instanceId) { + Filter filter = Filter.builder() + .name("instance-id") + .values(instanceId) + .build(); + + DescribeIamInstanceProfileAssociationsRequest associationsRequest = DescribeIamInstanceProfileAssociationsRequest.builder() + .filters(filter) + .build(); + + DescribeIamInstanceProfileAssociationsResponse response = getEc2Client().describeIamInstanceProfileAssociations(associationsRequest); + return response.iamInstanceProfileAssociations().get(0).associationId(); + } + // snippet-end:[javav2.cross_service.resilient_service.ec2.DescribeIamInstanceProfileAssociations] + + public void deleteRolesPolicies(String policyName, String roleName, String InstanceProfile ) { + ListPoliciesRequest listPoliciesRequest = ListPoliciesRequest.builder().build(); + ListPoliciesResponse listPoliciesResponse = getIAMClient().listPolicies(listPoliciesRequest); + for (Policy policy : listPoliciesResponse.policies()) { + if (policy.policyName().equals(policyName)) { + // List the entities (users, groups, roles) that are attached to the policy. + software.amazon.awssdk.services.iam.model.ListEntitiesForPolicyRequest listEntitiesRequest = software.amazon.awssdk.services.iam.model.ListEntitiesForPolicyRequest.builder() + .policyArn(policy.arn()) + .build(); + ListEntitiesForPolicyResponse listEntitiesResponse = iamClient.listEntitiesForPolicy(listEntitiesRequest); + if (!listEntitiesResponse.policyGroups().isEmpty() || !listEntitiesResponse.policyUsers().isEmpty() || !listEntitiesResponse.policyRoles().isEmpty()) { + // Detach the policy from any entities it is attached to. + DetachRolePolicyRequest detachPolicyRequest = DetachRolePolicyRequest.builder() + .policyArn(policy.arn()) + .roleName(roleName) // Specify the name of the IAM role + .build(); + + getIAMClient().detachRolePolicy(detachPolicyRequest); + System.out.println("Policy detached from entities."); + } + + // Now, you can delete the policy. + DeletePolicyRequest deletePolicyRequest = DeletePolicyRequest.builder() + .policyArn(policy.arn()) + .build(); + + getIAMClient().deletePolicy(deletePolicyRequest); + System.out.println("Policy deleted successfully."); + break; + } + } + + // List the roles associated with the instance profile + ListInstanceProfilesForRoleRequest listRolesRequest = ListInstanceProfilesForRoleRequest .builder() + .roleName(roleName) + .build(); + + // Detach the roles from the instance profile + ListInstanceProfilesForRoleResponse listRolesResponse = iamClient.listInstanceProfilesForRole(listRolesRequest); + for (software.amazon.awssdk.services.iam.model.InstanceProfile profile : listRolesResponse.instanceProfiles()) { + RemoveRoleFromInstanceProfileRequest removeRoleRequest = RemoveRoleFromInstanceProfileRequest.builder() + .instanceProfileName(InstanceProfile) + .roleName(roleName) // Remove the extra dot here + .build(); + + getIAMClient().removeRoleFromInstanceProfile(removeRoleRequest); + System.out.println("Role " + roleName + " removed from instance profile " + InstanceProfile); + } + + // Delete the instance profile after removing all roles + DeleteInstanceProfileRequest deleteInstanceProfileRequest = DeleteInstanceProfileRequest.builder() + .instanceProfileName(InstanceProfile) + .build(); + + + getIAMClient().deleteInstanceProfile(r->r.instanceProfileName(InstanceProfile)); + System.out.println(InstanceProfile +" Deleted"); + System.out.println("All roles and policies are deleted."); + } +} +// snippet-end:[javav2.example_code.workflow.ResilientService_AutoScaler] diff --git a/javav2/usecases/resilient_service/src/main/java/com/example/resilient/Database.java b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/Database.java new file mode 100644 index 00000000000..5bec75249a9 --- /dev/null +++ b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/Database.java @@ -0,0 +1,153 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +package com.example.resilient; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import software.amazon.awssdk.core.waiters.WaiterResponse; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; +import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; +import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest; +import software.amazon.awssdk.services.dynamodb.model.DescribeTableResponse; +import software.amazon.awssdk.services.dynamodb.model.DynamoDbException; +import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement; +import software.amazon.awssdk.services.dynamodb.model.KeyType; +import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; +import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException; +import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; +import software.amazon.awssdk.services.dynamodb.waiters.DynamoDbWaiter; +import java.io.File; +import java.io.IOException; + +// snippet-start:[javav2.example_code.workflow.ResilientService_RecommendationService] +public class Database { + + private static DynamoDbClient dynamoDbClient; + + public static DynamoDbClient getDynamoDbClient() { + if (dynamoDbClient == null) { + dynamoDbClient = DynamoDbClient.builder() + .region(Region.US_EAST_1) + .build(); + } + return dynamoDbClient; + } + + // Checks to see if the Amazon DynamoDB table exists. + private boolean doesTableExist(String tableName){ + try { + // Describe the table and catch any exceptions. + DescribeTableRequest describeTableRequest = DescribeTableRequest.builder() + .tableName(tableName) + .build(); + + getDynamoDbClient().describeTable(describeTableRequest); + System.out.println("Table '" + tableName + "' exists."); + return true; + + } catch (ResourceNotFoundException e) { + System.out.println("Table '" + tableName + "' does not exist."); + } catch (DynamoDbException e) { + System.err.println("Error checking table existence: " + e.getMessage()); + } + return false; + } + + /* + Creates a DynamoDB table to use a recommendation service. The table has a + hash key named 'MediaType' that defines the type of media recommended, such as + Book or Movie, and a range key named 'ItemId' that, combined with the MediaType, + forms a unique identifier for the recommended item. + */ + public void createTable(String tableName, String fileName) throws IOException { + // First check to see if the table exists. + boolean doesExist = doesTableExist(tableName); + if (!doesExist) { + DynamoDbWaiter dbWaiter = getDynamoDbClient().waiter(); + CreateTableRequest createTableRequest = CreateTableRequest.builder() + .tableName(tableName) + .attributeDefinitions( + AttributeDefinition.builder() + .attributeName("MediaType") + .attributeType(ScalarAttributeType.S) + .build(), + AttributeDefinition.builder() + .attributeName("ItemId") + .attributeType(ScalarAttributeType.N) + .build()) + .keySchema( + KeySchemaElement.builder() + .attributeName("MediaType") + .keyType(KeyType.HASH) + .build(), + KeySchemaElement.builder() + .attributeName("ItemId") + .keyType(KeyType.RANGE) + .build()) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(5L) + .writeCapacityUnits(5L) + .build()) + .build(); + + getDynamoDbClient().createTable(createTableRequest); + System.out.println("Creating table " + tableName + "..."); + + // Wait until the Amazon DynamoDB table is created. + DescribeTableRequest tableRequest = DescribeTableRequest.builder() + .tableName(tableName) + .build(); + + WaiterResponse waiterResponse = dbWaiter.waitUntilTableExists(tableRequest); + waiterResponse.matched().response().ifPresent(System.out::println); + System.out.println("Table " + tableName + " created."); + + // Add records to the table. + populateTable(fileName, tableName); + } + } + + public void deleteTable(String tableName) { + getDynamoDbClient().deleteTable(table->table.tableName(tableName)); + System.out.println("Table " + tableName + " deleted."); + } + + // Populates the table with data located in a JSON file using the DynamoDB enhanced client. + public void populateTable(String fileName, String tableName) throws IOException { + DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder() + .dynamoDbClient(getDynamoDbClient()) + .build(); + ObjectMapper objectMapper = new ObjectMapper(); + File jsonFile = new File(fileName); + JsonNode rootNode = objectMapper.readTree(jsonFile); + + DynamoDbTable mappedTable = enhancedClient.table(tableName, TableSchema.fromBean(Recommendation.class)); + for (JsonNode currentNode : rootNode) { + String mediaType = currentNode.path("MediaType").path("S").asText(); + int itemId = currentNode.path("ItemId").path("N").asInt(); + String title = currentNode.path("Title").path("S").asText(); + String creator = currentNode.path("Creator").path("S").asText(); + + // Create a Recommendation object and set its properties. + Recommendation rec = new Recommendation(); + rec.setMediaType(mediaType); + rec.setItemId(itemId); + rec.setTitle(title); + rec.setCreator(creator); + + // Put the item into the DynamoDB table. + mappedTable.putItem(rec); // Add the Recommendation to the list. + } + System.out.println("Added all records to the "+tableName); + } +} +// snippet-end:[javav2.example_code.workflow.ResilientService_RecommendationService] \ No newline at end of file diff --git a/javav2/usecases/resilient_service/src/main/java/com/example/resilient/GroupInfo.java b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/GroupInfo.java new file mode 100644 index 00000000000..0d6eb552037 --- /dev/null +++ b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/GroupInfo.java @@ -0,0 +1,39 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +package com.example.resilient; + +public class GroupInfo { + + private String groupName; + private boolean portOpen = false; + + public GroupInfo() { + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public boolean isPortOpen() { + return portOpen; + } + + public void setPortOpen(boolean portOpen) { + this.portOpen = portOpen; + } + + @Override + public String toString() { + return "GroupInfo{" + + "groupName='" + groupName + '\'' + + ", postOpen=" + portOpen + + '}'; + } +} diff --git a/javav2/usecases/resilient_service/src/main/java/com/example/resilient/HelloLoadBalancer.java b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/HelloLoadBalancer.java new file mode 100644 index 00000000000..41f227ac183 --- /dev/null +++ b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/HelloLoadBalancer.java @@ -0,0 +1,40 @@ +// snippet-sourcedescription:[DisplayFacesFrame.java demonstrates how to retrieve load balancers or all of your load balancers.] +//snippet-keyword:[AWS SDK for Java v2] +// snippet-service:[Elastic Load Balancing] + +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +package com.example.resilient; + +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.elasticloadbalancingv2.ElasticLoadBalancingV2Client; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeLoadBalancersResponse; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.LoadBalancer; +import java.util.List; + +/** + * Before running this Java V2 code example, set up your development environment, including your credentials. + * + * For more information, see the following documentation topic: + * + * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html + */ + +// snippet-start:[javav2.example_code.elbv2.Hello] +public class HelloLoadBalancer { + + public static void main(String[]args){ + ElasticLoadBalancingV2Client loadBalancingV2Client = ElasticLoadBalancingV2Client.builder() + .region(Region.US_EAST_1) + .build(); + + DescribeLoadBalancersResponse loadBalancersResponse = loadBalancingV2Client.describeLoadBalancers(r->r.pageSize(10)); + List loadBalancerList = loadBalancersResponse.loadBalancers(); + for (LoadBalancer lb : loadBalancerList) + System.out.println("Load Balancer DNS name = " + lb.dnsName()); + } +} +// snippet-end:[javav2.example_code.elbv2.Hello] diff --git a/javav2/usecases/resilient_service/src/main/java/com/example/resilient/LaunchTemplateCreator.java b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/LaunchTemplateCreator.java new file mode 100644 index 00000000000..bd6bbcb73e2 --- /dev/null +++ b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/LaunchTemplateCreator.java @@ -0,0 +1,277 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +package com.example.resilient; + +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.CreateLaunchTemplateRequest; +import software.amazon.awssdk.services.ec2.model.CreateLaunchTemplateResponse; +import software.amazon.awssdk.services.ec2.model.Ec2Exception; +import software.amazon.awssdk.services.ec2.model.InstanceType; +import software.amazon.awssdk.services.ec2.model.LaunchTemplate; +import software.amazon.awssdk.services.ec2.model.LaunchTemplateIamInstanceProfileSpecificationRequest; +import software.amazon.awssdk.services.ec2.model.RequestLaunchTemplateData; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.model.AddRoleToInstanceProfileRequest; +import software.amazon.awssdk.services.iam.model.AttachRolePolicyRequest; +import software.amazon.awssdk.services.iam.model.CreateInstanceProfileRequest; +import software.amazon.awssdk.services.iam.model.CreatePolicyRequest; +import software.amazon.awssdk.services.iam.model.CreatePolicyResponse; +import software.amazon.awssdk.services.iam.model.CreateRoleRequest; +import software.amazon.awssdk.services.iam.model.EntityAlreadyExistsException; +import software.amazon.awssdk.services.iam.model.GetInstanceProfileRequest; +import software.amazon.awssdk.services.iam.model.GetInstanceProfileResponse; +import software.amazon.awssdk.services.iam.model.GetRoleRequest; +import software.amazon.awssdk.services.iam.model.IamException; +import software.amazon.awssdk.services.iam.model.ListPoliciesRequest; +import software.amazon.awssdk.services.iam.model.ListPoliciesResponse; +import software.amazon.awssdk.services.iam.model.NoSuchEntityException; +import software.amazon.awssdk.services.iam.model.Policy; +import software.amazon.awssdk.services.iam.model.PolicyScopeType; +import software.amazon.awssdk.services.ssm.SsmClient; +import software.amazon.awssdk.services.ssm.model.GetParameterRequest; +import software.amazon.awssdk.services.ssm.model.GetParameterResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Base64; +import java.util.List; + +public class LaunchTemplateCreator { + + private static Ec2Client ec2Client; + private static SsmClient ssmClient; + private static IamClient iamClient; + + private Ec2Client getEc2Client() { + if (ec2Client == null) { + ec2Client = Ec2Client.builder() + .region(Region.US_EAST_1) + .build(); + } + return ec2Client; + } + + private static IamClient getIAMClient() { + if (iamClient == null) { + iamClient = IamClient.builder() + .region(Region.US_EAST_1) + .build(); + } + return iamClient; + } + + private SsmClient getSSMClient() { + if (ssmClient == null) { + ssmClient = SsmClient.builder() + .region(Region.US_EAST_1) + .build(); + } + return ssmClient; + } + + // snippet-start:[javav2.cross_service.resilient_service.ec2.CreateLaunchTemplate] + public void createTemplate(String policyFile, String policyName, String profileName, String startScript, String templateName, String roleName) { + String profileArn = createInstanceProfile(policyFile, policyName, profileName, roleName); + String amiId = getLatestAmazonLinuxAmiId(); + + try { + String userData = getBase64EncodedUserData(startScript); + LaunchTemplateIamInstanceProfileSpecificationRequest specification = LaunchTemplateIamInstanceProfileSpecificationRequest.builder() + .arn(profileArn) + .build(); + + RequestLaunchTemplateData templateData = RequestLaunchTemplateData.builder() + .instanceType(InstanceType.T3_MICRO) // Replace with your desired instance type. + .imageId(amiId) + .iamInstanceProfile(specification) + .userData(userData) + .build(); + + CreateLaunchTemplateRequest templateRequest = CreateLaunchTemplateRequest.builder() + .launchTemplateName(templateName) + .launchTemplateData(templateData) + .build(); + + CreateLaunchTemplateResponse templateResponse = getEc2Client().createLaunchTemplate(templateRequest); + LaunchTemplate template = templateResponse.launchTemplate(); + System.out.println("\nCreated launch template named " + template.launchTemplateName()); + + } catch (Ec2Exception e) { + System.out.println("An exception occurred "+e.getMessage()); + } + } + // snippet-end:[javav2.cross_service.resilient_service.ec2.CreateLaunchTemplate] + + // snippet-start:[javav2.cross_service.resilient_service.iam.CreateInstanceProfile] + public String createInstanceProfile(String policyFile, String policyName, String profileName, String roleName) { + boolean instacneProfileExists = false; + String assumeRoleDoc = """ + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "ec2.amazonaws.com"}, + "Action": "sts:AssumeRole" + } + ] + }"""; + + // Read the policy document for the role + String instancePolicyDoc = ""; + try { + instancePolicyDoc = Files.readString(Paths.get(policyFile)); + } catch (IOException e) { + e.printStackTrace(); + } + + // Checks if the policy exists, if not creates it. + String policyArn = createPolicy(policyName, instancePolicyDoc); + + // Checks if the role exists, if not creates it. + if (!createRoleExists(roleName, assumeRoleDoc)) { + + // Attach role policy. + AttachRolePolicyRequest attachRequest = AttachRolePolicyRequest.builder() + .roleName(roleName) + .policyArn(policyArn) + .build(); + + getIAMClient().attachRolePolicy(attachRequest); + } else { + System.out.printf("Role %s already exists, nothing to do.", roleName); + } + + try { + CreateInstanceProfileRequest instanceProfileRequest = CreateInstanceProfileRequest.builder() + .instanceProfileName(profileName) + .build(); + + getIAMClient().createInstanceProfile(instanceProfileRequest); + + } catch (EntityAlreadyExistsException e){ + // This exception is thrown if the instance profile exists. + System.out.println(profileName + "already exists - moving on"); + instacneProfileExists = true; + } + + String profileArn = getInstanceProfile(profileName); + + // Only call addRoleToInstanceProfile if it's new instance profile. + if (!instacneProfileExists) { + AddRoleToInstanceProfileRequest profileRequest = AddRoleToInstanceProfileRequest.builder() + .instanceProfileName(profileName) + .roleName(roleName) + .build(); + + getIAMClient().addRoleToInstanceProfile(profileRequest); + System.out.printf("Created profile %s and added role %s.", profileName, roleName); + } + return profileArn; + } + // snippet-end:[javav2.cross_service.resilient_service.iam.CreateInstanceProfile] + + private String getInstanceProfile(String profileName) { + GetInstanceProfileRequest profileRequest = GetInstanceProfileRequest.builder() + .instanceProfileName(profileName) + .build(); + + GetInstanceProfileResponse resp = getIAMClient().getInstanceProfile(profileRequest); + return resp.instanceProfile().arn(); + + } + + public String createPolicy(String policyName, String policyDocument) { + String policyArn; + // Determine if policy exists already. + policyArn = checkPolicyExists(policyName); + if (policyArn.isEmpty()) { + try { + CreatePolicyRequest policyRequest = CreatePolicyRequest.builder() + .policyName(policyName) + .policyDocument(policyDocument) + .build(); + + CreatePolicyResponse policyResponse = getIAMClient().createPolicy(policyRequest); + policyArn = policyResponse.policy().arn(); + System.out.println("Created policy with ARN " + policyArn); + + } catch (IamException e) { + System.out.println("Error creating IAM policy: " + e.getMessage()); + } + } + + return policyArn; + } + + public String checkPolicyExists(String polName) { + String polARN = ""; + ListPoliciesRequest policiesRequest = ListPoliciesRequest.builder() + .scope(PolicyScopeType.LOCAL) + .build(); + + ListPoliciesResponse policiesResponse = getIAMClient().listPolicies(policiesRequest); + List policyList = policiesResponse.policies(); + for (Policy pol: policyList) { + if (pol.policyName().compareTo(polName)==0) { + return pol.arn(); + } + } + return ""; // Pol does not exist + } + + private boolean doesRoleExist(String roleName) { + GetRoleRequest request = GetRoleRequest.builder() + .roleName(roleName) + .build(); + + try { + getIAMClient().getRole(request); + return true; + } catch (NoSuchEntityException e) { + return false; + } + } + + private boolean createRoleExists(String roleName, String assumeRoleDoc) { + if (!doesRoleExist(roleName)) { + CreateRoleRequest roleRequest = CreateRoleRequest.builder() + .roleName(roleName) + .assumeRolePolicyDocument(assumeRoleDoc) + .build(); + + getIAMClient().createRole(roleRequest); + System.out.println(roleName + " created"); + return false; + } else { + System.out.println(roleName + " exists"); + return true; + } + } + + public String getLatestAmazonLinuxAmiId() { + String parameterName = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"; + GetParameterRequest parameterRequest = GetParameterRequest.builder() + .name(parameterName) + .build(); + + GetParameterResponse parameterResponse = getSSMClient().getParameter(parameterRequest); + return parameterResponse.parameter().value(); + } + + public String getBase64EncodedUserData(String scriptPath) { + try { + String scriptContent = new String(Files.readAllBytes(Paths.get(scriptPath)), StandardCharsets.UTF_8); + byte[] encodedBytes = Base64.getEncoder().encode(scriptContent.getBytes(StandardCharsets.UTF_8)); + return new String(encodedBytes, StandardCharsets.UTF_8); + } catch (IOException e) { + e.printStackTrace(); + return ""; + } + } +} diff --git a/javav2/usecases/resilient_service/src/main/java/com/example/resilient/LoadBalancer.java b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/LoadBalancer.java new file mode 100644 index 00000000000..a51f2e3c1c6 --- /dev/null +++ b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/LoadBalancer.java @@ -0,0 +1,229 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +package com.example.resilient; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import software.amazon.awssdk.core.waiters.WaiterResponse; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ec2.model.Subnet; +import software.amazon.awssdk.services.elasticloadbalancingv2.ElasticLoadBalancingV2Client; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.Action; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.CreateListenerRequest; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.CreateLoadBalancerRequest; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.CreateLoadBalancerResponse; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.CreateTargetGroupRequest; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.CreateTargetGroupResponse; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeLoadBalancersRequest; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeLoadBalancersResponse; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTargetGroupsRequest; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTargetGroupsResponse; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTargetHealthRequest; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTargetHealthResponse; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.ElasticLoadBalancingV2Exception; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.TargetHealthDescription; +import software.amazon.awssdk.services.elasticloadbalancingv2.waiters.ElasticLoadBalancingV2Waiter; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +// snippet-start:[javav2.example_code.workflow.ResilientService_LoadBalancer] +public class LoadBalancer { + public ElasticLoadBalancingV2Client elasticLoadBalancingV2Client; + + public ElasticLoadBalancingV2Client getLoadBalancerClient() { + if (elasticLoadBalancingV2Client == null) { + elasticLoadBalancingV2Client = ElasticLoadBalancingV2Client.builder() + .region(Region.US_EAST_1) + .build(); + } + + return elasticLoadBalancingV2Client; + } + + // snippet-start:[javav2.cross_service.resilient_service.elbv2.DescribeTargetHealth] + // Checks the health of the instances in the target group. + public List checkTargetHealth(String targetGroupName) { + DescribeTargetGroupsRequest targetGroupsRequest = DescribeTargetGroupsRequest.builder() + .names(targetGroupName) + .build(); + + DescribeTargetGroupsResponse tgResponse = getLoadBalancerClient().describeTargetGroups(targetGroupsRequest); + + DescribeTargetHealthRequest healthRequest = DescribeTargetHealthRequest.builder() + .targetGroupArn(tgResponse.targetGroups().get(0).targetGroupArn()) + .build(); + + DescribeTargetHealthResponse healthResponse = getLoadBalancerClient().describeTargetHealth(healthRequest); + return healthResponse.targetHealthDescriptions(); + } + // snippet-end:[javav2.cross_service.resilient_service.elbv2.DescribeTargetHealth] + + + // Gets the HTTP endpoint of the load balancer. + public String getEndpoint(String lbName){ + DescribeLoadBalancersResponse res = getLoadBalancerClient().describeLoadBalancers(describe -> describe.names(lbName)); + return res.loadBalancers().get(0).dnsName(); + } + + // snippet-start:[javav2.cross_service.resilient_service.elbv2.DeleteLoadBalancer] + // Deletes a load balancer. + public void deleteLoadBalancer(String lbName) { + try { + // Use a waiter to delete the Load Balancer. + DescribeLoadBalancersResponse res = getLoadBalancerClient().describeLoadBalancers(describe -> describe.names(lbName)); + ElasticLoadBalancingV2Waiter loadBalancerWaiter = getLoadBalancerClient().waiter(); + DescribeLoadBalancersRequest request = DescribeLoadBalancersRequest.builder() + .loadBalancerArns(res.loadBalancers().get(0).loadBalancerArn()) + .build(); + + getLoadBalancerClient().deleteLoadBalancer(builder -> builder.loadBalancerArn(res.loadBalancers().get(0).loadBalancerArn())); + WaiterResponse waiterResponse = loadBalancerWaiter.waitUntilLoadBalancersDeleted(request); + waiterResponse.matched().response().ifPresent(System.out::println); + + } catch (ElasticLoadBalancingV2Exception e) { + System.err.println(e.awsErrorDetails().errorMessage()); + } + System.out.println(lbName +" was deleted."); + } + // snippet-end:[javav2.cross_service.resilient_service.elbv2.DeleteLoadBalancer] + + // snippet-start:[javav2.cross_service.resilient_service.elbv2.DeleteTargetGroup] + // Deletes the target group. + public void deleteTargetGroup(String targetGroupName) { + try { + DescribeTargetGroupsResponse res = getLoadBalancerClient().describeTargetGroups(describe -> describe.names(targetGroupName)); + getLoadBalancerClient().deleteTargetGroup(builder -> builder.targetGroupArn(res.targetGroups().get(0).targetGroupArn())); + } catch (ElasticLoadBalancingV2Exception e) { + System.err.println(e.awsErrorDetails().errorMessage()); + } + System.out.println(targetGroupName +" was deleted."); + } + // snippet-end:[javav2.cross_service.resilient_service.elbv2.DeleteTargetGroup] + + // Verify this computer can successfully send a GET request to the load balancer endpoint. + public boolean verifyLoadBalancerEndpoint(String elbDnsName) throws IOException, InterruptedException { + boolean success = false; + int retries = 3; + CloseableHttpClient httpClient = HttpClients.createDefault(); + + // Create an HTTP GET request to the ELB. + HttpGet httpGet = new HttpGet("http://" + elbDnsName); + try { + while ((!success) && (retries > 0)) { + // Execute the request and get the response. + HttpResponse response = httpClient.execute(httpGet); + int statusCode = response.getStatusLine().getStatusCode(); + System.out.println("HTTP Status Code: " + statusCode); + if (statusCode == 200) { + success = true ; + } else { + retries-- ; + System.out.println("Got connection error from load balancer endpoint, retrying..."); + TimeUnit.SECONDS.sleep(15); + } + } + + } catch (org.apache.http.conn.HttpHostConnectException e) { + System.out.println(e.getMessage()); + } + + System.out.println("Status.." + success); + return success; + } + + // snippet-start:[javav2.cross_service.resilient_service.elbv2.CreateTargetGroup] + /* + Creates an Elastic Load Balancing target group. The target group specifies how + the load balancer forward requests to instances in the group and how instance + health is checked. + */ + public String createTargetGroup(String protocol, int port, String vpcId, String targetGroupName) { + CreateTargetGroupRequest targetGroupRequest = CreateTargetGroupRequest.builder() + .healthCheckPath("/healthcheck") + .healthCheckTimeoutSeconds(5) + .port(port) + .vpcId(vpcId) + .name(targetGroupName) + .protocol(protocol) + .build(); + + CreateTargetGroupResponse targetGroupResponse = getLoadBalancerClient().createTargetGroup(targetGroupRequest); + String targetGroupArn = targetGroupResponse.targetGroups().get(0).targetGroupArn(); + String targetGroup = targetGroupResponse.targetGroups().get(0).targetGroupName(); + System.out.println("The " + targetGroup + " was created with ARN" + targetGroupArn); + return targetGroupArn; + } + // snippet-end:[javav2.cross_service.resilient_service.elbv2.CreateTargetGroup] + + // snippet-start:[javav2.cross_service.resilient_service.elbv2.CreateLoadBalancer] + // snippet-start:[javav2.cross_service.resilient_service.elbv2.CreateListener] + /* + Creates an Elastic Load Balancing load balancer that uses the specified subnets + and forwards requests to the specified target group. + */ + public String createLoadBalancer(List subnetIds, String targetGroupARN, String lbName, int port, String protocol) { + try { + List subnetIdStrings = subnetIds.stream() + .map(Subnet::subnetId) + .collect(Collectors.toList()); + + CreateLoadBalancerRequest balancerRequest = CreateLoadBalancerRequest.builder() + .subnets(subnetIdStrings) + .name(lbName) + .scheme("internet-facing") + .build(); + + // Create and wait for the load balancer to become available. + CreateLoadBalancerResponse lsResponse = getLoadBalancerClient().createLoadBalancer(balancerRequest); + String lbARN = lsResponse.loadBalancers().get(0).loadBalancerArn(); + + ElasticLoadBalancingV2Waiter loadBalancerWaiter = getLoadBalancerClient().waiter(); + DescribeLoadBalancersRequest request = DescribeLoadBalancersRequest.builder() + .loadBalancerArns(lbARN) + .build(); + + System.out.println("Waiting for Load Balancer " + lbName + " to become available."); + WaiterResponse waiterResponse = loadBalancerWaiter.waitUntilLoadBalancerAvailable(request); + waiterResponse.matched().response().ifPresent(System.out::println); + System.out.println("Load Balancer " + lbName + " is available."); + + // Get the DNS name (endpoint) of the load balancer. + String lbDNSName = lsResponse.loadBalancers().get(0).dnsName(); + System.out.println("*** Load Balancer DNS Name: " + lbDNSName); + + // Create a listener for the load balance. + Action action = Action.builder() + .targetGroupArn(targetGroupARN) + .type("forward") + .build(); + + CreateListenerRequest listenerRequest = CreateListenerRequest.builder() + .loadBalancerArn(lsResponse.loadBalancers().get(0).loadBalancerArn()) + .defaultActions(action) + .port(port) + .protocol(protocol) + .defaultActions(action) + .build(); + + getLoadBalancerClient().createListener(listenerRequest); + System.out.println( "Created listener to forward traffic from load balancer " + lbName + " to target group " + targetGroupARN); + + // Return the load balancer DNS name. + return lbDNSName; + + } catch (ElasticLoadBalancingV2Exception e) { + e.printStackTrace(); + } + return ""; + } + // snippet-end:[javav2.cross_service.resilient_service.elbv2.CreateListener] + // snippet-end:[javav2.cross_service.resilient_service.elbv2.CreateLoadBalancer] +} +// snippet-end:[javav2.example_code.workflow.ResilientService_LoadBalancer] \ No newline at end of file diff --git a/javav2/usecases/resilient_service/src/main/java/com/example/resilient/Main.java b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/Main.java new file mode 100644 index 00000000000..b4bbba6ecf7 --- /dev/null +++ b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/Main.java @@ -0,0 +1,465 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +package com.example.resilient; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.Scanner; +import java.util.concurrent.TimeUnit; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import software.amazon.awssdk.services.autoscaling.model.AutoScalingException; +import software.amazon.awssdk.services.ec2.model.Subnet; +import software.amazon.awssdk.services.elasticloadbalancingv2.model.TargetHealthDescription; +/** + * Before running this Java V2 code example, set up your development environment, including your credentials. + * + * For more information, see the following documentation topic: + * + * ... + * + * In addition, set these values: + * + * 1. fileName - The location of the recommendations.json (you can locate this file in python/cross_service/resilient_service). + * 2. tableName - The name of the Amazon DynamoDB table. + * 3. startScript - The location of the server_startup_script.sh script (you can locate this file in workflows/resilient_service/resources). + * 4. policyFile - the location of the instance_policy.json (you can locate this file in workflows/resilient_service/resources). + * 5. ssmJSON - the location of the ssm_only_policy.json (you can locate this file in workflows/resilient_service/resources). + * 6. templateName - The name of the template. + * 7. roleName - The name of the role. + * 8. policyName - The name of the policy. + * 9. profileName - The name of the profile. + * 10. targetGroupName - The name of the target group. + * 11. autoScalingGroupName - The name of the auto-scaling group. + * 12. lbName - The name of the load balancer. + */ + + +// snippet-start:[javav2.example_code.workflow.ResilientService_Runner] +public class Main { + + public static final String fileName = "C:\\AWS\\resworkflow\\recommendations.json"; // Modify file location. + public static final String tableName = "doc-example-recommendation-service"; + public static final String startScript = "C:\\AWS\\resworkflow\\server_startup_script.sh"; // Modify file location. + public static final String policyFile = "C:\\AWS\\resworkflow\\instance_policy.json"; // Modify file location. + public static final String ssmJSON = "C:\\AWS\\resworkflow\\ssm_only_policy.json"; // Modify file location. + public static final String failureResponse = "doc-example-resilient-architecture-failure-response"; + public static final String healthCheck = "doc-example-resilient-architecture-health-check"; + public static final String templateName = "doc-example-resilience-template" ; + public static final String roleName = "doc-example-resilience-role"; + public static final String policyName = "doc-example-resilience-pol"; + public static final String profileName ="doc-example-resilience-prof" ; + + public static final String badCredsProfileName ="doc-example-resilience-prof-bc" ; + + public static final String targetGroupName = "doc-example-resilience-tg" ; + public static final String autoScalingGroupName = "doc-example-resilience-group"; + public static final String lbName = "doc-example-resilience-lb" ; + public static final String protocol = "HTTP" ; + public static final int port = 80 ; + + public static final String DASHES = new String(new char[80]).replace("\0", "-"); + public static void main(String[] args) throws IOException, InterruptedException { + Scanner in = new Scanner(System.in); + Database database = new Database(); + AutoScaler autoScaler = new AutoScaler(); + LoadBalancer loadBalancer = new LoadBalancer(); + + System.out.println(DASHES); + System.out.println("Welcome to the demonstration of How to Build and Manage a Resilient Service!"); + System.out.println(DASHES); + + System.out.println(DASHES); + System.out.println("A - SETUP THE RESOURCES"); + System.out.println("Press Enter when you're ready to start deploying resources."); + in.nextLine(); + deploy(loadBalancer); + System.out.println(DASHES); + System.out.println(DASHES); + System.out.println("B - DEMO THE RESILIENCE FUNCTIONALITY"); + System.out.println("Press Enter when you're ready."); + in.nextLine(); + demo(loadBalancer); + System.out.println(DASHES); + + System.out.println(DASHES); + System.out.println("C - DELETE THE RESOURCES"); + System.out.println(""" + This concludes the demo of how to build and manage a resilient service. + To keep things tidy and to avoid unwanted charges on your account, we can clean up all AWS resources + that were created for this demo. + """); + + System.out.println("\n Do you want to delete the resources (y/n)? "); + String userInput = in.nextLine().trim().toLowerCase(); // Capture user input + + if (userInput.equals("y")) { + // Delete resources here + deleteResources(loadBalancer, autoScaler, database); + System.out.println("Resources deleted."); + } else { + System.out.println(""" + Okay, we'll leave the resources intact. + Don't forget to delete them when you're done with them or you might incur unexpected charges. + """); + } + System.out.println(DASHES); + + System.out.println(DASHES); + System.out.println("The example has completed. "); + System.out.println("\n Thanks for watching!"); + System.out.println(DASHES); + } + + // Deletes the AWS resources used in this example. + private static void deleteResources(LoadBalancer loadBalancer, AutoScaler autoScaler, Database database) throws IOException, InterruptedException { + loadBalancer.deleteLoadBalancer(lbName); + System.out.println("*** Wait 30 secs for resource to be deleted"); + TimeUnit.SECONDS.sleep(30); + loadBalancer.deleteTargetGroup(targetGroupName); + autoScaler.deleteAutoScaleGroup(autoScalingGroupName); + autoScaler.deleteRolesPolicies(policyName, roleName, profileName ); + autoScaler.deleteTemplate(templateName); + database.deleteTable(tableName); + } + + private static void deploy(LoadBalancer loadBalancer) throws InterruptedException, IOException { + Scanner in = new Scanner(System.in); + System.out.println(""" + For this demo, we'll use the AWS SDK for Java (v2) to create several AWS resources + to set up a load-balanced web service endpoint and explore some ways to make it resilient + against various kinds of failures. + + Some of the resources create by this demo are: + \t* A DynamoDB table that the web service depends on to provide book, movie, and song recommendations. + \t* An EC2 launch template that defines EC2 instances that each contain a Python web server. + \t* An EC2 Auto Scaling group that manages EC2 instances across several Availability Zones. + \t* An Elastic Load Balancing (ELB) load balancer that targets the Auto Scaling group to distribute requests. + """); + + System.out.println("Press Enter when you're ready."); + in.nextLine(); + System.out.println(DASHES); + + System.out.println(DASHES); + System.out.println("Creating and populating a DynamoDB table named "+tableName); + Database database = new Database(); + database.createTable(tableName, fileName); + System.out.println(DASHES); + + System.out.println(DASHES); + System.out.println(""" + Creating an EC2 launch template that runs '{startup_script}' when an instance starts. + This script starts a Python web server defined in the `server.py` script. The web server + listens to HTTP requests on port 80 and responds to requests to '/' and to '/healthcheck'. + For demo purposes, this server is run as the root user. In production, the best practice is to + run a web server, such as Apache, with least-privileged credentials. + + The template also defines an IAM policy that each instance uses to assume a role that grants + permissions to access the DynamoDB recommendation table and Systems Manager parameters + that control the flow of the demo. + """); + + LaunchTemplateCreator templateCreator = new LaunchTemplateCreator(); + templateCreator.createTemplate(policyFile, policyName, profileName, startScript, templateName, roleName); + System.out.println(DASHES); + + System.out.println(DASHES); + System.out.println("Creating an EC2 Auto Scaling group that maintains three EC2 instances, each in a different Availability Zone."); + System.out.println("*** Wait 30 secs for the VPC to be created"); + TimeUnit.SECONDS.sleep(30); + AutoScaler autoScaler = new AutoScaler(); + String[] zones = autoScaler.createGroup(3, templateName, autoScalingGroupName); + + System.out.println(""" + At this point, you have EC2 instances created. Once each instance starts, it listens for + HTTP requests. You can see these instances in the console or continue with the demo. + Press Enter when you're ready to continue. + """); + + in.nextLine(); + System.out.println(DASHES); + + System.out.println(DASHES); + System.out.println("Creating variables that control the flow of the demo."); + ParameterHelper paramHelper = new ParameterHelper(); + paramHelper.reset(); + System.out.println(DASHES); + + System.out.println(DASHES); + System.out.println(""" + Creating an Elastic Load Balancing target group and load balancer. The target group + defines how the load balancer connects to instances. The load balancer provides a + single endpoint where clients connect and dispatches requests to instances in the group. + """); + + String vpcId = autoScaler.getDefaultVPC(); + List subnets = autoScaler.getSubnets(vpcId, zones); + System.out.println("You have retrieved a list with "+subnets.size() +" subnets"); + String targetGroupArn = loadBalancer.createTargetGroup(protocol, port, vpcId, targetGroupName); + String elbDnsName = loadBalancer.createLoadBalancer(subnets, targetGroupArn, lbName, port, protocol); + autoScaler.attachLoadBalancerTargetGroup(autoScalingGroupName, targetGroupArn); + System.out.println("Verifying access to the load balancer endpoint..."); + boolean wasSuccessul = loadBalancer.verifyLoadBalancerEndpoint(elbDnsName); + if (!wasSuccessul) { + System.out.println("Couldn't connect to the load balancer, verifying that the port is open..."); + CloseableHttpClient httpClient = HttpClients.createDefault(); + + // Create an HTTP GET request to "http://checkip.amazonaws.com" + HttpGet httpGet = new HttpGet("http://checkip.amazonaws.com"); + try { + // Execute the request and get the response + HttpResponse response = httpClient.execute(httpGet); + + // Read the response content. + String ipAddress = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8).trim(); + + // Print the public IP address. + System.out.println("Public IP Address: " + ipAddress); + GroupInfo groupInfo = autoScaler.verifyInboundPort(vpcId, port, ipAddress); + if (!groupInfo.isPortOpen()) { + System.out.println(""" + For this example to work, the default security group for your default VPC must + allow access from this computer. You can either add it automatically from this + example or add it yourself using the AWS Management Console. + """); + + System.out.println("Do you want to add a rule to security group "+groupInfo.getGroupName() +" to allow"); + System.out.println("inbound traffic on port "+port +" from your computer's IP address (y/n) "); + String ans = in.nextLine(); + if ("y".equalsIgnoreCase(ans)) { + autoScaler.openInboundPort(groupInfo.getGroupName(), String.valueOf(port), ipAddress); + System.out.println("Security group rule added."); + } else { + System.out.println("No security group rule added."); + } + } + + } catch (AutoScalingException e) { + e.printStackTrace(); + } + } else if (wasSuccessul) { + System.out.println("Your load balancer is ready. You can access it by browsing to:"); + System.out.println("\t http://"+elbDnsName); + } else { + System.out.println("Couldn't get a successful response from the load balancer endpoint. Troubleshoot by"); + System.out.println("manually verifying that your VPC and security group are configured correctly and that"); + System.out.println( "you can successfully make a GET request to the load balancer."); + } + + System.out.println("Press Enter when you're ready to continue with the demo."); + in.nextLine(); + } + // A method that controls the demo part of the Java program. + public static void demo( LoadBalancer loadBalancer) throws IOException, InterruptedException { + ParameterHelper paramHelper = new ParameterHelper(); + System.out.println("Read the ssm_only_policy.json file"); + String ssmOnlyPolicy = readFileAsString(ssmJSON); + + System.out.println("Resetting parameters to starting values for demo."); + paramHelper.reset(); + + System.out.println(""" + This part of the demonstration shows how to toggle different parts of the system + to create situations where the web service fails, and shows how using a resilient + architecture can keep the web service running in spite of these failures. + + At the start, the load balancer endpoint returns recommendations and reports that all targets are healthy. + """); + demoChoices(loadBalancer); + + System.out.println(""" + The web service running on the EC2 instances gets recommendations by querying a DynamoDB table. + The table name is contained in a Systems Manager parameter named self.param_helper.table. + To simulate a failure of the recommendation service, let's set this parameter to name a non-existent table. + """); + paramHelper.put(paramHelper.tableName, "this-is-not-a-table"); + + System.out.println(""" + \nNow, sending a GET request to the load balancer endpoint returns a failure code. But, the service reports as + healthy to the load balancer because shallow health checks don't check for failure of the recommendation service. + """); + demoChoices(loadBalancer); + + System.out.println(""" + Instead of failing when the recommendation service fails, the web service can return a static response. + While this is not a perfect solution, it presents the customer with a somewhat better experience than failure. + """); + paramHelper.put(paramHelper.failureResponse, "static"); + + System.out.println(""" + Now, sending a GET request to the load balancer endpoint returns a static response. + The service still reports as healthy because health checks are still shallow. + """); + demoChoices(loadBalancer); + + System.out.println("Let's reinstate the recommendation service."); + paramHelper.put(paramHelper.tableName, paramHelper.dyntable); + + System.out.println(""" + Let's also substitute bad credentials for one of the instances in the target group so that it can't + access the DynamoDB recommendation table. We will get an instance id value. + """); + + LaunchTemplateCreator templateCreator = new LaunchTemplateCreator(); + AutoScaler autoScaler = new AutoScaler(); + + //Create a new instance profile based on badCredsProfileName. + templateCreator.createInstanceProfile(policyFile, policyName, badCredsProfileName, roleName); + String badInstanceId = autoScaler.getBadInstance(autoScalingGroupName); + System.out.println("The bad instance id values used for this demo is "+badInstanceId); + + String profileAssociationId = autoScaler.getInstanceProfile(badInstanceId); + System.out.println("The association Id value is "+profileAssociationId); + System.out.println("Replacing the profile for instance " + badInstanceId + " with a profile that contains bad credentials"); + autoScaler.replaceInstanceProfile(badInstanceId, badCredsProfileName, profileAssociationId) ; + + System.out.println(""" + Now, sending a GET request to the load balancer endpoint returns either a recommendation or a static response, + depending on which instance is selected by the load balancer. + """); + + demoChoices(loadBalancer); + + System.out.println(""" + Let's implement a deep health check. For this demo, a deep health check tests whether + the web service can access the DynamoDB table that it depends on for recommendations. Note that + the deep health check is only for ELB routing and not for Auto Scaling instance health. + This kind of deep health check is not recommended for Auto Scaling instance health, because it + risks accidental termination of all instances in the Auto Scaling group when a dependent service fails. + """); + + System.out.println(""" + By implementing deep health checks, the load balancer can detect when one of the instances is failing + and take that instance out of rotation. + """); + + paramHelper.put(paramHelper.healthCheck, "deep"); + + System.out.println(""" + Now, checking target health indicates that the instance with bad credentials + is unhealthy. Note that it might take a minute or two for the load balancer to detect the unhealthy + instance. Sending a GET request to the load balancer endpoint always returns a recommendation, because + the load balancer takes unhealthy instances out of its rotation. + """); + + demoChoices(loadBalancer); + + System.out.println(""" + Because the instances in this demo are controlled by an auto scaler, the simplest way to fix an unhealthy + instance is to terminate it and let the auto scaler start a new instance to replace it. + """); + autoScaler.terminateInstance(badInstanceId); + + System.out.println(""" + Even while the instance is terminating and the new instance is starting, sending a GET + request to the web service continues to get a successful recommendation response because + the load balancer routes requests to the healthy instances. After the replacement instance + starts and reports as healthy, it is included in the load balancing rotation. + Note that terminating and replacing an instance typically takes several minutes, during which time you + can see the changing health check status until the new instance is running and healthy. + """); + + demoChoices(loadBalancer); + System.out.println("If the recommendation service fails now, deep health checks mean all instances report as unhealthy."); + paramHelper.put(paramHelper.tableName, "this-is-not-a-table"); + + demoChoices(loadBalancer); + paramHelper.reset(); + } + + public static void demoChoices(LoadBalancer loadBalancer) throws IOException, InterruptedException { + String[] actions = { + "Send a GET request to the load balancer endpoint.", + "Check the health of load balancer targets.", + "Go to the next part of the demo." + }; + Scanner scanner = new Scanner(System.in); + + while (true) { + System.out.println("-".repeat(88)); + System.out.println("See the current state of the service by selecting one of the following choices:"); + for (int i = 0; i < actions.length; i++) { + System.out.println(i + ": " + actions[i]); + } + + try { + System.out.print("\nWhich action would you like to take? "); + int choice = scanner.nextInt(); + System.out.println("-".repeat(88)); + + switch (choice) { + case 0 -> { + System.out.println("Request:\n"); + System.out.println("GET http://" + loadBalancer.getEndpoint(lbName)); + CloseableHttpClient httpClient = HttpClients.createDefault(); + + // Create an HTTP GET request to the ELB. + HttpGet httpGet = new HttpGet("http://" + loadBalancer.getEndpoint(lbName)); + + // Execute the request and get the response. + HttpResponse response = httpClient.execute(httpGet); + int statusCode = response.getStatusLine().getStatusCode(); + System.out.println("HTTP Status Code: " + statusCode); + + // Display the JSON response + BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); + StringBuilder jsonResponse = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + jsonResponse.append(line); + } + reader.close(); + + // Print the formatted JSON response. + System.out.println("Full Response:\n"); + System.out.println(jsonResponse.toString()); + + // Close the HTTP client. + httpClient.close(); + + + } + case 1 -> { + System.out.println("\nChecking the health of load balancer targets:\n"); + List health = loadBalancer.checkTargetHealth(targetGroupName); + for (TargetHealthDescription target : health) { + System.out.printf("\tTarget %s on port %d is %s%n", target.target().id(), target.target().port(), target.targetHealth().stateAsString()); + } + System.out.println(""" + Note that it can take a minute or two for the health check to update + after changes are made. + """); + } + case 2 -> { + System.out.println("\nOkay, let's move on."); + System.out.println("-".repeat(88)); + return; // Exit the method when choice is 2 + } + default -> System.out.println("You must choose a value between 0-2. Please select again."); + } + + } catch (java.util.InputMismatchException e) { + System.out.println("Invalid input. Please select again."); + scanner.nextLine(); // Clear the input buffer. + } + } + } + + public static String readFileAsString(String filePath) throws IOException { + byte[] bytes = Files.readAllBytes(Paths.get(filePath)); + return new String(bytes); + } +} +// snippet-end:[javav2.example_code.workflow.ResilientService_Runner] \ No newline at end of file diff --git a/javav2/usecases/resilient_service/src/main/java/com/example/resilient/ParameterHelper.java b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/ParameterHelper.java new file mode 100644 index 00000000000..f07ed2d2eb6 --- /dev/null +++ b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/ParameterHelper.java @@ -0,0 +1,42 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +package com.example.resilient; + +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ssm.SsmClient; +import software.amazon.awssdk.services.ssm.model.PutParameterRequest; + +// snippet-start:[javav2.example_code.workflow.ResilientService_ParameterHelper] +public class ParameterHelper { + + String tableName = "doc-example-resilient-architecture-table"; + String dyntable = "doc-example-recommendation-service"; + String failureResponse = "doc-example-resilient-architecture-failure-response"; + String healthCheck = "doc-example-resilient-architecture-health-check"; + + public void reset() { + put(dyntable, tableName ); + put(failureResponse, "none"); + put(healthCheck, "shallow"); + } + + public void put(String name, String value) { + SsmClient ssmClient = SsmClient.builder() + .region(Region.US_EAST_1) + .build(); + + PutParameterRequest parameterRequest = PutParameterRequest.builder() + .name(name) + .value(value) + .overwrite(true) + .type("String") + .build(); + + ssmClient.putParameter(parameterRequest); + System.out.printf("Setting demo parameter %s to '%s'.", name, value); + } +} +// snippet-end:[javav2.example_code.workflow.ResilientService_ParameterHelper] \ No newline at end of file diff --git a/javav2/usecases/resilient_service/src/main/java/com/example/resilient/Recommendation.java b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/Recommendation.java new file mode 100644 index 00000000000..52ca7c121e5 --- /dev/null +++ b/javav2/usecases/resilient_service/src/main/java/com/example/resilient/Recommendation.java @@ -0,0 +1,56 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +package com.example.resilient; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; + +@DynamoDbBean +public class Recommendation { + + private String mediaType; + private int itemId; + private String title; + private String creator; + + @DynamoDbPartitionKey + @DynamoDbAttribute("MediaType") + public String getMediaType() { + return mediaType; + } + + public void setMediaType(String mediaType) { + this.mediaType = mediaType; + } + + @DynamoDbSortKey + @DynamoDbAttribute("ItemId") + public int getItemId() { + return itemId; + } + + public void setItemId(int itemId) { + this.itemId = itemId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getCreator() { + return creator; + } + + public void setCreator(String creator) { + this.creator = creator; + } +} diff --git a/javav2/usecases/resilient_service/src/main/resources/config.properties b/javav2/usecases/resilient_service/src/main/resources/config.properties new file mode 100644 index 00000000000..2d5fc4d5015 --- /dev/null +++ b/javav2/usecases/resilient_service/src/main/resources/config.properties @@ -0,0 +1,17 @@ +fileName = C:\\AWS\\resworkflow\\recommendations.json +tableName = doc-example-recommendation-service +startScript = C:\\AWS\\resworkflow\\server_startup_script.sh +policyFile = C:\\AWS\\resworkflow\\instance_policy.json +ssmJSON = C:\\AWS\\resworkflow\\ssm_only_policy.json +failureResponse = doc-example-resilient-architecture-failure-response +healthCheck = doc-example-resilient-architecture-health-check +templateName = doc-example-resilience-template +roleName = doc-example-resilience-role +policyName = doc-example-resilience-pol +profileName =doc-example-resilience-prof +badCredsProfileName =doc-example-resilience-prof-bc +targetGroupName = doc-example-resilience-tg +autoScalingGroupName = doc-example-resilience-group +lbName = doc-example-resilience-lb +protocol = HTTP +port = 80 diff --git a/javav2/usecases/resilient_service/src/test/java/ResilientTests.java b/javav2/usecases/resilient_service/src/test/java/ResilientTests.java new file mode 100644 index 00000000000..e75ecef75e2 --- /dev/null +++ b/javav2/usecases/resilient_service/src/test/java/ResilientTests.java @@ -0,0 +1,142 @@ +import com.example.resilient.AutoScaler; +import com.example.resilient.Database; +import com.example.resilient.LaunchTemplateCreator; +import com.example.resilient.LoadBalancer; +import com.example.resilient.ParameterHelper; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; +import software.amazon.awssdk.services.ec2.model.Subnet; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.TimeUnit; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@TestInstance(TestInstance.Lifecycle.PER_METHOD) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class ResilientTests { + + public static String fileName = "" ; + public static String tableName = "" ; + public static String startScript = "" ; + public static String policyFile = "" ; + public static String ssmJSON = "" ; + public static String failureResponse = "" ; + public static String healthCheck = "" ; + public static String templateName = "" ; + public static String roleName = "" ; + public static String policyName = "" ; + public static String profileName = "" ; + + public static String badCredsProfileName= "" ; + + public static String targetGroupName = "" ; + public static String autoScalingGroupName = "" ; + public static String lbName = "" ; + public static String protocol = "" ; + public static int port ; + + String elbDnsName = ""; + + static LoadBalancer loadBalancer = null; + + static AutoScaler autoScaler = null ; + + static Database database = null; + + @BeforeAll + public static void setUp() { + + loadBalancer = new LoadBalancer(); + autoScaler = new AutoScaler(); + database = new Database(); + + try (InputStream input = ResilientTests.class.getClassLoader().getResourceAsStream("config.properties")) { + Properties prop = new Properties(); + if (input == null) { + System.out.println("Sorry, unable to find config.properties"); + return; + } + + prop.load(input); + fileName = prop.getProperty("fileName"); + tableName = prop.getProperty("tableName"); + startScript = prop.getProperty("startScript"); + policyFile = prop.getProperty("policyFile"); + ssmJSON = prop.getProperty("ssmJSON"); + failureResponse = prop.getProperty("failureResponse"); + healthCheck = prop.getProperty("healthCheck"); + templateName = prop.getProperty("templateName"); + roleName = prop.getProperty("roleName"); + policyName = prop.getProperty("policyName"); + profileName = prop.getProperty("profileName"); + badCredsProfileName = prop.getProperty("badCredsProfileName"); + targetGroupName = prop.getProperty("targetGroupName"); + autoScalingGroupName = prop.getProperty("autoScalingGroupName"); + lbName = prop.getProperty("lbName"); + protocol = prop.getProperty("protocol"); + port = Integer.parseInt(prop.getProperty("port")); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + @Tag("IntegrationTest") + @Order(1) + public void setUpTable() throws IOException { + Database database = new Database(); + database.createTable(tableName, fileName); + } + + @Test + @Tag("IntegrationTest") + @Order(2) + public void createTemplate() { + LaunchTemplateCreator templateCreator = new LaunchTemplateCreator(); + templateCreator.createTemplate(policyFile, policyName, profileName, startScript, templateName, roleName); + } + + @Test + @Tag("IntegrationTest") + @Order(3) + public void setupResources() throws InterruptedException { + TimeUnit.SECONDS.sleep(30); + AutoScaler autoScaler = new AutoScaler(); + String[] zones = autoScaler.createGroup(3, templateName, autoScalingGroupName); + + ParameterHelper paramHelper = new ParameterHelper(); + paramHelper.reset(); + + String vpcId = autoScaler.getDefaultVPC(); + List subnets = autoScaler.getSubnets(vpcId, zones); + System.out.println("You have retrieved a list with "+subnets.size() +" subnets"); + String targetGroupArn = loadBalancer.createTargetGroup(protocol, port, vpcId, targetGroupName); + elbDnsName = loadBalancer.createLoadBalancer(subnets, targetGroupArn, lbName, port, protocol); + assertNotNull(elbDnsName); + } + + + @Test + @Tag("IntegrationTest") + @Order(4) + public void destroyResources() throws InterruptedException { + loadBalancer.deleteLoadBalancer(lbName); + System.out.println("*** Wait 30 secs for resource to be deleted"); + TimeUnit.SECONDS.sleep(30); + loadBalancer.deleteTargetGroup(targetGroupName); + autoScaler.deleteAutoScaleGroup(autoScalingGroupName); + autoScaler.deleteRolesPolicies(policyName, roleName, profileName ); + autoScaler.deleteTemplate(templateName); + database.deleteTable(tableName); + + } +}