diff --git a/walkthroughs/howto-grpc/README.md b/walkthroughs/howto-grpc/README.md new file mode 100644 index 00000000..19fae336 --- /dev/null +++ b/walkthroughs/howto-grpc/README.md @@ -0,0 +1,123 @@ +## Overview + +This example shows how we can route between gRPC clients and servers using App Mesh. + +![System Diagram](./howto-grpc.png "System Diagram") + +### Color Server + +The Color Server is a gRPC server that implements [color.ColorService](./color.proto). Additionally, it implements the [gRPC Health Checking Protocol](https://github.com/grpc/grpc/blob/master/doc/health-checking.md) which we will configure App Mesh to use as the health check for the its virtual-nodes. + +### Color Client + +The Color Client is a HTTP/1.1 front-end webserver that maintains a persistent gRPC connection to the Color Server. The HTTP/1.1 webserver will be connected to an internet-facing ALB. It forwards requests to `/getColor` and `/setColor` to the same methods in [color.ColorService](./color.proto). Initially, the Envoy sidecar for the Color Client will be configured to only route the `GetColor` gRPC method, but we will update the route to forward all methods to the Color Server. + +## Setup + +1. This example uses features in the [App Mesh Preview Channel](https://docs.aws.amazon.com/app-mesh/latest/userguide/preview.html). You'll need to install the latest `appmesh-preview` model to deploy it + ``` + aws configure add-model \ + --service-name appmesh-preview \ + --service-model https://raw.githubusercontent.com/aws/aws-app-mesh-roadmap/master/appmesh-preview/service-model.json + ``` +2. Clone this repository and navigate to the walkthrough/howto-grpc folder, all commands will be ran from this location +3. **Project Name** used to isolate resources created in this demo from other's in your account. e.g. howto-grpc + ``` + export PROJECT_NAME=howto-grpc + ``` +4. **Your** account id: + ``` + export AWS_ACCOUNT_ID= + ``` +5. **Region** e.g. us-west-2 + ``` + export AWS_DEFAULT_REGION=us-west-2 + ``` +6. **ENVOY_IMAGE** environment variable is not set to App Mesh Envoy, see https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy.html + ``` + export ENVOY_IMAGE=... + ``` +7. Setup using cloudformation + ``` + ./deploy.sh + ``` + Note that the example apps use go modules. If you have trouble accessing https://proxy.golang.org during the deployment you can override the GOPROXY by setting `GO_PROXY=direct` + ``` + GO_PROXY=direct ./deploy.sh + ``` + +## Verification + +1. After a few minutes, the applications should be deployed and you will see an output such as: + ``` + Successfully created/updated stack - howto-grpc-app + Public endpoint: + http://howto-Publi-5555555.us-west-2.elb.amazonaws.com + ``` + This is the public endpoint to access the Color Client APIs. Export it. + ``` + export COLOR_ENDPOINT= + ``` +2. Try curling the `/getColor` API + ``` + curl $COLOR_ENDPOINT/getColor + ``` + You should see `no_color`. The color returned by the Color Service via the Color Client can be configured using the `/setColor` API. +3. Attempt to change the color by curling the `/setColor` API + ``` + curl -i -X POST -d "blue" $COLOR_ENDPOINT/setColor + ``` + We passed the `-i` flag to see any error information in the response. You should see something like: + ``` + HTTP/1.1 404 Not Found + Date: Fri, 27 Sep 2019 01:27:42 GMT + Content-Type: text/plain; charset=utf-8 + Content-Length: 40 + Connection: keep-alive + x-content-type-options: nosniff + x-envoy-upstream-service-time: 1 + server: envoy + + rpc error: code = Unimplemented desc = + ``` + This is because our current mesh is only configured to route the gRPC Method `GetColor`: + + (from [mesh/route.json](./mesh/route.json)) + ```json + { + "grpcRoute": { + "action": { + "weightedTargets": [ + { + "virtualNode": "color_server", + "weight": 100 + } + ] + }, + "match": { + "serviceName": "color.ColorService", + "methodName": "GetColor" + } + } + } + ``` + We'll remove the `methodName` match condition in the gRPC route to match all methods for `color.ColorService`. +4. Update the route to [mesh/route-all-methods.json](./mesh/route-all-methods.json): + ``` + aws appmesh-preview update-route --mesh-name $PROJECT_NAME-mesh --virtual-router-name virtual-router --route-name route --cli-input-json file://mesh/route-all-methods.json + ``` +5. Now try updating the color again + ``` + curl -i -X POST -d "blue" $COLOR_ENDPOINT/setColor + ``` + You'll see that we got a `HTTP/1.1 200 OK` response. You'll also see `no_color` in the response. But this is the previous color being returned after a successful color update. +6. You can verify that the color did, in fact, update + ``` + curl $COLOR_ENDPOINT/getColor + ``` + +### Teardown +When you are done with the example you can delete everything we created by running: +``` +./deploy.sh delete +``` diff --git a/walkthroughs/howto-grpc/app.yaml b/walkthroughs/howto-grpc/app.yaml new file mode 100644 index 00000000..9cc8d070 --- /dev/null +++ b/walkthroughs/howto-grpc/app.yaml @@ -0,0 +1,391 @@ +Parameters: + ProjectName: + Type: String + Description: Project name to link stacks + + EnvoyImage: + Type: String + Description: Envoy container image + + ColorClientImage: + Type: String + Description: Color client app container image + + ColorServerImage: + Type: String + Description: Color server app container image + + ContainerPort: + Type: Number + Description: Port number to use for applications + Default: 8080 + +Resources: + PublicLoadBalancerSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: 'Access to the public facing load balancer' + VpcId: + Fn::ImportValue: !Sub '${ProjectName}:VPC' + SecurityGroupIngress: + - CidrIp: 0.0.0.0/0 + IpProtocol: -1 + + PublicLoadBalancer: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Scheme: internet-facing + LoadBalancerAttributes: + - Key: idle_timeout.timeout_seconds + Value: '30' + Subnets: + - Fn::ImportValue: !Sub '${ProjectName}:PublicSubnet1' + - Fn::ImportValue: !Sub '${ProjectName}:PublicSubnet2' + SecurityGroups: + - !Ref PublicLoadBalancerSecurityGroup + + WebTargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + HealthCheckIntervalSeconds: 6 + HealthCheckPath: '/ping' + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: 5 + HealthyThresholdCount: 2 + TargetType: ip + Name: !Sub '${ProjectName}-webtarget' + Port: 80 + Protocol: HTTP + UnhealthyThresholdCount: 2 + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: 120 + VpcId: + Fn::ImportValue: !Sub '${ProjectName}:VPC' + + PublicLoadBalancerListener: + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + DefaultActions: + - TargetGroupArn: !Ref WebTargetGroup + Type: 'forward' + LoadBalancerArn: !Ref PublicLoadBalancer + Port: 80 + Protocol: HTTP + + WebLoadBalancerRule: + Type: AWS::ElasticLoadBalancingV2::ListenerRule + Properties: + Actions: + - TargetGroupArn: !Ref WebTargetGroup + Type: 'forward' + Conditions: + - Field: path-pattern + Values: + - '*' + ListenerArn: !Ref PublicLoadBalancerListener + Priority: 1 + + TaskSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: "Security group for the tasks" + VpcId: + Fn::ImportValue: !Sub '${ProjectName}:VPC' + SecurityGroupIngress: + - CidrIp: + Fn::ImportValue: !Sub '${ProjectName}:VpcCIDR' + IpProtocol: -1 + + LogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub '${ProjectName}-log-group' + RetentionInDays: 30 + + TaskIamRole: + Type: AWS::IAM::Role + Properties: + Path: / + AssumeRolePolicyDocument: | + { + "Statement": [{ + "Effect": "Allow", + "Principal": { "Service": [ "ecs-tasks.amazonaws.com" ]}, + "Action": [ "sts:AssumeRole" ] + }] + } + ManagedPolicyArns: + - arn:aws:iam::aws:policy/CloudWatchFullAccess + - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess + - arn:aws:iam::aws:policy/AWSAppMeshEnvoyAccess + - arn:aws:iam::aws:policy/AWSAppMeshPreviewEnvoyAccess + + TaskExecutionIamRole: + Type: AWS::IAM::Role + Properties: + Path: / + AssumeRolePolicyDocument: | + { + "Statement": [{ + "Effect": "Allow", + "Principal": { "Service": [ "ecs-tasks.amazonaws.com" ]}, + "Action": [ "sts:AssumeRole" ] + }] + } + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly + - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess + + SecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: "Security group for the instances" + VpcId: + Fn::ImportValue: !Sub '${ProjectName}:VPC' + SecurityGroupIngress: + - CidrIp: + Fn::ImportValue: !Sub '${ProjectName}:VpcCIDR' + IpProtocol: -1 + + ColorClientRegistry: + Type: AWS::ServiceDiscovery::Service + Properties: + Name: 'color_client' + DnsConfig: + NamespaceId: + Fn::ImportValue: !Sub '${ProjectName}:CloudMapNamespaceId' + DnsRecords: + - Type: A + TTL: 300 + HealthCheckCustomConfig: + FailureThreshold: 1 + + ColorClientService: + Type: AWS::ECS::Service + DependsOn: + - WebLoadBalancerRule + - ColorServerService + Properties: + Cluster: + Fn::ImportValue: !Sub '${ProjectName}:ECSCluster' + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 100 + DesiredCount: 1 + LaunchType: 'FARGATE' + ServiceRegistries: + - RegistryArn: !GetAtt 'ColorClientRegistry.Arn' + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: DISABLED + SecurityGroups: + - !Ref SecurityGroup + Subnets: + - Fn::ImportValue: !Sub '${ProjectName}:PrivateSubnet1' + - Fn::ImportValue: !Sub '${ProjectName}:PrivateSubnet2' + TaskDefinition: !Ref ColorClientTaskDef + LoadBalancers: + - ContainerName: app + ContainerPort: !Ref ContainerPort + TargetGroupArn: !Ref WebTargetGroup + + ColorClientTaskDef: + Type: AWS::ECS::TaskDefinition + Properties: + RequiresCompatibilities: + - 'FARGATE' + Family: 'color_client' + NetworkMode: 'awsvpc' + Cpu: 256 + Memory: 512 + TaskRoleArn: !Ref TaskIamRole + ExecutionRoleArn: !Ref TaskExecutionIamRole + ProxyConfiguration: + Type: 'APPMESH' + ContainerName: 'envoy' + ProxyConfigurationProperties: + - Name: 'IgnoredUID' + Value: '1337' + - Name: 'ProxyIngressPort' + Value: '15000' + - Name: 'ProxyEgressPort' + Value: '15001' + - Name: 'AppPorts' + Value: !Sub '${ContainerPort}' + - Name: 'EgressIgnoredIPs' + Value: '169.254.170.2,169.254.169.254' + ContainerDefinitions: + - Name: 'app' + Image: !Ref ColorClientImage + Essential: true + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub '${ProjectName}-log-group' + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: 'color_client' + PortMappings: + - ContainerPort: !Ref ContainerPort + Protocol: 'tcp' + Environment: + - Name: 'PORT' + Value: !Sub '${ContainerPort}' + - Name: 'COLOR_HOST' + Value: !Sub 'color_server.grpc.local:${ContainerPort}' + - Name: envoy + Image: !Ref EnvoyImage + Essential: true + User: '1337' + Ulimits: + - Name: "nofile" + HardLimit: 15000 + SoftLimit: 15000 + PortMappings: + - ContainerPort: 9901 + Protocol: 'tcp' + - ContainerPort: 15000 + Protocol: 'tcp' + - ContainerPort: 15001 + Protocol: 'tcp' + HealthCheck: + Command: + - 'CMD-SHELL' + - 'curl -s http://localhost:9901/server_info | grep state | grep -q LIVE' + Interval: 5 + Timeout: 2 + Retries: 3 + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub '${ProjectName}-log-group' + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: 'color_client-envoy' + Environment: + - Name: 'APPMESH_VIRTUAL_NODE_NAME' + Value: !Sub 'mesh/${ProjectName}-mesh/virtualNode/color_client' + - Name: 'APPMESH_PREVIEW' + Value: '1' + - Name: 'ENVOY_LOG_LEVEL' + Value: 'debug' + + ColorServerTaskDef: + Type: AWS::ECS::TaskDefinition + Properties: + RequiresCompatibilities: + - 'FARGATE' + Family: 'color_server' + NetworkMode: 'awsvpc' + Cpu: 256 + Memory: 512 + TaskRoleArn: !Ref TaskIamRole + ExecutionRoleArn: !Ref TaskExecutionIamRole + ProxyConfiguration: + Type: 'APPMESH' + ContainerName: 'envoy' + ProxyConfigurationProperties: + - Name: 'IgnoredUID' + Value: '1337' + - Name: 'ProxyIngressPort' + Value: '15000' + - Name: 'ProxyEgressPort' + Value: '15001' + - Name: 'AppPorts' + Value: !Sub '${ContainerPort}' + - Name: 'EgressIgnoredIPs' + Value: '169.254.170.2,169.254.169.254' + ContainerDefinitions: + - Name: 'app' + Image: !Ref ColorServerImage + Essential: true + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub '${ProjectName}-log-group' + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: 'color_server' + PortMappings: + - ContainerPort: !Ref ContainerPort + Protocol: 'tcp' + Environment: + - Name: 'PORT' + Value: !Sub '${ContainerPort}' + - Name: 'COLOR' + Value: !Sub 'no color!' + - Name: envoy + Image: !Ref EnvoyImage + Essential: true + User: '1337' + Ulimits: + - Name: "nofile" + HardLimit: 15000 + SoftLimit: 15000 + PortMappings: + - ContainerPort: 9901 + Protocol: 'tcp' + - ContainerPort: 15000 + Protocol: 'tcp' + - ContainerPort: 15001 + Protocol: 'tcp' + HealthCheck: + Command: + - 'CMD-SHELL' + - 'curl -s http://localhost:9901/server_info | grep state | grep -q LIVE' + Interval: 5 + Timeout: 2 + Retries: 3 + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub '${ProjectName}-log-group' + awslogs-region: !Ref AWS::Region + awslogs-stream-prefix: 'color_server-envoy' + Environment: + - Name: 'APPMESH_VIRTUAL_NODE_NAME' + Value: !Sub 'mesh/${ProjectName}-mesh/virtualNode/color_server' + - Name: 'APPMESH_PREVIEW' + Value: '1' + - Name: 'ENVOY_LOG_LEVEL' + Value: 'debug' + + ColorServerRegistry: + Type: AWS::ServiceDiscovery::Service + Properties: + Name: 'color_server' + DnsConfig: + NamespaceId: + Fn::ImportValue: !Sub '${ProjectName}:CloudMapNamespaceId' + DnsRecords: + - Type: A + TTL: 300 + HealthCheckCustomConfig: + FailureThreshold: 1 + + ColorServerService: + Type: AWS::ECS::Service + Properties: + Cluster: + Fn::ImportValue: !Sub '${ProjectName}:ECSCluster' + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 100 + DesiredCount: 1 + LaunchType: 'FARGATE' + ServiceRegistries: + - RegistryArn: !GetAtt 'ColorServerRegistry.Arn' + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: DISABLED + SecurityGroups: + - !Ref SecurityGroup + Subnets: + - Fn::ImportValue: !Sub '${ProjectName}:PrivateSubnet1' + - Fn::ImportValue: !Sub '${ProjectName}:PrivateSubnet2' + TaskDefinition: !Ref ColorServerTaskDef + +Outputs: + PublicEndpoint: + Description: 'Public endpoint for the color client service' + Value: !Join ['', ['http://', !GetAtt 'PublicLoadBalancer.DNSName']] + Export: + Name: !Sub '${ProjectName}:PublicEndpoint' diff --git a/walkthroughs/howto-grpc/color.proto b/walkthroughs/howto-grpc/color.proto new file mode 100644 index 00000000..50a85ba0 --- /dev/null +++ b/walkthroughs/howto-grpc/color.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; + +package color; + +service ColorService { + rpc GetColor (GetColorRequest) returns (GetColorResponse) {} + rpc SetColor (SetColorRequest) returns (SetColorResponse) {} +} + +enum Color { + NO_COLOR = 0; + RED = 1; + BLUE = 2; + GREEN = 3; + YELLOW = 4; + ORANGE = 5; + PURPLE = 6; + PINK = 7; + BLACK = 8; + WHITE = 9; +} + +message GetColorRequest {} + +message GetColorResponse { + Color color = 1; +} + +message SetColorRequest { + Color color = 1; +} + +message SetColorResponse { + Color color = 1; +} diff --git a/walkthroughs/howto-grpc/color_client/Dockerfile b/walkthroughs/howto-grpc/color_client/Dockerfile new file mode 100644 index 00000000..f356ce7c --- /dev/null +++ b/walkthroughs/howto-grpc/color_client/Dockerfile @@ -0,0 +1,24 @@ +FROM golang:1 AS builder + +# Use the default go proxy +ARG GO_PROXY=https://proxy.golang.org + +WORKDIR /go/src/github.com/aws/aws-app-mesh-examples/walkthroughs/howto-grpc/color_client + +# Set the proxies for the go compiler. +ENV GOPROXY=$GO_PROXY + +# go.mod and go.sum go into their own layers. +# This ensures `go mod download` happens only when go.mod and go.sum change. +COPY go.mod . +COPY go.sum . +RUN go mod download + +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix nocgo -o /color_client . + +FROM amazonlinux:2 +RUN yum install -y ca-certificates +COPY --from=builder /color_client /color_client + +ENTRYPOINT ["/color_client"] diff --git a/walkthroughs/howto-grpc/color_client/color/color.pb.go b/walkthroughs/howto-grpc/color_client/color/color.pb.go new file mode 100644 index 00000000..ed5d0bab --- /dev/null +++ b/walkthroughs/howto-grpc/color_client/color/color.pb.go @@ -0,0 +1,369 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: color.proto + +package color + +import ( + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type Color int32 + +const ( + Color_NO_COLOR Color = 0 + Color_RED Color = 1 + Color_BLUE Color = 2 + Color_GREEN Color = 3 + Color_YELLOW Color = 4 + Color_ORANGE Color = 5 + Color_PURPLE Color = 6 + Color_PINK Color = 7 + Color_BLACK Color = 8 + Color_WHITE Color = 9 +) + +var Color_name = map[int32]string{ + 0: "NO_COLOR", + 1: "RED", + 2: "BLUE", + 3: "GREEN", + 4: "YELLOW", + 5: "ORANGE", + 6: "PURPLE", + 7: "PINK", + 8: "BLACK", + 9: "WHITE", +} + +var Color_value = map[string]int32{ + "NO_COLOR": 0, + "RED": 1, + "BLUE": 2, + "GREEN": 3, + "YELLOW": 4, + "ORANGE": 5, + "PURPLE": 6, + "PINK": 7, + "BLACK": 8, + "WHITE": 9, +} + +func (x Color) String() string { + return proto.EnumName(Color_name, int32(x)) +} + +func (Color) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_83c7eb30498dece0, []int{0} +} + +type GetColorRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetColorRequest) Reset() { *m = GetColorRequest{} } +func (m *GetColorRequest) String() string { return proto.CompactTextString(m) } +func (*GetColorRequest) ProtoMessage() {} +func (*GetColorRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_83c7eb30498dece0, []int{0} +} + +func (m *GetColorRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetColorRequest.Unmarshal(m, b) +} +func (m *GetColorRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetColorRequest.Marshal(b, m, deterministic) +} +func (m *GetColorRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetColorRequest.Merge(m, src) +} +func (m *GetColorRequest) XXX_Size() int { + return xxx_messageInfo_GetColorRequest.Size(m) +} +func (m *GetColorRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetColorRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetColorRequest proto.InternalMessageInfo + +type GetColorResponse struct { + Color Color `protobuf:"varint,1,opt,name=color,proto3,enum=color.Color" json:"color,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetColorResponse) Reset() { *m = GetColorResponse{} } +func (m *GetColorResponse) String() string { return proto.CompactTextString(m) } +func (*GetColorResponse) ProtoMessage() {} +func (*GetColorResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_83c7eb30498dece0, []int{1} +} + +func (m *GetColorResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetColorResponse.Unmarshal(m, b) +} +func (m *GetColorResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetColorResponse.Marshal(b, m, deterministic) +} +func (m *GetColorResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetColorResponse.Merge(m, src) +} +func (m *GetColorResponse) XXX_Size() int { + return xxx_messageInfo_GetColorResponse.Size(m) +} +func (m *GetColorResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetColorResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetColorResponse proto.InternalMessageInfo + +func (m *GetColorResponse) GetColor() Color { + if m != nil { + return m.Color + } + return Color_NO_COLOR +} + +type SetColorRequest struct { + Color Color `protobuf:"varint,1,opt,name=color,proto3,enum=color.Color" json:"color,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SetColorRequest) Reset() { *m = SetColorRequest{} } +func (m *SetColorRequest) String() string { return proto.CompactTextString(m) } +func (*SetColorRequest) ProtoMessage() {} +func (*SetColorRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_83c7eb30498dece0, []int{2} +} + +func (m *SetColorRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SetColorRequest.Unmarshal(m, b) +} +func (m *SetColorRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SetColorRequest.Marshal(b, m, deterministic) +} +func (m *SetColorRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SetColorRequest.Merge(m, src) +} +func (m *SetColorRequest) XXX_Size() int { + return xxx_messageInfo_SetColorRequest.Size(m) +} +func (m *SetColorRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SetColorRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_SetColorRequest proto.InternalMessageInfo + +func (m *SetColorRequest) GetColor() Color { + if m != nil { + return m.Color + } + return Color_NO_COLOR +} + +type SetColorResponse struct { + Color Color `protobuf:"varint,1,opt,name=color,proto3,enum=color.Color" json:"color,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SetColorResponse) Reset() { *m = SetColorResponse{} } +func (m *SetColorResponse) String() string { return proto.CompactTextString(m) } +func (*SetColorResponse) ProtoMessage() {} +func (*SetColorResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_83c7eb30498dece0, []int{3} +} + +func (m *SetColorResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SetColorResponse.Unmarshal(m, b) +} +func (m *SetColorResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SetColorResponse.Marshal(b, m, deterministic) +} +func (m *SetColorResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_SetColorResponse.Merge(m, src) +} +func (m *SetColorResponse) XXX_Size() int { + return xxx_messageInfo_SetColorResponse.Size(m) +} +func (m *SetColorResponse) XXX_DiscardUnknown() { + xxx_messageInfo_SetColorResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_SetColorResponse proto.InternalMessageInfo + +func (m *SetColorResponse) GetColor() Color { + if m != nil { + return m.Color + } + return Color_NO_COLOR +} + +func init() { + proto.RegisterEnum("color.Color", Color_name, Color_value) + proto.RegisterType((*GetColorRequest)(nil), "color.GetColorRequest") + proto.RegisterType((*GetColorResponse)(nil), "color.GetColorResponse") + proto.RegisterType((*SetColorRequest)(nil), "color.SetColorRequest") + proto.RegisterType((*SetColorResponse)(nil), "color.SetColorResponse") +} + +func init() { proto.RegisterFile("color.proto", fileDescriptor_83c7eb30498dece0) } + +var fileDescriptor_83c7eb30498dece0 = []byte{ + // 264 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4e, 0xce, 0xcf, 0xc9, + 0x2f, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x73, 0x94, 0x04, 0xb9, 0xf8, 0xdd, + 0x53, 0x4b, 0x9c, 0x41, 0xec, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, 0x25, 0x33, 0x2e, 0x01, + 0x84, 0x50, 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0x90, 0x12, 0x17, 0x44, 0xbd, 0x04, 0xa3, 0x02, + 0xa3, 0x06, 0x9f, 0x11, 0x8f, 0x1e, 0xc4, 0x28, 0x88, 0x22, 0xa8, 0x51, 0xa6, 0x5c, 0xfc, 0xc1, + 0xa8, 0x46, 0x11, 0xa5, 0xcd, 0x8c, 0x4b, 0x20, 0x98, 0x0c, 0xeb, 0xb4, 0xca, 0xb9, 0x58, 0xc1, + 0x7c, 0x21, 0x1e, 0x2e, 0x0e, 0x3f, 0xff, 0x78, 0x67, 0x7f, 0x1f, 0xff, 0x20, 0x01, 0x06, 0x21, + 0x76, 0x2e, 0xe6, 0x20, 0x57, 0x17, 0x01, 0x46, 0x21, 0x0e, 0x2e, 0x16, 0x27, 0x9f, 0x50, 0x57, + 0x01, 0x26, 0x21, 0x4e, 0x2e, 0x56, 0xf7, 0x20, 0x57, 0x57, 0x3f, 0x01, 0x66, 0x21, 0x2e, 0x2e, + 0xb6, 0x48, 0x57, 0x1f, 0x1f, 0xff, 0x70, 0x01, 0x16, 0x10, 0xdb, 0x3f, 0xc8, 0xd1, 0xcf, 0xdd, + 0x55, 0x80, 0x15, 0xc4, 0x0e, 0x08, 0x0d, 0x0a, 0xf0, 0x71, 0x15, 0x60, 0x03, 0x69, 0x0c, 0xf0, + 0xf4, 0xf3, 0x16, 0x60, 0x07, 0x69, 0x74, 0xf2, 0x71, 0x74, 0xf6, 0x16, 0xe0, 0x00, 0x31, 0xc3, + 0x3d, 0x3c, 0x43, 0x5c, 0x05, 0x38, 0x8d, 0x7a, 0x18, 0xb9, 0x78, 0xc0, 0x36, 0x07, 0xa7, 0x16, + 0x95, 0x65, 0x26, 0xa7, 0x0a, 0xd9, 0x72, 0x71, 0xc0, 0x02, 0x4c, 0x48, 0x0c, 0xea, 0x54, 0xb4, + 0x40, 0x95, 0x12, 0xc7, 0x10, 0x87, 0x78, 0x55, 0x89, 0x01, 0xa4, 0x3d, 0x18, 0x5d, 0x7b, 0x30, + 0x0e, 0xed, 0xc1, 0x18, 0xda, 0x93, 0xd8, 0xc0, 0xf1, 0x69, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, + 0x42, 0x63, 0xf3, 0xd5, 0xde, 0x01, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// ColorServiceClient is the client API for ColorService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ColorServiceClient interface { + GetColor(ctx context.Context, in *GetColorRequest, opts ...grpc.CallOption) (*GetColorResponse, error) + SetColor(ctx context.Context, in *SetColorRequest, opts ...grpc.CallOption) (*SetColorResponse, error) +} + +type colorServiceClient struct { + cc *grpc.ClientConn +} + +func NewColorServiceClient(cc *grpc.ClientConn) ColorServiceClient { + return &colorServiceClient{cc} +} + +func (c *colorServiceClient) GetColor(ctx context.Context, in *GetColorRequest, opts ...grpc.CallOption) (*GetColorResponse, error) { + out := new(GetColorResponse) + err := c.cc.Invoke(ctx, "/color.ColorService/GetColor", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *colorServiceClient) SetColor(ctx context.Context, in *SetColorRequest, opts ...grpc.CallOption) (*SetColorResponse, error) { + out := new(SetColorResponse) + err := c.cc.Invoke(ctx, "/color.ColorService/SetColor", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ColorServiceServer is the server API for ColorService service. +type ColorServiceServer interface { + GetColor(context.Context, *GetColorRequest) (*GetColorResponse, error) + SetColor(context.Context, *SetColorRequest) (*SetColorResponse, error) +} + +// UnimplementedColorServiceServer can be embedded to have forward compatible implementations. +type UnimplementedColorServiceServer struct { +} + +func (*UnimplementedColorServiceServer) GetColor(ctx context.Context, req *GetColorRequest) (*GetColorResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetColor not implemented") +} +func (*UnimplementedColorServiceServer) SetColor(ctx context.Context, req *SetColorRequest) (*SetColorResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetColor not implemented") +} + +func RegisterColorServiceServer(s *grpc.Server, srv ColorServiceServer) { + s.RegisterService(&_ColorService_serviceDesc, srv) +} + +func _ColorService_GetColor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetColorRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ColorServiceServer).GetColor(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/color.ColorService/GetColor", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ColorServiceServer).GetColor(ctx, req.(*GetColorRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ColorService_SetColor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetColorRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ColorServiceServer).SetColor(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/color.ColorService/SetColor", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ColorServiceServer).SetColor(ctx, req.(*SetColorRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _ColorService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "color.ColorService", + HandlerType: (*ColorServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetColor", + Handler: _ColorService_GetColor_Handler, + }, + { + MethodName: "SetColor", + Handler: _ColorService_SetColor_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "color.proto", +} diff --git a/walkthroughs/howto-grpc/color_client/go.mod b/walkthroughs/howto-grpc/color_client/go.mod new file mode 100644 index 00000000..d6711492 --- /dev/null +++ b/walkthroughs/howto-grpc/color_client/go.mod @@ -0,0 +1,8 @@ +module github.com/aws/aws-app-mesh-examples/walkthroughs/howto-grpc/color_client + +go 1.13 + +require ( + github.com/golang/protobuf v1.3.2 + google.golang.org/grpc v1.24.0 +) diff --git a/walkthroughs/howto-grpc/color_client/go.sum b/walkthroughs/howto-grpc/color_client/go.sum new file mode 100644 index 00000000..c691fe9d --- /dev/null +++ b/walkthroughs/howto-grpc/color_client/go.sum @@ -0,0 +1,26 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/walkthroughs/howto-grpc/color_client/main.go b/walkthroughs/howto-grpc/color_client/main.go new file mode 100644 index 00000000..49c954bc --- /dev/null +++ b/walkthroughs/howto-grpc/color_client/main.go @@ -0,0 +1,84 @@ +package main + +import ( + "context" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "strings" + + pb "github.com/aws/aws-app-mesh-examples/walkthroughs/howto-grpc/color_client/color" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func main() { + colorHost := os.Getenv("COLOR_HOST") + if colorHost == "" { + log.Fatalf("no COLOR_HOST defined") + } + port := os.Getenv("PORT") + if port == "" { + log.Fatalf("no PORT defined") + } + log.Printf("COLOR_HOST is: %v", colorHost) + log.Printf("PORT is: %v", port) + + // Connect to COLOR_HOST + conn, err := grpc.Dial(colorHost, grpc.WithInsecure()) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer conn.Close() + c := pb.NewColorServiceClient(conn) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + http.HandleFunc("/ping", func(w http.ResponseWriter, req *http.Request) {}) + + http.HandleFunc("/getColor", func(w http.ResponseWriter, req *http.Request) { + log.Printf("Recived getColor request: %v", req) + resp, err := c.GetColor(ctx, &pb.GetColorRequest{}) + if err != nil { + s, _ := status.FromError(err) + if s.Code() != codes.Unimplemented { + http.Error(w, err.Error(), 500) + log.Fatalf("Something really bad happened: %v %v", s.Code(), err) + } + http.Error(w, err.Error(), 404) + log.Printf("Can't find GetColor method: %v %v", s.Code(), err) + return + } + log.Printf("Got GetColor response: %v", resp) + io.WriteString(w, strings.ToLower(resp.GetColor().String())) + }) + + http.HandleFunc("/setColor", func(w http.ResponseWriter, req *http.Request) { + log.Printf("Recieved setColor request: %v", req) + defer req.Body.Close() + color, err := ioutil.ReadAll(req.Body) + if err != nil { + http.Error(w, err.Error(), 400) + log.Printf("Could not read request body: %v", err) + return + } + colorString := strings.ToUpper(string(color)) + resp, err := c.SetColor(ctx, &pb.SetColorRequest{Color: pb.Color(pb.Color_value[colorString])}) + if err != nil { + s, _ := status.FromError(err) + if s.Code() != codes.Unimplemented { + http.Error(w, err.Error(), 500) + log.Fatalf("Something really bad happened: %v %v", s.Code(), err) + } + http.Error(w, err.Error(), 404) + log.Printf("Can't find SetColor method: %v %v", s.Code(), err) + return + } + log.Printf("Got SetColor response: %v", resp) + io.WriteString(w, strings.ToLower(resp.GetColor().String())) + }) + log.Fatal(http.ListenAndServe("0.0.0.0:"+port, nil)) +} diff --git a/walkthroughs/howto-grpc/color_server/Dockerfile b/walkthroughs/howto-grpc/color_server/Dockerfile new file mode 100644 index 00000000..2b1c018e --- /dev/null +++ b/walkthroughs/howto-grpc/color_server/Dockerfile @@ -0,0 +1,24 @@ +FROM golang:1 AS builder + +# Use the default go proxy +ARG GO_PROXY=https://proxy.golang.org + +WORKDIR /go/src/github.com/aws/aws-app-mesh-examples/walkthroughs/howto-grpc/color_server + +# Set the proxies for the go compiler. +ENV GOPROXY=$GO_PROXY + +# go.mod and go.sum go into their own layers. +# This ensures `go mod download` happens only when go.mod and go.sum change. +COPY go.mod . +COPY go.sum . +RUN go mod download + +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix nocgo -o /color_server . + +FROM amazonlinux:2 +RUN yum install -y ca-certificates +COPY --from=builder /color_server /color_server + +ENTRYPOINT ["/color_server"] diff --git a/walkthroughs/howto-grpc/color_server/color/color.pb.go b/walkthroughs/howto-grpc/color_server/color/color.pb.go new file mode 100644 index 00000000..ed5d0bab --- /dev/null +++ b/walkthroughs/howto-grpc/color_server/color/color.pb.go @@ -0,0 +1,369 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: color.proto + +package color + +import ( + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type Color int32 + +const ( + Color_NO_COLOR Color = 0 + Color_RED Color = 1 + Color_BLUE Color = 2 + Color_GREEN Color = 3 + Color_YELLOW Color = 4 + Color_ORANGE Color = 5 + Color_PURPLE Color = 6 + Color_PINK Color = 7 + Color_BLACK Color = 8 + Color_WHITE Color = 9 +) + +var Color_name = map[int32]string{ + 0: "NO_COLOR", + 1: "RED", + 2: "BLUE", + 3: "GREEN", + 4: "YELLOW", + 5: "ORANGE", + 6: "PURPLE", + 7: "PINK", + 8: "BLACK", + 9: "WHITE", +} + +var Color_value = map[string]int32{ + "NO_COLOR": 0, + "RED": 1, + "BLUE": 2, + "GREEN": 3, + "YELLOW": 4, + "ORANGE": 5, + "PURPLE": 6, + "PINK": 7, + "BLACK": 8, + "WHITE": 9, +} + +func (x Color) String() string { + return proto.EnumName(Color_name, int32(x)) +} + +func (Color) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_83c7eb30498dece0, []int{0} +} + +type GetColorRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetColorRequest) Reset() { *m = GetColorRequest{} } +func (m *GetColorRequest) String() string { return proto.CompactTextString(m) } +func (*GetColorRequest) ProtoMessage() {} +func (*GetColorRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_83c7eb30498dece0, []int{0} +} + +func (m *GetColorRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetColorRequest.Unmarshal(m, b) +} +func (m *GetColorRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetColorRequest.Marshal(b, m, deterministic) +} +func (m *GetColorRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetColorRequest.Merge(m, src) +} +func (m *GetColorRequest) XXX_Size() int { + return xxx_messageInfo_GetColorRequest.Size(m) +} +func (m *GetColorRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetColorRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetColorRequest proto.InternalMessageInfo + +type GetColorResponse struct { + Color Color `protobuf:"varint,1,opt,name=color,proto3,enum=color.Color" json:"color,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetColorResponse) Reset() { *m = GetColorResponse{} } +func (m *GetColorResponse) String() string { return proto.CompactTextString(m) } +func (*GetColorResponse) ProtoMessage() {} +func (*GetColorResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_83c7eb30498dece0, []int{1} +} + +func (m *GetColorResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetColorResponse.Unmarshal(m, b) +} +func (m *GetColorResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetColorResponse.Marshal(b, m, deterministic) +} +func (m *GetColorResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetColorResponse.Merge(m, src) +} +func (m *GetColorResponse) XXX_Size() int { + return xxx_messageInfo_GetColorResponse.Size(m) +} +func (m *GetColorResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetColorResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetColorResponse proto.InternalMessageInfo + +func (m *GetColorResponse) GetColor() Color { + if m != nil { + return m.Color + } + return Color_NO_COLOR +} + +type SetColorRequest struct { + Color Color `protobuf:"varint,1,opt,name=color,proto3,enum=color.Color" json:"color,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SetColorRequest) Reset() { *m = SetColorRequest{} } +func (m *SetColorRequest) String() string { return proto.CompactTextString(m) } +func (*SetColorRequest) ProtoMessage() {} +func (*SetColorRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_83c7eb30498dece0, []int{2} +} + +func (m *SetColorRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SetColorRequest.Unmarshal(m, b) +} +func (m *SetColorRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SetColorRequest.Marshal(b, m, deterministic) +} +func (m *SetColorRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SetColorRequest.Merge(m, src) +} +func (m *SetColorRequest) XXX_Size() int { + return xxx_messageInfo_SetColorRequest.Size(m) +} +func (m *SetColorRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SetColorRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_SetColorRequest proto.InternalMessageInfo + +func (m *SetColorRequest) GetColor() Color { + if m != nil { + return m.Color + } + return Color_NO_COLOR +} + +type SetColorResponse struct { + Color Color `protobuf:"varint,1,opt,name=color,proto3,enum=color.Color" json:"color,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SetColorResponse) Reset() { *m = SetColorResponse{} } +func (m *SetColorResponse) String() string { return proto.CompactTextString(m) } +func (*SetColorResponse) ProtoMessage() {} +func (*SetColorResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_83c7eb30498dece0, []int{3} +} + +func (m *SetColorResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SetColorResponse.Unmarshal(m, b) +} +func (m *SetColorResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SetColorResponse.Marshal(b, m, deterministic) +} +func (m *SetColorResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_SetColorResponse.Merge(m, src) +} +func (m *SetColorResponse) XXX_Size() int { + return xxx_messageInfo_SetColorResponse.Size(m) +} +func (m *SetColorResponse) XXX_DiscardUnknown() { + xxx_messageInfo_SetColorResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_SetColorResponse proto.InternalMessageInfo + +func (m *SetColorResponse) GetColor() Color { + if m != nil { + return m.Color + } + return Color_NO_COLOR +} + +func init() { + proto.RegisterEnum("color.Color", Color_name, Color_value) + proto.RegisterType((*GetColorRequest)(nil), "color.GetColorRequest") + proto.RegisterType((*GetColorResponse)(nil), "color.GetColorResponse") + proto.RegisterType((*SetColorRequest)(nil), "color.SetColorRequest") + proto.RegisterType((*SetColorResponse)(nil), "color.SetColorResponse") +} + +func init() { proto.RegisterFile("color.proto", fileDescriptor_83c7eb30498dece0) } + +var fileDescriptor_83c7eb30498dece0 = []byte{ + // 264 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4e, 0xce, 0xcf, 0xc9, + 0x2f, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x73, 0x94, 0x04, 0xb9, 0xf8, 0xdd, + 0x53, 0x4b, 0x9c, 0x41, 0xec, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, 0x25, 0x33, 0x2e, 0x01, + 0x84, 0x50, 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0x90, 0x12, 0x17, 0x44, 0xbd, 0x04, 0xa3, 0x02, + 0xa3, 0x06, 0x9f, 0x11, 0x8f, 0x1e, 0xc4, 0x28, 0x88, 0x22, 0xa8, 0x51, 0xa6, 0x5c, 0xfc, 0xc1, + 0xa8, 0x46, 0x11, 0xa5, 0xcd, 0x8c, 0x4b, 0x20, 0x98, 0x0c, 0xeb, 0xb4, 0xca, 0xb9, 0x58, 0xc1, + 0x7c, 0x21, 0x1e, 0x2e, 0x0e, 0x3f, 0xff, 0x78, 0x67, 0x7f, 0x1f, 0xff, 0x20, 0x01, 0x06, 0x21, + 0x76, 0x2e, 0xe6, 0x20, 0x57, 0x17, 0x01, 0x46, 0x21, 0x0e, 0x2e, 0x16, 0x27, 0x9f, 0x50, 0x57, + 0x01, 0x26, 0x21, 0x4e, 0x2e, 0x56, 0xf7, 0x20, 0x57, 0x57, 0x3f, 0x01, 0x66, 0x21, 0x2e, 0x2e, + 0xb6, 0x48, 0x57, 0x1f, 0x1f, 0xff, 0x70, 0x01, 0x16, 0x10, 0xdb, 0x3f, 0xc8, 0xd1, 0xcf, 0xdd, + 0x55, 0x80, 0x15, 0xc4, 0x0e, 0x08, 0x0d, 0x0a, 0xf0, 0x71, 0x15, 0x60, 0x03, 0x69, 0x0c, 0xf0, + 0xf4, 0xf3, 0x16, 0x60, 0x07, 0x69, 0x74, 0xf2, 0x71, 0x74, 0xf6, 0x16, 0xe0, 0x00, 0x31, 0xc3, + 0x3d, 0x3c, 0x43, 0x5c, 0x05, 0x38, 0x8d, 0x7a, 0x18, 0xb9, 0x78, 0xc0, 0x36, 0x07, 0xa7, 0x16, + 0x95, 0x65, 0x26, 0xa7, 0x0a, 0xd9, 0x72, 0x71, 0xc0, 0x02, 0x4c, 0x48, 0x0c, 0xea, 0x54, 0xb4, + 0x40, 0x95, 0x12, 0xc7, 0x10, 0x87, 0x78, 0x55, 0x89, 0x01, 0xa4, 0x3d, 0x18, 0x5d, 0x7b, 0x30, + 0x0e, 0xed, 0xc1, 0x18, 0xda, 0x93, 0xd8, 0xc0, 0xf1, 0x69, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, + 0x42, 0x63, 0xf3, 0xd5, 0xde, 0x01, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// ColorServiceClient is the client API for ColorService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ColorServiceClient interface { + GetColor(ctx context.Context, in *GetColorRequest, opts ...grpc.CallOption) (*GetColorResponse, error) + SetColor(ctx context.Context, in *SetColorRequest, opts ...grpc.CallOption) (*SetColorResponse, error) +} + +type colorServiceClient struct { + cc *grpc.ClientConn +} + +func NewColorServiceClient(cc *grpc.ClientConn) ColorServiceClient { + return &colorServiceClient{cc} +} + +func (c *colorServiceClient) GetColor(ctx context.Context, in *GetColorRequest, opts ...grpc.CallOption) (*GetColorResponse, error) { + out := new(GetColorResponse) + err := c.cc.Invoke(ctx, "/color.ColorService/GetColor", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *colorServiceClient) SetColor(ctx context.Context, in *SetColorRequest, opts ...grpc.CallOption) (*SetColorResponse, error) { + out := new(SetColorResponse) + err := c.cc.Invoke(ctx, "/color.ColorService/SetColor", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ColorServiceServer is the server API for ColorService service. +type ColorServiceServer interface { + GetColor(context.Context, *GetColorRequest) (*GetColorResponse, error) + SetColor(context.Context, *SetColorRequest) (*SetColorResponse, error) +} + +// UnimplementedColorServiceServer can be embedded to have forward compatible implementations. +type UnimplementedColorServiceServer struct { +} + +func (*UnimplementedColorServiceServer) GetColor(ctx context.Context, req *GetColorRequest) (*GetColorResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetColor not implemented") +} +func (*UnimplementedColorServiceServer) SetColor(ctx context.Context, req *SetColorRequest) (*SetColorResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetColor not implemented") +} + +func RegisterColorServiceServer(s *grpc.Server, srv ColorServiceServer) { + s.RegisterService(&_ColorService_serviceDesc, srv) +} + +func _ColorService_GetColor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetColorRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ColorServiceServer).GetColor(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/color.ColorService/GetColor", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ColorServiceServer).GetColor(ctx, req.(*GetColorRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ColorService_SetColor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetColorRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ColorServiceServer).SetColor(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/color.ColorService/SetColor", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ColorServiceServer).SetColor(ctx, req.(*SetColorRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _ColorService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "color.ColorService", + HandlerType: (*ColorServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetColor", + Handler: _ColorService_GetColor_Handler, + }, + { + MethodName: "SetColor", + Handler: _ColorService_SetColor_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "color.proto", +} diff --git a/walkthroughs/howto-grpc/color_server/go.mod b/walkthroughs/howto-grpc/color_server/go.mod new file mode 100644 index 00000000..ad1fc583 --- /dev/null +++ b/walkthroughs/howto-grpc/color_server/go.mod @@ -0,0 +1,8 @@ +module github.com/aws/aws-app-mesh-examples/walkthroughs/howto-grpc/color_server + +go 1.13 + +require ( + github.com/golang/protobuf v1.3.2 + google.golang.org/grpc v1.24.0 +) diff --git a/walkthroughs/howto-grpc/color_server/go.sum b/walkthroughs/howto-grpc/color_server/go.sum new file mode 100644 index 00000000..c691fe9d --- /dev/null +++ b/walkthroughs/howto-grpc/color_server/go.sum @@ -0,0 +1,26 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/walkthroughs/howto-grpc/color_server/main.go b/walkthroughs/howto-grpc/color_server/main.go new file mode 100644 index 00000000..2ba74c03 --- /dev/null +++ b/walkthroughs/howto-grpc/color_server/main.go @@ -0,0 +1,65 @@ +package main + +import ( + "context" + "log" + "net" + "os" + "strings" + + pb "github.com/aws/aws-app-mesh-examples/walkthroughs/howto-grpc/color_server/color" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + health "google.golang.org/grpc/health/grpc_health_v1" + "google.golang.org/grpc/status" +) + +type server struct { + color pb.Color +} + +func (s *server) GetColor(ctx context.Context, in *pb.GetColorRequest) (*pb.GetColorResponse, error) { + log.Printf("Received GetColor request") + return &pb.GetColorResponse{Color: s.color}, nil +} + +func (s *server) SetColor(ctx context.Context, in *pb.SetColorRequest) (*pb.SetColorResponse, error) { + log.Printf("Received SetColor request: %v", in) + oldColor := s.color + s.color = in.Color + return &pb.SetColorResponse{Color: oldColor}, nil +} + +func (s *server) Check(ctx context.Context, in *health.HealthCheckRequest) (*health.HealthCheckResponse, error) { + log.Printf("Received Check request: %v", in) + return &health.HealthCheckResponse{Status: health.HealthCheckResponse_SERVING}, nil +} + +func (s *server) Watch(in *health.HealthCheckRequest, _ health.Health_WatchServer) error { + log.Printf("Received Watch request: %v", in) + return status.Error(codes.Unimplemented, "unimplemented") +} + +func main() { + color := os.Getenv("COLOR") + if color == "" { + log.Fatalf("no COLOR defined") + } + port := os.Getenv("PORT") + if port == "" { + log.Fatalf("no PORT defined") + } + log.Printf("COLOR is: %v", color) + log.Printf("PORT is: %v", port) + lis, err := net.Listen("tcp", "0.0.0.0:"+port) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + s := grpc.NewServer() + colorValue := pb.Color(pb.Color_value[strings.ToUpper(color)]) + pb.RegisterColorServiceServer(s, &server{color: colorValue}) + health.RegisterHealthServer(s, &server{color: colorValue}) + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +} diff --git a/walkthroughs/howto-grpc/deploy.sh b/walkthroughs/howto-grpc/deploy.sh new file mode 100755 index 00000000..28024792 --- /dev/null +++ b/walkthroughs/howto-grpc/deploy.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash + +set -e + +if [ -z $PROJECT_NAME ]; then + echo "PROJECT_NAME environment variable is not set." + exit 1 +fi + + +if [ -z $AWS_ACCOUNT_ID ]; then + echo "AWS_ACCOUNT_ID environment variable is not set." + exit 1 +fi + +if [ -z $AWS_DEFAULT_REGION ]; then + echo "AWS_DEFAULT_REGION environment variable is not set." + exit 1 +fi + +if [ -z $ENVOY_IMAGE ]; then + echo "ENVOY_IMAGE environment variable is not set to App Mesh Envoy, see https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy.html" + exit 1 +fi + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +ECR_IMAGE_PREFIX=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${PROJECT_NAME} + +deploy_images() { + for app in color_client color_server; do + aws ecr describe-repositories --repository-name ${PROJECT_NAME}/${app} >/dev/null 2>&1 || aws ecr create-repository --repository-name ${PROJECT_NAME}/${app} + docker build -t ${ECR_IMAGE_PREFIX}/${app} ${DIR}/${app} --build-arg GO_PROXY=${GO_PROXY:-"https://proxy.golang.org"} + $(aws ecr get-login --no-include-email) + docker push ${ECR_IMAGE_PREFIX}/${app} + done +} + +deploy_infra() { + aws cloudformation deploy \ + --no-fail-on-empty-changeset \ + --stack-name "${PROJECT_NAME}-infra"\ + --template-file "${DIR}/infra.yaml" \ + --capabilities CAPABILITY_IAM \ + --parameter-overrides "ProjectName=${PROJECT_NAME}" +} + +deploy_app() { + aws cloudformation deploy \ + --no-fail-on-empty-changeset \ + --stack-name "${PROJECT_NAME}-app" \ + --template-file "${DIR}/app.yaml" \ + --capabilities CAPABILITY_IAM \ + --parameter-overrides "ProjectName=${PROJECT_NAME}" "EnvoyImage=${ENVOY_IMAGE}" "ColorClientImage=${ECR_IMAGE_PREFIX}/color_client" "ColorServerImage=${ECR_IMAGE_PREFIX}/color_server" +} + +deploy_mesh() { + mesh_name=$1 + aws appmesh-preview create-mesh --mesh-name $mesh_name --cli-input-json file://${DIR}/mesh/mesh.json + aws appmesh-preview create-virtual-node --mesh-name $mesh_name --cli-input-json file://${DIR}/mesh/color-client-node.json + aws appmesh-preview create-virtual-node --mesh-name $mesh_name --cli-input-json file://${DIR}/mesh/color-server-node.json + aws appmesh-preview create-virtual-router --mesh-name $mesh_name --cli-input-json file://${DIR}/mesh/virtual-router.json + aws appmesh-preview create-virtual-service --mesh-name $mesh_name --cli-input-json file://${DIR}/mesh/virtual-service.json + aws appmesh-preview create-route --mesh-name $mesh_name --cli-input-json file://${DIR}/mesh/route.json +} + +print_endpoint() { + echo "Public endpoint:" + prefix=$(aws cloudformation describe-stacks \ + --stack-name="${PROJECT_NAME}-app" \ + --query="Stacks[0].Outputs[?OutputKey=='PublicEndpoint'].OutputValue" \ + --output=text) + echo "${prefix}" +} + +deploy_stacks() { + echo "deploy images..." + deploy_images + + echo "deploy infra..." + deploy_infra + + echo "deploy mesh..." + deploy_mesh "${PROJECT_NAME}-mesh" + + echo "deploy app..." + deploy_app + + print_endpoint +} + +delete_cfn_stack() { + stack_name=$1 + aws cloudformation delete-stack --stack-name $stack_name + echo 'Waiting for the stack to be deleted, this may take a few minutes...' + aws cloudformation wait stack-delete-complete --stack-name $stack_name + echo 'Done' +} + +delete_mesh() { + mesh_name=$1 + aws appmesh-preview delete-route --mesh-name $mesh_name --virtual-router-name virtual-router --route-name route + aws appmesh-preview delete-virtual-service --mesh-name $mesh_name --virtual-service-name color_server.grpc.local + aws appmesh-preview delete-virtual-router --mesh-name $mesh_name --virtual-router-name virtual-router + aws appmesh-preview delete-virtual-node --mesh-name $mesh_name --virtual-node-name color_server + aws appmesh-preview delete-virtual-node --mesh-name $mesh_name --virtual-node-name color_client + aws appmesh-preview delete-mesh --mesh-name $mesh_name +} + +delete_stacks() { + echo "delete app..." + delete_cfn_stack "${PROJECT_NAME}-app" + + echo "delete infra..." + delete_cfn_stack "${PROJECT_NAME}-infra" + + echo "delete mesh..." + delete_mesh "${PROJECT_NAME}-mesh" +} + +action=${1:-"deploy"} +if [ "$action" == "delete" ]; then + delete_stacks + exit 0 +fi + +deploy_stacks diff --git a/walkthroughs/howto-grpc/generate_protos.sh b/walkthroughs/howto-grpc/generate_protos.sh new file mode 100755 index 00000000..1e3e8f0f --- /dev/null +++ b/walkthroughs/howto-grpc/generate_protos.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -e + +protoc ./color.proto --go_out=plugins=grpc:./color_client/color +protoc ./color.proto --go_out=plugins=grpc:./color_server/color diff --git a/walkthroughs/howto-grpc/howto-grpc.png b/walkthroughs/howto-grpc/howto-grpc.png new file mode 100644 index 00000000..fbff1cc7 Binary files /dev/null and b/walkthroughs/howto-grpc/howto-grpc.png differ diff --git a/walkthroughs/howto-grpc/infra.yaml b/walkthroughs/howto-grpc/infra.yaml new file mode 100644 index 00000000..823a4e7a --- /dev/null +++ b/walkthroughs/howto-grpc/infra.yaml @@ -0,0 +1,250 @@ +Parameters: + ProjectName: + Type: String + Description: Project name to link stacks + + VpcCIDR: + Description: Please enter the IP range (CIDR notation) for this VPC + Type: String + Default: 10.0.0.0/16 + + PublicSubnet1CIDR: + Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone + Type: String + Default: 10.0.0.0/19 + + PublicSubnet2CIDR: + Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone + Type: String + Default: 10.0.32.0/19 + + PrivateSubnet1CIDR: + Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone + Type: String + Default: 10.0.64.0/19 + + PrivateSubnet2CIDR: + Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone + Type: String + Default: 10.0.96.0/19 + +Resources: + VPC: + Type: AWS::EC2::VPC + Properties: + CidrBlock: !Ref VpcCIDR + EnableDnsHostnames: true + Tags: + - Key: Name + Value: !Ref ProjectName + + InternetGateway: + Type: AWS::EC2::InternetGateway + Properties: + Tags: + - Key: Name + Value: !Ref ProjectName + + InternetGatewayAttachment: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + InternetGatewayId: !Ref InternetGateway + VpcId: !Ref VPC + + PublicSubnet1: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + AvailabilityZone: !Select [ 0, !GetAZs '' ] + CidrBlock: !Ref PublicSubnet1CIDR + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: !Sub '${ProjectName} Public Subnet (AZ1)' + + PublicSubnet2: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + AvailabilityZone: !Select [ 1, !GetAZs '' ] + CidrBlock: !Ref PublicSubnet2CIDR + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: !Sub '${ProjectName} Public Subnet (AZ2)' + + PrivateSubnet1: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + AvailabilityZone: !Select [ 0, !GetAZs '' ] + CidrBlock: !Ref PrivateSubnet1CIDR + MapPublicIpOnLaunch: false + Tags: + - Key: Name + Value: !Sub '${ProjectName} Private Subnet (AZ1)' + + PrivateSubnet2: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + AvailabilityZone: !Select [ 1, !GetAZs '' ] + CidrBlock: !Ref PrivateSubnet2CIDR + MapPublicIpOnLaunch: false + Tags: + - Key: Name + Value: !Sub '${ProjectName} Private Subnet (AZ2)' + + NatGateway1EIP: + Type: AWS::EC2::EIP + DependsOn: InternetGatewayAttachment + Properties: + Domain: vpc + + NatGateway2EIP: + Type: AWS::EC2::EIP + DependsOn: InternetGatewayAttachment + Properties: + Domain: vpc + + NatGateway1: + Type: AWS::EC2::NatGateway + Properties: + AllocationId: !GetAtt NatGateway1EIP.AllocationId + SubnetId: !Ref PublicSubnet1 + + NatGateway2: + Type: AWS::EC2::NatGateway + Properties: + AllocationId: !GetAtt NatGateway2EIP.AllocationId + SubnetId: !Ref PublicSubnet2 + + PublicRouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub '${ProjectName} Public Routes' + + DefaultPublicRoute: + Type: AWS::EC2::Route + DependsOn: InternetGatewayAttachment + Properties: + RouteTableId: !Ref PublicRouteTable + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref InternetGateway + + PublicSubnet1RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + RouteTableId: !Ref PublicRouteTable + SubnetId: !Ref PublicSubnet1 + + PublicSubnet2RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + RouteTableId: !Ref PublicRouteTable + SubnetId: !Ref PublicSubnet2 + + PrivateRouteTable1: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub '${ProjectName} Private Routes (AZ1)' + + DefaultPrivateRoute1: + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref PrivateRouteTable1 + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGateway1 + + PrivateSubnet1RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + RouteTableId: !Ref PrivateRouteTable1 + SubnetId: !Ref PrivateSubnet1 + + PrivateRouteTable2: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub '${ProjectName} Private Routes (AZ2)' + + DefaultPrivateRoute2: + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref PrivateRouteTable2 + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGateway2 + + PrivateSubnet2RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + RouteTableId: !Ref PrivateRouteTable2 + SubnetId: !Ref PrivateSubnet2 + + ECSCluster: + Type: AWS::ECS::Cluster + Properties: + ClusterName: !Ref ProjectName + + CloudMapNamespace: + Type: AWS::ServiceDiscovery::PrivateDnsNamespace + Properties: + Name: grpc.local + Vpc: !Ref VPC + +Outputs: + VPC: + Description: A reference to the created VPC + Value: !Ref VPC + Export: + Name: !Sub '${ProjectName}:VPC' + + PublicSubnet1: + Description: A reference to the public subnet in the 1st Availability Zone + Value: !Ref PublicSubnet1 + Export: + Name: !Sub '${ProjectName}:PublicSubnet1' + + PublicSubnet2: + Description: A reference to the public subnet in the 2nd Availability Zone + Value: !Ref PublicSubnet2 + Export: + Name: !Sub '${ProjectName}:PublicSubnet2' + + PrivateSubnet1: + Description: A reference to the private subnet in the 1st Availability Zone + Value: !Ref PrivateSubnet1 + Export: + Name: !Sub '${ProjectName}:PrivateSubnet1' + + PrivateSubnet2: + Description: A reference to the private subnet in the 2nd Availability Zone + Value: !Ref PrivateSubnet2 + Export: + Name: !Sub '${ProjectName}:PrivateSubnet2' + + VpcCIDR: + Description: VPC CIDR + Value: !Ref VpcCIDR + Export: + Name: !Sub '${ProjectName}:VpcCIDR' + + ECSCluster: + Description: A reference to the ECS cluster + Value: !Ref ECSCluster + Export: + Name: !Sub '${ProjectName}:ECSCluster' + + CloudMapNamespaceId: + Description: The id of to the Cloud Map namespace + Value: !GetAtt CloudMapNamespace.Id + Export: + Name: !Sub '${ProjectName}:CloudMapNamespaceId' diff --git a/walkthroughs/howto-grpc/mesh/color-client-node.json b/walkthroughs/howto-grpc/mesh/color-client-node.json new file mode 100644 index 00000000..ab5eda1d --- /dev/null +++ b/walkthroughs/howto-grpc/mesh/color-client-node.json @@ -0,0 +1,26 @@ +{ + "spec": { + "backends": [ + { + "virtualService": { + "virtualServiceName": "color_server.grpc.local" + } + } + ], + "listeners": [ + { + "portMapping": { + "port": 8080, + "protocol": "http" + } + } + ], + "serviceDiscovery": { + "awsCloudMap": { + "namespaceName": "grpc.local", + "serviceName": "color_client" + } + } + }, + "virtualNodeName": "color_client" +} diff --git a/walkthroughs/howto-grpc/mesh/color-server-node.json b/walkthroughs/howto-grpc/mesh/color-server-node.json new file mode 100644 index 00000000..179653a5 --- /dev/null +++ b/walkthroughs/howto-grpc/mesh/color-server-node.json @@ -0,0 +1,27 @@ +{ + "spec": { + "listeners": [ + { + "healthCheck": { + "healthyThreshold": 2, + "intervalMillis": 5000, + "port": 8080, + "protocol": "grpc", + "timeoutMillis": 2000, + "unhealthyThreshold": 3 + }, + "portMapping": { + "port": 8080, + "protocol": "grpc" + } + } + ], + "serviceDiscovery": { + "awsCloudMap": { + "namespaceName": "grpc.local", + "serviceName": "color_server" + } + } + }, + "virtualNodeName": "color_server" +} diff --git a/walkthroughs/howto-grpc/mesh/mesh.json b/walkthroughs/howto-grpc/mesh/mesh.json new file mode 100644 index 00000000..0cede390 --- /dev/null +++ b/walkthroughs/howto-grpc/mesh/mesh.json @@ -0,0 +1,3 @@ +{ + "spec": {} +} diff --git a/walkthroughs/howto-grpc/mesh/route-all-methods.json b/walkthroughs/howto-grpc/mesh/route-all-methods.json new file mode 100644 index 00000000..10f00116 --- /dev/null +++ b/walkthroughs/howto-grpc/mesh/route-all-methods.json @@ -0,0 +1,19 @@ +{ + "routeName": "route", + "spec": { + "grpcRoute": { + "action": { + "weightedTargets": [ + { + "virtualNode": "color_server", + "weight": 100 + } + ] + }, + "match": { + "serviceName": "color.ColorService" + } + } + }, + "virtualRouterName": "virtual-router" +} diff --git a/walkthroughs/howto-grpc/mesh/route.json b/walkthroughs/howto-grpc/mesh/route.json new file mode 100644 index 00000000..47220b6b --- /dev/null +++ b/walkthroughs/howto-grpc/mesh/route.json @@ -0,0 +1,20 @@ +{ + "routeName": "route", + "spec": { + "grpcRoute": { + "action": { + "weightedTargets": [ + { + "virtualNode": "color_server", + "weight": 100 + } + ] + }, + "match": { + "serviceName": "color.ColorService", + "methodName": "GetColor" + } + } + }, + "virtualRouterName": "virtual-router" +} diff --git a/walkthroughs/howto-grpc/mesh/virtual-router.json b/walkthroughs/howto-grpc/mesh/virtual-router.json new file mode 100644 index 00000000..d25cca9a --- /dev/null +++ b/walkthroughs/howto-grpc/mesh/virtual-router.json @@ -0,0 +1,13 @@ +{ + "spec": { + "listeners": [ + { + "portMapping": { + "port": 8080, + "protocol": "grpc" + } + } + ] + }, + "virtualRouterName": "virtual-router" +} diff --git a/walkthroughs/howto-grpc/mesh/virtual-service.json b/walkthroughs/howto-grpc/mesh/virtual-service.json new file mode 100644 index 00000000..0a308ea4 --- /dev/null +++ b/walkthroughs/howto-grpc/mesh/virtual-service.json @@ -0,0 +1,10 @@ +{ + "spec": { + "provider": { + "virtualRouter": { + "virtualRouterName": "virtual-router" + } + } + }, + "virtualServiceName": "color_server.grpc.local" +}