Allow reuse of the EBS volume or create a new one based on a snapshot

Currently every single-master stack creates new EBS volume. Reusing the
volume allows to preserve the data.

Feature: Issue 13407
Change-Id: I8c2c831e73ef110c945838093c3f55abad013696
diff --git a/Makefile.common b/Makefile.common
index 35ff78a..efcc0c3 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -17,6 +17,7 @@
 		aws s3api create-bucket $(CREATE_BUCKET_PARAMS)
 	aws s3 cp ../common-templates/cf-gerrit-task-execution-role.yml s3://$(TEMPLATE_BUCKET_NAME)/
 	aws s3 cp ../common-templates/cf-gerrit-network-stack.yml s3://$(TEMPLATE_BUCKET_NAME)/
+	aws s3 cp ../common-templates/cf-gerrit-volume.yml s3://$(TEMPLATE_BUCKET_NAME)/
 
 set-optional-params-metrics-cloudwatch:
 ifdef METRICS_CLOUDWATCH_ENABLED
diff --git a/common-templates/cf-gerrit-volume.yml b/common-templates/cf-gerrit-volume.yml
new file mode 100644
index 0000000..05c9b43
--- /dev/null
+++ b/common-templates/cf-gerrit-volume.yml
@@ -0,0 +1,36 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Deploy an EBS Volume for Gerrit service data.
+Parameters:
+  GerritVolumeId:
+      Description: Existing Gerrit volume id
+      Type: String
+      Default: ""
+  GerritVolumeSnapshotId:
+      Description: Id of the EBS snapshot for Gerrit volume
+      Type: String
+      Default: ""
+  GerritVolumeSizeInGiB:
+      Description: Gerrit volume size in GiB
+      Type: Number
+      Default: 10
+
+Conditions:
+  CreateEBSVolume: !Equals [!Ref GerritVolumeId, ""]
+  CreateEBSVolumeBasedOnSnapshot: !Not
+      - !Equals [!Ref GerritVolumeSnapshotId, ""]
+
+Resources:
+  GerritVolume:
+    Condition: CreateEBSVolume
+    Type: AWS::EC2::Volume
+    Properties:
+      AvailabilityZone: !Select
+        - 0
+        - !GetAZs
+           Ref: 'AWS::Region'
+      SnapshotId: !If [CreateEBSVolumeBasedOnSnapshot, !Ref GerritVolumeSnapshotId, !Ref "AWS::NoValue"]
+      Size: !If [CreateEBSVolumeBasedOnSnapshot,  !Ref "AWS::NoValue", !Ref GerritVolumeSizeInGiB]
+
+Outputs:
+  GerritVolumeRef:
+    Value: !If [CreateEBSVolume, !Ref "GerritVolume", !Ref "GerritVolumeId"]
\ No newline at end of file
diff --git a/single-master/Makefile b/single-master/Makefile
index eb3ca8e..13d0205 100644
--- a/single-master/Makefile
+++ b/single-master/Makefile
@@ -29,6 +29,15 @@
 ifdef VPC_CIDR
 		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=VPCCIDR,ParameterValue=$(VPC_CIDR))
 endif
+ifdef GERRIT_VOLUME_ID
+		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=GerritVolumeId,ParameterValue=$(GERRIT_VOLUME_ID))
+endif
+ifdef GERRIT_VOLUME_SNAPSHOT_ID
+		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=GerritVolumeSnapshotId,ParameterValue=$(GERRIT_VOLUME_SNAPSHOT_ID))
+endif
+ifdef GERRIT_VOLUME_SIZE_IN_GIB
+		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=GerritVolumeSizeInGiB,ParameterValue=$(GERRIT_VOLUME_SIZE_IN_GIB))
+endif
 
 	$(AWS_FC_COMMAND) create-stack \
 		--stack-name $(CLUSTER_STACK_NAME) \
diff --git a/single-master/README.md b/single-master/README.md
index c098806..d47f899 100644
--- a/single-master/README.md
+++ b/single-master/README.md
@@ -73,6 +73,11 @@
 
 * `SERVICE_STACK_NAME`: Optional. Name of the service stack. `gerrit-service` by default.
 * `GERRIT_INSTANCE_ID`: Optional. Identifier for the Gerrit instance. "gerrit-single-master" by default.
+* `GERRIT_VOLUME_ID` : Optional. Id of an extisting EBS volume. If empty, a new volume
+for Gerrit data will be created
+* `GERRIT_VOLUME_SNAPSHOT_ID` : Optional. Ignored if GERRIT_VOLUME_ID is not empty. Id of
+the EBS volume snapshot used to create new EBS volume for Gerrit data.
+* `GERRIT_VOLUME_SIZE_IN_GIB`: Optional. The size of the Gerrit data volume, in GiBs. `10` by default.
 
 ### 2 - Deploy
 
diff --git a/single-master/cf-cluster.yml b/single-master/cf-cluster.yml
index 41327df..356b252 100644
--- a/single-master/cf-cluster.yml
+++ b/single-master/cf-cluster.yml
@@ -56,6 +56,27 @@
       Description: An environment name that will be prefixed to resource names
       Type: String
       Default: test
+  GerritVolumeId:
+      Description: Existing Gerrit volume id
+      Type: String
+      Default: ""
+  GerritVolumeSnapshotId:
+      Description: Id of the EBS snapshot for Gerrit volume
+      Type: String
+      Default: ""
+  GerritVolumeAttachMaxRetries:
+      Description: Maximum number of retries when attaching Gerrit Volume
+      Type: Number
+      Default: 5
+  GerritVolumeAttachRetryDelay:
+      Description: The delay in seconds between Gerrit Volume attach attempts
+      Type: Number
+      Default: 5
+  GerritVolumeSizeInGiB:
+      Description: Gerrit volume size in GiB
+      Type: Number
+      Default: 10
+
 Resources:
   # ECS Resources
   ECSCluster:
@@ -108,7 +129,46 @@
           echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config
           # Make sure latest version of the helper scripts are installed as per recommendation:
           # https://github.com/awsdocs/aws-cloudformation-user-guide/blob/master/doc_source/cfn-helper-scripts-reference.md#using-the-latest-version
-          yum install -y aws-cfn-bootstrap
+          yum install -y aws-cfn-bootstrap aws-cli xfsprogs
+          EC2_INSTANCE_ID=`curl -s http://169.254.169.254/latest/meta-data/instance-id`
+          VOLUME_ID=${GerritVolumeStack.Outputs.GerritVolumeRef}
+          echo "Volume Id: $VOLUME_ID"
+
+          aws ec2 attach-volume --region ${AWS::Region} --volume-id $VOLUME_ID --instance-id $EC2_INSTANCE_ID --device /dev/xvdg
+
+          for i in $(seq 1 ${GerritVolumeAttachMaxRetries}); do
+            echo "Waiting for volume $VOLUME_ID to be attached to the instace $EC2_INSTANCE_ID"
+            volumeStatus=`aws ec2 describe-volumes --region ${AWS::Region} --volume-ids $VOLUME_ID`
+            if [[ $volumeStatus =~ "\"State\": \"attached\"" ]]; then
+              echo "Volume $VOLUME_ID attached to the instace $EC2_INSTANCE_ID"
+              break
+            elif [[ "$i" -eq "${GerritVolumeAttachMaxRetries}" ]]; then
+              echo "Could not attach the volume $VOLUME_ID to the instace $EC2_INSTANCE_ID"
+              exit 1
+            fi
+            sleep ${GerritVolumeAttachRetryDelay}
+          done
+
+          if [[ "${GerritVolumeId}" = "" && "${GerritVolumeSnapshotId}" = "" ]]; then
+            echo "Create file system for Gerrit volume"
+            mkfs -t xfs /dev/xvdg
+          fi
+
+          mkdir /gerrit-mount-point
+          mount /dev/xvdg /gerrit-mount-point
+
+          if [[ "${GerritVolumeId}" = "" && "${GerritVolumeSnapshotId}" = "" ]]; then
+            echo "Create Gerrit directories"
+            mkdir -p /gerrit-mount-point/gerrit-logs \
+             /gerrit-mount-point/gerrit-cache \
+             /gerrit-mount-point/gerrit-data \
+             /gerrit-mount-point/gerrit-git \
+             /gerrit-mount-point/gerrit-index \
+             /gerrit-mount-point/gerrit-db
+          fi
+
+          chown 1000:1000 -R /gerrit-mount-point
+
           # Get the CloudWatch Logs agent
           echo -e "
             {\"logs\":
@@ -116,22 +176,22 @@
                 {\"files\":
                   {\"collect_list\":
                     [
-                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs/_data/httpd_log\",
+                      {\"file_path\": \"/gerrit-mount-point/gerrit-logs/httpd_log\",
                       \"log_group_name\": \"${AWS::StackName}\",
                       \"log_stream_name\": \"${EnvironmentName}/{instance_id}/httpd_log\",
                       \"timezone\": \"UTC\"
                       },
-                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs/_data/sshd_log\",
+                      {\"file_path\": \"/gerrit-mount-point/gerrit-logs/sshd_log\",
                       \"log_group_name\": \"${AWS::StackName}\",
                       \"log_stream_name\": \"${EnvironmentName}/{instance_id}/sshd_log\",
                       \"timezone\": \"UTC\"
                       },
-                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs/_data/gc_log\",
+                      {\"file_path\": \"/gerrit-mount-point/gerrit-logs/gc_log\",
                       \"log_group_name\": \"${AWS::StackName}\",
                       \"log_stream_name\": \"${EnvironmentName}/{instance_id}/gc_log\",
                       \"timezone\": \"UTC\"
                       },
-                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs/_data/audit_log\",
+                      {\"file_path\": \"/gerrit-mount-point/gerrit-logs/audit_log\",
                       \"log_group_name\": \"${AWS::StackName}\",
                       \"log_stream_name\": \"${EnvironmentName}/{instance_id}/audit_log\",
                       \"timezone\": \"UTC\"
@@ -186,6 +246,8 @@
               - 'ecr:GetAuthorizationToken'
               - 'ecr:BatchGetImage'
               - 'ecr:GetDownloadUrlForLayer'
+              - 'ec2:AttachVolume'
+              - 'ec2:DescribeVolumes'
             Resource: '*'
 
   ECSTaskNetworkStack:
@@ -200,6 +262,16 @@
         SubnetIdProp: !Ref 'SubnetIdProp'
         SubnetCIDR: !Ref 'SubnetCIDR'
 
+  GerritVolumeStack:
+    Type: AWS::CloudFormation::Stack
+    Properties:
+      TemplateURL: !Join [ '', ['https://', !Ref TemplateBucketName, '.s3.amazonaws.com/cf-gerrit-volume.yml'] ]
+      TimeoutInMinutes: '25'
+      Parameters:
+        GerritVolumeId: !Ref 'GerritVolumeId'
+        GerritVolumeSnapshotId: !Ref 'GerritVolumeSnapshotId'
+        GerritVolumeSizeInGiB: !Ref 'GerritVolumeSizeInGiB'
+
 Outputs:
   ClusterName:
     Description: The name of the ECS cluster
diff --git a/single-master/cf-service.yml b/single-master/cf-service.yml
index 99cab14..fcfe842 100644
--- a/single-master/cf-service.yml
+++ b/single-master/cf-service.yml
@@ -285,47 +285,23 @@
                         awslogs-stream-prefix: !Ref EnvironmentName
             Volumes:
               - Name: !Ref 'GerritDbVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-db: !Join ['-', [!Ref EnvironmentName, !Ref GerritDbVolume]]
+                Host:
+                  SourcePath: !Join ['/', ["/gerrit-mount-point", !Ref GerritDbVolume]]
               - Name: !Ref 'GerritGitVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-git: !Join ['-', [!Ref EnvironmentName, !Ref GerritGitVolume]]
+                Host:
+                  SourcePath: !Join ['/', ["/gerrit-mount-point", !Ref GerritGitVolume]]
               - Name: !Ref 'GerritDataVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-data: !Join ['-', [!Ref EnvironmentName, !Ref GerritDataVolume]]
+                Host:
+                  SourcePath: !Join ['/', ["/gerrit-mount-point", !Ref GerritDataVolume]]
               - Name: !Ref 'GerritCacheVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-cache: !Join ['-', [!Ref EnvironmentName, !Ref GerritCacheVolume]]
+                Host:
+                  SourcePath: !Join ['/', ["/gerrit-mount-point", !Ref GerritCacheVolume]]
               - Name: !Ref 'GerritIndexVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-index: !Join ['-', [!Ref EnvironmentName, !Ref GerritIndexVolume]]
+                Host:
+                  SourcePath: !Join ['/', ["/gerrit-mount-point", !Ref GerritIndexVolume]]
               - Name: !Ref 'GerritLogsVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-logs: !Join ['-', [!Ref EnvironmentName, !Ref GerritLogsVolume]]
+                Host:
+                  SourcePath: !Join ['/', ["/gerrit-mount-point", !Ref GerritLogsVolume]]
 
     LoadBalancer:
         Type: AWS::ElasticLoadBalancingV2::LoadBalancer