Add recipe to set up geo-location routing

Introduce geo-location-routing recipe to enable the deployment of
geo-location based DNS records in route53.

Useful in a multi-site deployment to route traffic based on the location
of the users.

Feature: Issue 13371
Change-Id: I25e80d9b993df3293ae3e52deb22e11828ebf924
diff --git a/geo-location-routing/Makefile b/geo-location-routing/Makefile
new file mode 100644
index 0000000..c57417e
--- /dev/null
+++ b/geo-location-routing/Makefile
@@ -0,0 +1,51 @@
+include ../common.env # Must be included before setup.env because the latter depends on it
+include setup.env
+include ../Makefile.common
+
+GEO_LOCATION_TEMPLATE:=cf-geo-location-routing.yml
+AWS_FC_COMMAND=export AWS_PAGER=;aws cloudformation
+
+.PHONY: create-all delete-all \
+		create-geo-location-routing wait-for-geo-location-routing-creation \
+		delete-geo-location-routing wait-for-geo-location-routing-deletion
+
+create-all: create-geo-location-routing wait-for-geo-location-routing-creation
+delete-all: delete-geo-location-routing wait-for-geo-location-routing-deletion
+
+create-geo-location-routing:
+
+	$(AWS_FC_COMMAND) create-stack \
+		--stack-name $(GEO_LOCATION_ROUTING_STACK_NAME) \
+		--capabilities CAPABILITY_IAM  \
+		--template-body file://`pwd`/$(GEO_LOCATION_TEMPLATE) \
+		--region $(AWS_REGION) \
+		--parameters \
+			ParameterKey=HostedZoneName,ParameterValue=$(HOSTED_ZONE_NAME) \
+			ParameterKey=HostedZoneId,ParameterValue=$(HOSTED_ZONE_ID) \
+			ParameterKey=GlobalSubdomainName,ParameterValue=$(GLOBAL_SUBDOMAIN_NAME) \
+			ParameterKey=DefaultAliasTarget,ParameterValue=$(DEFAULT_ALIAS_TARGET) \
+			ParameterKey=SiteAContinentCode,ParameterValue=$(SITE_A_CONTINENT_CODE) \
+			ParameterKey=SiteAAliasTarget,ParameterValue=$(SITE_A_ALIAS_TARGET) \
+			ParameterKey=SiteBContinentCode,ParameterValue=$(SITE_B_CONTINENT_CODE) \
+			ParameterKey=SiteBAliasTarget,ParameterValue=$(SITE_B_ALIAS_TARGET)
+
+
+delete-geo-location-routing:
+	$(AWS_FC_COMMAND) delete-stack \
+	--stack-name $(GEO_LOCATION_ROUTING_STACK_NAME) \
+	--region $(AWS_REGION)
+
+wait-for-geo-location-routing-creation:
+	@echo "*** Wait for geo-location-routing stack '$(GEO_LOCATION_ROUTING_STACK_NAME)' creation"
+	$(AWS_FC_COMMAND) wait stack-create-complete \
+	--stack-name $(GEO_LOCATION_ROUTING_STACK_NAME) \
+	--region $(AWS_REGION)
+	@echo "*** geo-location-routing stack '$(GEO_LOCATION_ROUTING_STACK_NAME)' created"
+
+
+wait-for-geo-location-routing-deletion:
+	@echo "*** Wait for geo-location-routing stack '$(GEO_LOCATION_ROUTING_STACK_NAME)' deletion"
+	$(AWS_FC_COMMAND) wait stack-delete-complete \
+	--stack-name $(GEO_LOCATION_ROUTING_STACK_NAME) \
+	--region $(AWS_REGION)
+	@echo "*** geo-location-routing stack '$(GEO_LOCATION_ROUTING_STACK_NAME)' deleted"
\ No newline at end of file
diff --git a/geo-location-routing/README.md b/geo-location-routing/README.md
new file mode 100644
index 0000000..f14f4ab
--- /dev/null
+++ b/geo-location-routing/README.md
@@ -0,0 +1,99 @@
+# Geo Location Routing
+
+This recipe lets you define a DNS configuration to two different Gerrit sites,
+deployed in AWS, so that the traffic routed to a specific site based on the
+location of the users.
+
+It is based on the Route53's GeoLocation routing strategy (docs can be found
+[here](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy.html#routing-policy-geo))
+
+## How to run it
+
+### Prerequisites
+
+This recipe assumes that you do have a multi-site Gerrit deployment in two regions.
+You can refer to the [relevant documentation](../dual-master/README.md#multi-site)
+to understand how to setup Gerrit in a multi-site deployment.
+
+### Configuration
+
+* `GEO_LOCATION_ROUTING_STACK_NAME`: Required. The name of the cloudformation stack
+* created by this recipe.
+
+* `HOSTED_ZONE_NAME`: Required. The name of the DNS zone name that will host the
+geo-location entries.
+
+* `HOSTED_ZONE_ID`: Required. The Id of the route53 zone that will host the
+geo-location entries. You can find this value by navigating to the relevant zone
+via the Route53 control panel. Alternatively you can inspect it by issuing
+
+```shell script
+aws route53 list-hosted-zones
+```
+
+* `GLOBAL_SUBDOMAIN_NAME`: Required. The subdomain for the record that will
+perform the geo-location-based routing. This will be used together with the
+`HOSTED_ZONE_NAME` to define a fqdn. Specifying `global.gerrit-demo` for example
+will generate an entry `global.gerrit-demo.yourcompany.com`
+
+* `SITE_A_CONTINENT_CODE`: Required. Traffic coming from this continent will be
+routed to the `SITE_A_ALIAS_TARGET`. Allowed values are:
+('AF', 'AN', 'AS', 'EU', 'OC', 'NA', 'SA'). For more details on this please check
+the [AWS documentation](https://docs.aws.amazon.com/Route53/latest/APIReference/API_GetGeoLocation.html#API_GetGeoLocation_RequestSyntax)
+
+* `SITE_A_ALIAS_TARGET`: Required. Gerrit site to route traffic to, when it originates
+from `SITE_A_CONTINENT_CODE`
+
+* `SITE_B_CONTINENT_CODE`: Required. Traffic coming from this continent will be
+routed to the `SITE_A_ALIAS_TARGET`. Allowed values are:
+('AF', 'AN', 'AS', 'EU', 'OC', 'NA', 'SA'). For more details on this please check
+the [AWS documentation](https://docs.aws.amazon.com/Route53/latest/APIReference/API_GetGeoLocation.html#API_GetGeoLocation_RequestSyntax)
+
+* `SITE_B_ALIAS_TARGET`: Required. Gerrit site to route traffic to, when it originates
+from `SITE_B_CONTINENT_CODE`
+
+* `DEFAULT_ALIAS_TARGET`: Required. Gerrit site to route traffic to when it doesn't
+originate from `SITE_A_CONTINENT_CODE` nor `SITE_A_CONTINENT_CODE`
+
+### Healthcheck
+
+In addition to the DNS records, two healthchecks will also be created: one to
+monitor `SITE_A_ALIAS_TARGET` and one to monitor `SITE_B_ALIAS_TARGET` respectively.
+
+The healthcheck polls the healthcheck endpoint exposed by the gerrit target, over
+HTTPS, as such:
+
+```
+https://SITE_A_ALIAS_TARGET/config/server/healthcheck~status
+```
+
+Should one of the two healthcheck become unhealthy, the traffic will be routed
+to the healthy one.
+
+### Deploy
+
+```
+make [AWS_REGION=a-valid-aws-region] [AWS_PREFIX=some-cluster-prefix] create-all
+```
+
+The optional `AWS_REGION` and `AWS_REFIX` allow you to define where it will be deployed and what it will be named.
+
+It might take several minutes to build the stack.
+You can monitor the creations of the stacks in [CloudFormation](https://console.aws.amazon.com/cloudformation/home)
+
+### Cleaning up
+
+```
+make [AWS_REGION=a-valid-aws-region] [AWS_PREFIX=some-cluster-prefix] delete-all
+```
+
+The optional `AWS_REGION` and `AWS_REFIX` allow you to specify exactly which stack you target for deletion.
+
+### Limitations
+
+* Only support two sites at the moment.
+* Only support mapping of *one* location to *one* target. All other traffic will
+be routed to the default target.
+* Only continents can be used for routing, countries and states are not supported
+* The healthcheck request interval is hardcoded to be every "30 secs" and the failure
+threshold is set to "3"
\ No newline at end of file
diff --git a/geo-location-routing/cf-geo-location-routing.yml b/geo-location-routing/cf-geo-location-routing.yml
new file mode 100644
index 0000000..64c1898
--- /dev/null
+++ b/geo-location-routing/cf-geo-location-routing.yml
@@ -0,0 +1,109 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: "Routing policies to route traffic based on users location"
+Parameters:
+  HostedZoneName:
+    Description: The hosted zone name for the Geo Location entries
+    Type: String
+  HostedZoneId:
+    Description: The hosted zone Id for the Geo Location entries
+    Type: String
+  DefaultAliasTarget:
+    Description: The default alias target to route requests from
+    Type: String
+    AllowedValues: ['SITE_A', 'SITE_B']
+  GlobalSubdomainName:
+    Description: The subdomain for the globally available DNS record
+    Type: String
+  SiteAAliasTarget:
+    Description: The alias target for site A
+    Type: String
+  SiteAContinentCode:
+    Description: The continent from which requests should be routed to site A
+    Type: String
+    AllowedValues: ['AF','AN','AS','EU','OC','NA','SA']
+  SiteBAliasTarget:
+    Description: The alias target for site B
+    Type: String
+  SiteBContinentCode:
+    Description: The continent from which requests should be routed to site B
+    Type: String
+    AllowedValues: ['AF','AN','AS','EU','OC','NA','SA']
+Conditions:
+  IsDefaultSiteA: !Equals [!Ref DefaultAliasTarget, "SITE_A"]
+Resources:
+    HealthCheckSiteA:
+      Type: AWS::Route53::HealthCheck
+      Properties:
+        HealthCheckConfig:
+          FailureThreshold: 3
+          FullyQualifiedDomainName: !Ref SiteAAliasTarget
+          Port: 443
+          RequestInterval: 30
+          ResourcePath: /config/server/healthcheck~status
+          Type: HTTPS
+
+    HealthCheckSiteB:
+      Type: AWS::Route53::HealthCheck
+      Properties:
+        HealthCheckConfig:
+          FailureThreshold: 3
+          FullyQualifiedDomainName: !Ref SiteAAliasTarget
+          Port: 443
+          RequestInterval: 30
+          ResourcePath: /config/server/healthcheck~status
+          Type: HTTPS
+
+    DefaultRecord:
+      Type: AWS::Route53::RecordSetGroup
+      DependsOn:
+        - HealthCheckSiteA
+        - HealthCheckSiteB
+      Properties:
+        HostedZoneName: !Sub '${HostedZoneName}.'
+        RecordSets:
+          - Name: !Sub '${GlobalSubdomainName}.${HostedZoneName}.'
+            SetIdentifier: "Default Record"
+            Type: "A"
+            HealthCheckId: !If [IsDefaultSiteA, !Ref HealthCheckSiteA , !Ref HealthCheckSiteB]
+            GeoLocation:
+              CountryCode: "*"
+            AliasTarget:
+              HostedZoneId: !Ref HostedZoneId
+              DNSName: !If [IsDefaultSiteA, !Ref SiteAAliasTarget , !Ref SiteBAliasTarget]
+              EvaluateTargetHealth: true
+
+    SiteARecord:
+      Type: AWS::Route53::RecordSetGroup
+      DependsOn:
+        - HealthCheckSiteA
+      Properties:
+        HostedZoneName: !Sub '${HostedZoneName}.'
+        RecordSets:
+          - Name: !Sub '${GlobalSubdomainName}.${HostedZoneName}.'
+            SetIdentifier: !Sub 'Route requests from ${SiteAContinentCode} to site A'
+            Type: "A"
+            HealthCheckId: !Ref HealthCheckSiteA
+            GeoLocation:
+              ContinentCode: !Ref SiteAContinentCode
+            AliasTarget:
+              HostedZoneId: !Ref HostedZoneId
+              DNSName: !Ref SiteAAliasTarget
+              EvaluateTargetHealth: true
+
+    SiteBRecord:
+      Type: AWS::Route53::RecordSetGroup
+      DependsOn:
+        - HealthCheckSiteA
+      Properties:
+        HostedZoneName: !Sub '${HostedZoneName}.'
+        RecordSets:
+          - Name: !Sub '${GlobalSubdomainName}.${HostedZoneName}.'
+            SetIdentifier: !Sub 'Route requests from ${SiteBContinentCode} to site B'
+            Type: "A"
+            HealthCheckId: !Ref HealthCheckSiteB
+            GeoLocation:
+              ContinentCode: !Ref SiteBContinentCode
+            AliasTarget:
+              HostedZoneId: !Ref HostedZoneId
+              DNSName: !Ref SiteBAliasTarget
+              EvaluateTargetHealth: true
\ No newline at end of file
diff --git a/geo-location-routing/setup.env.template b/geo-location-routing/setup.env.template
new file mode 100644
index 0000000..f549628
--- /dev/null
+++ b/geo-location-routing/setup.env.template
@@ -0,0 +1,12 @@
+GEO_LOCATION_ROUTING_STACK_NAME:=$(AWS_PREFIX)-geo-location-routing
+HOSTED_ZONE_NAME:=yourcompany.com
+HOSTED_ZONE_ID:=Z2O433557GA5W8
+
+GLOBAL_SUBDOMAIN_NAME:=$(AWS_PREFIX)-global.gerrit-demo
+DEFAULT_ALIAS_TARGET:=SITE_A
+
+SITE_A_CONTINENT_CODE:="NA"
+SITE_A_ALIAS_TARGET:=$(AWS_PREFIX)-master-site-a.gerrit-demo.yourcompany.com
+
+SITE_B_CONTINENT_CODE="EU"
+SITE_B_ALIAS_TARGET:=$(AWS_PREFIX)-master-site-b.gerrit-demo.yourcompany.com