From be61295a96af48b8f4d3f7ce45311911d24a72ba Mon Sep 17 00:00:00 2001 From: Julian <> Date: Wed, 23 Feb 2022 09:52:30 +0100 Subject: [PATCH] sample contribution --- .gitignore | 215 ++++++++++++++++++++++++++++++++++++++++++ README.md | 74 +++++++++++++-- cdk/.gitignore | 10 ++ cdk/.npmignore | 6 ++ cdk/README.md | 14 +++ cdk/bin/cdk.ts | 38 ++++++++ cdk/cdk.json | 26 +++++ cdk/jest.config.js | 8 ++ cdk/lib/demo-stack.ts | 143 ++++++++++++++++++++++++++++ cdk/package.json | 27 ++++++ cdk/test/cdk.test.ts | 35 +++++++ cdk/tsconfig.json | 30 ++++++ client/Dockerfile | 20 ++++ client/balancer.go | 111 ++++++++++++++++++++++ client/main.go | 182 +++++++++++++++++++++++++++++++++++ client/resolver.go | 119 +++++++++++++++++++++++ go.mod | 28 ++++++ go.sum | 148 +++++++++++++++++++++++++++++ proto/responder.proto | 40 ++++++++ server/Dockerfile | 22 +++++ server/main.go | 120 +++++++++++++++++++++++ 21 files changed, 1407 insertions(+), 9 deletions(-) create mode 100644 .gitignore create mode 100644 cdk/.gitignore create mode 100644 cdk/.npmignore create mode 100644 cdk/README.md create mode 100644 cdk/bin/cdk.ts create mode 100644 cdk/cdk.json create mode 100644 cdk/jest.config.js create mode 100644 cdk/lib/demo-stack.ts create mode 100644 cdk/package.json create mode 100644 cdk/test/cdk.test.ts create mode 100644 cdk/tsconfig.json create mode 100644 client/Dockerfile create mode 100644 client/balancer.go create mode 100644 client/main.go create mode 100644 client/resolver.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 proto/responder.proto create mode 100644 server/Dockerfile create mode 100644 server/main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..327c62c --- /dev/null +++ b/.gitignore @@ -0,0 +1,215 @@ +/client/client +/server/server + + +# Created by https://www.toptal.com/developers/gitignore/api/go,terraform,intellij+all,visualstudiocode,macos +# Edit at https://www.toptal.com/developers/gitignore?templates=go,terraform,intellij+all,visualstudiocode,macos + +### Go ### +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +### Go Patch ### +/vendor/ +/Godeps/ + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/* + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Terraform ### +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# Support for Project snippet scope + +# End of https://www.toptal.com/developers/gitignore/api/go,terraform,intellij+all,visualstudiocode,macos diff --git a/README.md b/README.md index 7f92204..0b3fb82 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,73 @@ -## My Project +# Using AWS Cloud Map as gRPC name resolver -TODO: Fill this README out! +This repository show how AWS Cloud Map can be used as a name resolver in gRPC clients. Using the AWS Cloud Map HTTP API instead of DNS based resolution allows to create custom client-side loadbalancing policies leveraging the metadata associated with registered instances of AWS Cloud Map services. -Be sure to: +Potential benefits of this approach are: +* Cutting out one or more loadbalancers which would be fronting Fargate services. This reduces cost, removes a network hop and may reduce efforts to maintain the loadbalancer. +* Options for advanced loadbalancing scenarios. This sample shows, as an example, how AWS Cloud Map metadata can be used to prefer calling service instances which reside in the same az as the client. -* Change the title in this README -* Edit your repository description on GitHub +## Running the sample -## Security +Build the client and server: -See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. +```bash +#--- +cd client +# generate gRPC code +mkdir pb +go generate +# build executable +GOOS=linux GOARCH=amd64 go build -o client . -## License +#--- +cd server +# generate gRPC code +mkdir pb +go generate +# build executable +GOOS=linux GOARCH=amd64 go build -o server . +``` -This library is licensed under the MIT-0 License. See the LICENSE file. +Provision the CDK app. Make sure docker is running as the Docker images are built as CDK assets during CDK app execution. +Account and Region will be picked up during CDK app execution. + +**Important**: Region **must** be eu-central-1 currently, as this region is used in the coding. + +```bash +cd cdk +cdk deploy +``` + +## Create sample data + +After the CDK app is deployed, test drive with a couple of calls. It is best to use tools like `hey` or `ab` to provide a large enough number of calls to see the loadbalancing preference show up. + +Obtain the Public IP of the single task of the client service from the ECS console + +```bash +# with curl +curl http://:8080/describe + +# with hey, runs a total of 200 requests by default over 10s +hey -c 2 -q 10 -m GET http://:8080/describe +# this will take a couple of seconds to run without giving an output +``` + +## Observe custom loadbalancing policy + +We route approx 50% of requests to the same availability zone (see balancer.go:53). + +Use CloudWatch Insights for the log group and run the following query on log group `grpcdemo`: + +``` +fields @timestamp, @message +| limit 1000 +| filter @message like 'server response from az' +| parse 'server response from az: *' as az +| stats count(az) as az_calls by az +``` + +**Be sure to allow a couple of minutes for all logsto arrive, otherwise the count will not be correct.** + +You should see approx. 50% of calls going to the AZ in which the single task of service `client` is located in. \ No newline at end of file diff --git a/cdk/.gitignore b/cdk/.gitignore new file mode 100644 index 0000000..5fb6f1f --- /dev/null +++ b/cdk/.gitignore @@ -0,0 +1,10 @@ +*.js +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out + +cdk.context.json \ No newline at end of file diff --git a/cdk/.npmignore b/cdk/.npmignore new file mode 100644 index 0000000..c1d6d45 --- /dev/null +++ b/cdk/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/cdk/README.md b/cdk/README.md new file mode 100644 index 0000000..3247665 --- /dev/null +++ b/cdk/README.md @@ -0,0 +1,14 @@ +# Welcome to your CDK TypeScript project! + +This is a blank project for TypeScript development with CDK. + +The `cdk.json` file tells the CDK Toolkit how to execute your app. + +## Useful commands + + * `npm run build` compile typescript to js + * `npm run watch` watch for changes and compile + * `npm run test` perform the jest unit tests + * `cdk deploy` deploy this stack to your default AWS account/region + * `cdk diff` compare deployed stack with current state + * `cdk synth` emits the synthesized CloudFormation template diff --git a/cdk/bin/cdk.ts b/cdk/bin/cdk.ts new file mode 100644 index 0000000..f1e93f6 --- /dev/null +++ b/cdk/bin/cdk.ts @@ -0,0 +1,38 @@ +#!/usr/bin/env node +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: MIT-0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { DemoStack } from '../lib/demo-stack'; + +const app = new cdk.App(); +new DemoStack(app, 'DemoStack', { + /* If you don't specify 'env', this stack will be environment-agnostic. + * Account/Region-dependent features and context lookups will not work, + * but a single synthesized template can be deployed anywhere. */ + + /* Uncomment the next line to specialize this stack for the AWS Account + * and Region that are implied by the current CLI configuration. */ + env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, + + /* Uncomment the next line if you know exactly what Account and Region you + * want to deploy the stack to. */ + // env: { account: '123456789012', region: 'us-east-1' }, + + /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ +}); \ No newline at end of file diff --git a/cdk/cdk.json b/cdk/cdk.json new file mode 100644 index 0000000..6247cdb --- /dev/null +++ b/cdk/cdk.json @@ -0,0 +1,26 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/core:stackRelativeExports": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-lambda:recognizeVersionProps": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true + } +} diff --git a/cdk/jest.config.js b/cdk/jest.config.js new file mode 100644 index 0000000..08263b8 --- /dev/null +++ b/cdk/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + testEnvironment: 'node', + roots: ['/test'], + testMatch: ['**/*.test.ts'], + transform: { + '^.+\\.tsx?$': 'ts-jest' + } +}; diff --git a/cdk/lib/demo-stack.ts b/cdk/lib/demo-stack.ts new file mode 100644 index 0000000..15ab1d0 --- /dev/null +++ b/cdk/lib/demo-stack.ts @@ -0,0 +1,143 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: MIT-0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +import {RemovalPolicy, Stack, StackProps} from 'aws-cdk-lib'; +import {Construct} from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import {Peer, Port, SubnetType} from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import {FargatePlatformVersion, LogDriver} from 'aws-cdk-lib/aws-ecs'; +import * as ecra from 'aws-cdk-lib/aws-ecr-assets'; +import {NamespaceType} from "aws-cdk-lib/aws-servicediscovery"; +import {LogGroup} from "aws-cdk-lib/aws-logs"; +import {Effect, PolicyStatement} from "aws-cdk-lib/aws-iam"; + +export class DemoStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const vpc = new ec2.Vpc(this, "vpc", { + vpcName: "demovpc", + maxAzs: 3, + natGateways: 0, + subnetConfiguration: [{ + subnetType: SubnetType.PUBLIC, + name: "public" + }] + }); + + const cluster = new ecs.Cluster(this, "democluster", { + vpc, + clusterName: "democluster", + defaultCloudMapNamespace: { + name: "grpc.demo", + type: NamespaceType.HTTP + } + }); + + const clientSg = new ec2.SecurityGroup(this, "client-sg", { + vpc, + securityGroupName: "clientSecurityGroup", + allowAllOutbound: true + }); + clientSg.addIngressRule(Peer.anyIpv4(), Port.tcp(8080)); + + const serverSg = new ec2.SecurityGroup(this, "server-sg", { + vpc, + securityGroupName: "serverSecurityGroup" + }); + serverSg.addIngressRule(clientSg, Port.allTcp()) + + const loggroup = new LogGroup(this, "loggroup", { + logGroupName: "grpcdemo", + retention: 3, + removalPolicy: RemovalPolicy.DESTROY + }); + + const serverImage = new ecra.DockerImageAsset(this, "serverimage", { + directory: "./../server", + }); + + const serverTask = new ecs.FargateTaskDefinition(this, "serverTask", { + cpu: 512, + memoryLimitMiB: 1024, + family: "server", + }); + serverTask.addContainer("main", { + image: ecs.ContainerImage.fromDockerImageAsset(serverImage), + logging: LogDriver.awsLogs({ + logGroup: loggroup, + streamPrefix: "server" + }) + }); + + const serverService = new ecs.FargateService(this, "serverSvc", { + cluster, + assignPublicIp: true, + serviceName: "server", + cloudMapOptions: { + name: "server", + containerPort: 9000 + }, + taskDefinition: serverTask, + desiredCount: 3, + platformVersion: FargatePlatformVersion.VERSION1_4, + securityGroups: [serverSg], + }); + + const clientImage = new ecra.DockerImageAsset(this, "clientimage", { + directory: "./../client", + }); + + const clientTask = new ecs.FargateTaskDefinition(this, "clientTask", { + cpu: 512, + memoryLimitMiB: 1024, + family: "client", + }); + clientTask.addContainer("main", { + image: ecs.ContainerImage.fromDockerImageAsset(clientImage), + portMappings: [ + { + containerPort: 8080 + } + ], + logging: LogDriver.awsLogs({ + logGroup: loggroup, + streamPrefix: "client" + }) + }); + clientTask.addToTaskRolePolicy(new PolicyStatement({ + effect: Effect.ALLOW, + actions: [ + "servicediscovery:DiscoverInstances" + ], + resources: ["*"] + })); + + const clientService = new ecs.FargateService(this, "clientSvc", { + cluster, + assignPublicIp: true, + serviceName: "client", + taskDefinition: clientTask, + desiredCount: 1, + platformVersion: FargatePlatformVersion.VERSION1_4, + securityGroups: [clientSg] + }); + + clientService.node.addDependency(serverService); + } +} diff --git a/cdk/package.json b/cdk/package.json new file mode 100644 index 0000000..999a13e --- /dev/null +++ b/cdk/package.json @@ -0,0 +1,27 @@ +{ + "name": "cdk", + "version": "0.1.0", + "bin": { + "cdk": "bin/cdk.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk" + }, + "devDependencies": { + "@types/jest": "^26.0.10", + "@types/node": "10.17.27", + "jest": "^26.4.2", + "ts-jest": "^26.2.0", + "aws-cdk": "2.3.0", + "ts-node": "^9.0.0", + "typescript": "~3.9.7" + }, + "dependencies": { + "aws-cdk-lib": "2.3.0", + "constructs": "^10.0.0", + "source-map-support": "^0.5.16" + } +} diff --git a/cdk/test/cdk.test.ts b/cdk/test/cdk.test.ts new file mode 100644 index 0000000..8154e28 --- /dev/null +++ b/cdk/test/cdk.test.ts @@ -0,0 +1,35 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: MIT-0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// import * as cdk from 'aws-cdk-lib'; +// import { Template } from 'aws-cdk-lib/assertions'; +// import * as Cdk from '../lib/cdk-stack'; + +// example test. To run these tests, uncomment this file along with the +// example resource in lib/cdk-stack.ts +test('SQS Queue Created', () => { +// const app = new cdk.App(); +// // WHEN +// const stack = new Cdk.CdkStack(app, 'MyTestStack'); +// // THEN +// const template = Template.fromStack(stack); + +// template.hasResourceProperties('AWS::SQS::Queue', { +// VisibilityTimeout: 300 +// }); +}); diff --git a/cdk/tsconfig.json b/cdk/tsconfig.json new file mode 100644 index 0000000..9f8e8be --- /dev/null +++ b/cdk/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2018", + "module": "commonjs", + "lib": [ + "es2018" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} diff --git a/client/Dockerfile b/client/Dockerfile new file mode 100644 index 0000000..03cbf0c --- /dev/null +++ b/client/Dockerfile @@ -0,0 +1,20 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this +# software and associated documentation files (the "Software"), to deal in the Software +# without restriction, including without limitation the rights to use, copy, modify, +# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +FROM registry.access.redhat.com/ubi8/ubi-minimal:latest + +COPY ./client / + +ENTRYPOINT ["/client"] \ No newline at end of file diff --git a/client/balancer.go b/client/balancer.go new file mode 100644 index 0000000..48a9261 --- /dev/null +++ b/client/balancer.go @@ -0,0 +1,111 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: MIT-0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package main + +import ( + "log" + "math/rand" + "time" + + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/balancer/base" +) + +// BalancerName is used for registration and lookup of the loadbalancer +const BalancerName = "cm-az-aware" + +// NewBalancerBuilder provides a new balancer.Builder to configure gRPC channels +func NewBalancerBuilder() balancer.Builder { + return base.NewBalancerBuilder(BalancerName, &Picker{}, base.Config{HealthCheck: false}) +} + +// Picker implements the balancer.Picker interface as well as the PickerBuilder interface. +// +// With PickerBuilder a new Picker instance can be constructed, which will hold SubConns. Within +// the balancer.Picker#Pick method the actual SubConn for a gRPC call is selected. +type Picker struct { + subConns []subConn +} + +// subConn combines a balancer.SubConn with metadata handed down from the name resolver. +type subConn struct { + sc balancer.SubConn + az string +} + +// Pick selects a SubConn and is biased towards SubConns in the same AZ as the current task. +func (p *Picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { + az := queryAvailabilityZone() + + var selected balancer.SubConn + sameAz := make([]balancer.SubConn, 0, len(p.subConns)) + otherAz := make([]balancer.SubConn, 0, len(p.subConns)) + + // split subConns in lists for same AZ and different AZ + for _, conn := range p.subConns { + if az == conn.az { + sameAz = append(sameAz, conn.sc) + } else { + otherAz = append(otherAz, conn.sc) + } + } + + // coin flip for same AZ loadbalancing + // productive use cases may implement more sophisticated strategies + useSameAz := rand.Int()%2 == 0 + + if useSameAz { + idx := rand.Intn(len(sameAz)) + selected = sameAz[idx] + } else { + idx := rand.Intn(len(otherAz)) + selected = otherAz[idx] + } + + // safety net if no subConn has been selected due to incorrect config or other issues + if selected == nil { + log.Println("could not decide on subConn to use. falling back to subConn at idx 0") + selected = p.subConns[0].sc + } + + return balancer.PickResult{ + SubConn: selected, + }, nil +} + +// Build creates a new balancer.Picker and initializes it. +func (p *Picker) Build(info base.PickerBuildInfo) balancer.Picker { + + // extract metadata from the provided SubConns and carry it forward + // for use in the created Picker + p.subConns = make([]subConn, 0, len(info.ReadySCs)) + for sc, sci := range info.ReadySCs { + preparedSubconn := subConn{ + sc: sc, + // populated in CloudmapResolver#resolveForTesting + az: sci.Address.BalancerAttributes.Value("az").(string), + } + p.subConns = append(p.subConns, preparedSubconn) + } + + // re-seed the pseudo rng so we can have random guesses in Pick + // if we want same az loadbalancing + rand.Seed(time.Now().Unix()) + + return p +} diff --git a/client/main.go b/client/main.go new file mode 100644 index 0000000..e303e19 --- /dev/null +++ b/client/main.go @@ -0,0 +1,182 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: MIT-0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +//go:generate protoc -I ../proto/ --go_out=./pb/ --go-grpc_out=./pb/ --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative responder.proto +package main + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "strconv" + "time" + + "aws-cloud-map-with-grpc/client/pb" + "google.golang.org/grpc" + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/credentials/insecure" +) + +var ( + nextMsgId int // counter for providing unique message Ids +) + +func main() { + log.SetFlags(log.LstdFlags | log.Lmicroseconds) + + az := queryAvailabilityZone() + + c := newClient(az) + + // we handle a single pattern and ignore the http method provided + http.Handle("/describe", c) + + log.Println("Now serving at 0.0.0.0:8080...") + err := http.ListenAndServe("0.0.0.0:8080", nil) + if err != nil { + log.Fatalf("http serving failed: %v\n", err) + } +} + +// queryAvailabilityZone obtains the AZ in which the process runs +func queryAvailabilityZone() string { + mdUrl := os.Getenv("ECS_CONTAINER_METADATA_URI_V4") + if mdUrl == "" { + return "az-localhost" + } + + res, err := http.Get(fmt.Sprintf("%s/%s", mdUrl, "task")) + if err != nil { + log.Fatalf("HTTP Get on metadata endpoint failed: %v\n", err) + } + + jsonBytes, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Fatalf("could not read body: %v\n", err) + } + + // struct local to func, holding just the info we seek from the json + type MD struct { + AvailabilityZone string `json:"AvailabilityZone"` + } + + md := MD{} + err = json.Unmarshal(jsonBytes, &md) + if err != nil { + log.Fatalf("could not parse json from body: %v\n", err) + } + + return md.AvailabilityZone +} + +// client is the client for conducting gRPC to the server executable +type client struct { + client pb.ResponderClient + az string +} + +// newClient initializes a new client. +// +// It sets up the gRPC connection which it configures with instances of a custom name +// resolver and custom loadbalancer. +// Note that the coding uses our custom "cloudmap" scheme and the service name +// and namespace name as configured in cloudmap. +// +// Caveat: This demo will split on the first "." in the host part of the URL. This +// means you cannot have service names with "." in this specific demo. +func newClient(az string) client { + // the demo does not use channel security (no TLS in gRPC) + noCreds := insecure.NewCredentials() + + balancer.Register(NewBalancerBuilder()) + resolverBuilder := &CloudmapResolver{} + + ctx, _ := context.WithTimeout(context.Background(), time.Second*1) + + // Note we use cloudmap as the scheme + conn, err := grpc.DialContext( + ctx, + "cloudmap://server.grpc.demo", + grpc.WithTransportCredentials(noCreds), + grpc.WithResolvers(resolverBuilder), + grpc.WithBalancerName("cm-az-aware"), + ) + if err != nil { + log.Fatalln("error: could not dial server. Aborting.") + } + + c := pb.NewResponderClient(conn) + + return client{ + client: c, + az: az, + } +} + +// ServeHTTP implements http.Handler on client. +// +// This way we can directly mount an instance of client in http.Mux. +func (c client) ServeHTTP(w http.ResponseWriter, _ *http.Request) { + // Not thread-safe actually as this is only a demo. + id := nextMsgId + nextMsgId = nextMsgId + 1 + + req := &pb.DescribeServiceInstanceRequest{MsgId: strconv.Itoa(nextMsgId)} + pbRes, err := c.client.DescribeServiceInstance(context.Background(), req) + if err != nil { + log.Printf("error when calling server instance: %v\n", err) + } + + res := Response{ + MsgId: strconv.Itoa(id), + AvailabilityZone: pbRes.AvailabilityZone, + } + + b := marshal(&res) + + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-Type", "application/json") + _, err = w.Write(b) + if err != nil { + log.Fatalln("error: could not write out json response. Aborting.") + } +} + +// Response is used to serialize http responses as JSON +type Response struct { + MsgId string `json:"msgId,omitempty"` + AvailabilityZone string `json:"availabilityZone,omitempty"` +} + +// marshal is a helper method for JSON serialization +func marshal(in interface{}) []byte { + if in == nil { + return []byte{} + } + + b, err := json.Marshal(in) + if err != nil { + log.Println("error: could not marshal value to json") + return []byte{} + } + + return b +} diff --git a/client/resolver.go b/client/resolver.go new file mode 100644 index 0000000..eca2b93 --- /dev/null +++ b/client/resolver.go @@ -0,0 +1,119 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: MIT-0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package main + +import ( + "context" + "errors" + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "google.golang.org/grpc/attributes" + "google.golang.org/grpc/resolver" + + "github.com/aws/aws-sdk-go-v2/config" + sd "github.com/aws/aws-sdk-go-v2/service/servicediscovery" +) + +// CloudmapResolver implements both a resolver.Builder and resolver.Resolver +type CloudmapResolver struct { + service string + namespace string + clientConn resolver.ClientConn +} + +// ResolveNow triggers the actual name resolution. +func (r CloudmapResolver) ResolveNow(options resolver.ResolveNowOptions) { + + // we default to eu-central-1 in this demo + cfg, err := config.LoadDefaultConfig(context.Background(), config.WithDefaultRegion("eu-central-1")) + if err != nil { + log.Fatalf("unable to load SDK config, %v", err) + } + + svc := sd.NewFromConfig(cfg) + + res, err := svc.DiscoverInstances(context.Background(), &sd.DiscoverInstancesInput{ + NamespaceName: aws.String(r.namespace), + ServiceName: aws.String(r.service), + }) + if err != nil { + log.Fatalf("error in DiscoverInstances: %v\n", err) + } + + // extract metadata of registered instances, i.e. the availability zone + // for Fargate tasks with service discovery ECS will automatically create those + // attributes + // + // the availability zone is then put into BalancerAttributes of the + // resolver.Address struct, so the balancer.PickerBuilder can access + // and use them + addr := make([]resolver.Address, 0, len(res.Instances)) + for i := range res.Instances { + instAtt := res.Instances[i].Attributes + add := fmt.Sprintf("%s:%s", instAtt["AWS_INSTANCE_IPV4"], "9000") + + log.Printf("resolved target: %v", add) + + balancerAtts := attributes.New("az", instAtt["AVAILABILITY_ZONE"]) + addr = append(addr, resolver.Address{ + Addr: add, + BalancerAttributes: balancerAtts, + }) + } + + r.clientConn.UpdateState(resolver.State{ + Addresses: addr, + }) +} + +func (r CloudmapResolver) Close() { + // nothing to do +} + +// Build returns a new instance of a resolver.Resolver +// +// target is split on the first "." into a service name with the remainder as the namespace. +// Example: my.name.local will resolve to service "my" and namespace "name.local" +func (r CloudmapResolver) Build( + target resolver.Target, + cc resolver.ClientConn, + opts resolver.BuildOptions, +) (resolver.Resolver, error) { + if target.URL.Scheme != "cloudmap" { + return nil, errors.New("error: unsupported scheme in service discovery. Must be cloudmap") + } + comps := strings.SplitN(target.URL.Host, ".", 2) + + cmResolver := &CloudmapResolver{ + clientConn: cc, + service: comps[0], + namespace: comps[1], + } + + cmResolver.ResolveNow(resolver.ResolveNowOptions{}) + + return cmResolver, nil +} + +// Scheme always returns "cloudmap" +func (r CloudmapResolver) Scheme() string { + return "cloudmap" +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a9d96dc --- /dev/null +++ b/go.mod @@ -0,0 +1,28 @@ +module aws-cloud-map-with-grpc + +go 1.17 + +require ( + github.com/aws/aws-sdk-go-v2/config v1.11.1 + github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.13.0 + google.golang.org/grpc v1.43.0 + google.golang.org/protobuf v1.27.1 +) + +require ( + github.com/aws/aws-sdk-go-v2 v1.11.2 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.6.5 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.7.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.12.0 // indirect + github.com/aws/smithy-go v1.9.0 // indirect + github.com/golang/protobuf v1.5.0 // indirect + golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect + golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect + golang.org/x/text v0.3.0 // indirect + google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e7ca45e --- /dev/null +++ b/go.sum @@ -0,0 +1,148 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/aws/aws-sdk-go-v2 v1.11.2 h1:SDiCYqxdIYi6HgQfAWRhgdZrdnOuGyLDJVRSWLeHWvs= +github.com/aws/aws-sdk-go-v2 v1.11.2/go.mod h1:SQfA+m2ltnu1cA0soUkj4dRSsmITiVQUJvBIZjzfPyQ= +github.com/aws/aws-sdk-go-v2/config v1.11.1 h1:KXSjb7ZMLRtjxClFptukTYibiOqJS9NwBO+9WD3UMto= +github.com/aws/aws-sdk-go-v2/config v1.11.1/go.mod h1:VvfkzUhVtntSg1JfGFMSKS0CyiTZd3NqBxK5af4zsME= +github.com/aws/aws-sdk-go-v2/credentials v1.6.5 h1:ZrsO2js2v4T95rsCIWoAb/ck5+U1kwkizGdZHY+ni3s= +github.com/aws/aws-sdk-go-v2/credentials v1.6.5/go.mod h1:HWSOnsnqVMbLcWUmom6AN1cqhcLzLJ62AObW28CbYbU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.2 h1:KiN5TPOLrEjbGCvdTQR4t0U4T87vVwALZ5Bg3jpMqPY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.2/go.mod h1:dF2F6tXEOgmW5X1ZFO/EPtWrcm7XkW07KNcJUGNtt4s= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.2 h1:XJLnluKuUxQG255zPNe+04izXl7GSyUVafIsgfv9aw4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.2/go.mod h1:SgKKNBIoDC/E1ZCDhhMW3yalWjwuLjMcpLzsM/QQnWo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.2 h1:EauRoYZVNPlidZSZJDscjJBQ22JhVF2+tdteatax2Ak= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.2/go.mod h1:xT4XX6w5Sa3dhg50JrYyy3e4WPYo/+WjY/BXtqXVunU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.2 h1:IQup8Q6lorXeiA/rK72PeToWoWK8h7VAPgHNWdSrtgE= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.2/go.mod h1:VITe/MdW6EMXPb0o0txu/fsonXbMHUU2OC2Qp7ivU4o= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.2 h1:CKdUNKmuilw/KNmO2Q53Av8u+ZyXMC2M9aX8Z+c/gzg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.2/go.mod h1:FgR1tCsn8C6+Hf+N5qkfrE4IXvUL1RgW87sunJ+5J4I= +github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.13.0 h1:Iq7e+9Y3//EmMpcX8hHAK44BrLBrU7RRyIC1xCexOB4= +github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.13.0/go.mod h1:SCvVw3nMKhmw3GwAtHpnBIbVl8aHFw0Wt1DJ8B+f8pw= +github.com/aws/aws-sdk-go-v2/service/sso v1.7.0 h1:E4fxAg/UE8a6yiLZYv8/EP0uXKPPRImiMau4ift6S/g= +github.com/aws/aws-sdk-go-v2/service/sso v1.7.0/go.mod h1:KnIpszaIdwI33tmc/W/GGXyn22c1USYxA/2KyvoeDY0= +github.com/aws/aws-sdk-go-v2/service/sts v1.12.0 h1:7g0252k2TF3eA1DtfkTQB/tqI41YvbUPaolwTR0/ITc= +github.com/aws/aws-sdk-go-v2/service/sts v1.12.0/go.mod h1:UV2N5HaPfdbDpkgkz4sRzWCvQswZjdO1FfqCWl0t7RA= +github.com/aws/smithy-go v1.9.0 h1:c7FUdEqrQA1/UVKKCNDFQPNKGp4FQg3YW4Ck5SLTG58= +github.com/aws/smithy-go v1.9.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +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.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +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= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/proto/responder.proto b/proto/responder.proto new file mode 100644 index 0000000..1683c38 --- /dev/null +++ b/proto/responder.proto @@ -0,0 +1,40 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: MIT-0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +syntax = "proto3"; + +package awssamples; + +option go_package = "aws-cloud-map-with-grpc/client/pb"; + +service Responder { + rpc DescribeServiceInstance (DescribeServiceInstanceRequest) returns (DescribeServiceInstanceResponse); +} + +message DescribeServiceInstanceRequest { + string msg_id = 1; +} + +message DescribeServiceInstanceResponse { + string msg_id = 1; + + string cluster = 2; + string task_arn = 3; + string task_family = 4; + string task_family_revision = 5; + string availability_zone = 6; +} \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..5b30913 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,22 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this +# software and associated documentation files (the "Software"), to deal in the Software +# without restriction, including without limitation the rights to use, copy, modify, +# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +FROM registry.access.redhat.com/ubi8/ubi-minimal:latest + +ENV DIRTY=1 + +COPY server /server + +ENTRYPOINT ["/server"] \ No newline at end of file diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..f9ba02e --- /dev/null +++ b/server/main.go @@ -0,0 +1,120 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: MIT-0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +//go:generate protoc -I ../proto/ --go_out=./pb/ --go-grpc_out=./pb/ --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative responder.proto +package main + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net" + "net/http" + "os" + + "aws-cloud-map-with-grpc/server/pb" + "google.golang.org/grpc" +) + +func main() { + md := queryMetadata() + log.Printf("server starting with in AZ: %v\n", md.AvailabilityZone) + + s := grpc.NewServer() + pb.RegisterResponderServer(s, &server{md: md}) + + lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", 9000)) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + + log.Printf("server listening at 0.0.0.0:9000\n") + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +} + +// server implements the gRPC server interface for the Responder service +// as specified under proto/responder.proto +type server struct { + pb.UnimplementedResponderServer + + md metadata +} + +// DescribeServiceInstance responds by describing the current task with various attributes +func (r server) DescribeServiceInstance( + ctx context.Context, + request *pb.DescribeServiceInstanceRequest, +) (*pb.DescribeServiceInstanceResponse, error) { + + res := &pb.DescribeServiceInstanceResponse{ + MsgId: request.MsgId, + Cluster: r.md.Cluster, + TaskArn: r.md.TaskArn, + TaskFamily: r.md.Family, + TaskFamilyRevision: r.md.Revision, + AvailabilityZone: r.md.AvailabilityZone, + } + + log.Printf("server response from az: %s\n", res.AvailabilityZone) + + return res, nil +} + +// metadata holds metadata of a Fargate task +type metadata struct { + AvailabilityZone string `json:"AvailabilityZone"` + Cluster string `json:"Cluster"` + TaskArn string `json:"TaskArn"` + Family string `json:"Family"` + Revision string `json:"Revision"` +} + +// queryMetadata populates the metadata struct from runtime information +func queryMetadata() metadata { + mdUrl := os.Getenv("ECS_CONTAINER_METADATA_URI_V4") + if mdUrl == "" { + return metadata{ + AvailabilityZone: "az-localhost", + Cluster: "no-cluster", + TaskArn: "no-task-arn", + Family: "no-task-family", + Revision: "0", + } + } + + res, err := http.Get(fmt.Sprintf("%s/%s", mdUrl, "task")) + if err != nil { + log.Fatalf("HTTP Get on metadata endpoint failed: %v\n", err) + } + + jsonBytes, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Fatalf("could not read body: %v\n", err) + } + + md := metadata{} + err = json.Unmarshal(jsonBytes, &md) + if err != nil { + log.Fatalf("could not parse json from body: %v\n", err) + } + + return md +}