From 82588b2638bfd1e01ad7021a53a219785d14dd9a Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Wed, 15 Dec 2021 19:00:19 -0100 Subject: [PATCH] Redshift - Pause/Resume clusters (#4693) --- IMPLEMENTATION_COVERAGE.md | 6 +-- docs/docs/services/redshift.rst | 4 +- moto/redshift/models.py | 18 +++++++ moto/redshift/responses.py | 28 +++++++++++ tests/test_redshift/test_redshift.py | 71 ++++++++++++++++++++++++++++ 5 files changed, 122 insertions(+), 5 deletions(-) diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index d00aa9afc..9fe872a08 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -3770,7 +3770,7 @@ ## redshift
-23% implemented +25% implemented - [ ] accept_reserved_node_exchange - [ ] add_partner @@ -3876,7 +3876,7 @@ - [X] modify_snapshot_copy_retention_period - [ ] modify_snapshot_schedule - [ ] modify_usage_limit -- [ ] pause_cluster +- [X] pause_cluster - [ ] purchase_reserved_node_offering - [ ] reboot_cluster - [ ] reject_data_share @@ -3884,7 +3884,7 @@ - [ ] resize_cluster - [X] restore_from_cluster_snapshot - [ ] restore_table_from_cluster_snapshot -- [ ] resume_cluster +- [X] resume_cluster - [ ] revoke_cluster_security_group_ingress - [ ] revoke_endpoint_access - [ ] revoke_snapshot_access diff --git a/docs/docs/services/redshift.rst b/docs/docs/services/redshift.rst index eee47b452..64c3f5bc0 100644 --- a/docs/docs/services/redshift.rst +++ b/docs/docs/services/redshift.rst @@ -129,7 +129,7 @@ redshift - [X] modify_snapshot_copy_retention_period - [ ] modify_snapshot_schedule - [ ] modify_usage_limit -- [ ] pause_cluster +- [X] pause_cluster - [ ] purchase_reserved_node_offering - [ ] reboot_cluster - [ ] reject_data_share @@ -137,7 +137,7 @@ redshift - [ ] resize_cluster - [X] restore_from_cluster_snapshot - [ ] restore_table_from_cluster_snapshot -- [ ] resume_cluster +- [X] resume_cluster - [ ] revoke_cluster_security_group_ingress - [ ] revoke_endpoint_access - [ ] revoke_snapshot_access diff --git a/moto/redshift/models.py b/moto/redshift/models.py index 2ce05b6fc..fc18a91f7 100644 --- a/moto/redshift/models.py +++ b/moto/redshift/models.py @@ -261,6 +261,12 @@ class Cluster(TaggableResourceMixin, CloudFormationModel): def resource_id(self): return self.cluster_identifier + def pause(self): + self.status = "paused" + + def resume(self): + self.status = "available" + def to_json(self): json_response = { "MasterUsername": self.master_username, @@ -637,6 +643,18 @@ class RedshiftBackend(BaseBackend): self.clusters[cluster_identifier] = cluster return cluster + def pause_cluster(self, cluster_id): + if cluster_id not in self.clusters: + raise ClusterNotFoundError(cluster_identifier=cluster_id) + self.clusters[cluster_id].pause() + return self.clusters[cluster_id] + + def resume_cluster(self, cluster_id): + if cluster_id not in self.clusters: + raise ClusterNotFoundError(cluster_identifier=cluster_id) + self.clusters[cluster_id].resume() + return self.clusters[cluster_id] + def describe_clusters(self, cluster_identifier=None): clusters = self.clusters.values() if cluster_identifier: diff --git a/moto/redshift/responses.py b/moto/redshift/responses.py index a9bf31f0a..982dbc452 100644 --- a/moto/redshift/responses.py +++ b/moto/redshift/responses.py @@ -159,6 +159,34 @@ class RedshiftResponse(BaseResponse): } ) + def pause_cluster(self): + cluster_id = self._get_param("ClusterIdentifier") + cluster = self.redshift_backend.pause_cluster(cluster_id).to_json() + return self.get_response( + { + "PauseClusterResponse": { + "PauseClusterResult": {"Cluster": cluster}, + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937a" + }, + } + } + ) + + def resume_cluster(self): + cluster_id = self._get_param("ClusterIdentifier") + cluster = self.redshift_backend.resume_cluster(cluster_id).to_json() + return self.get_response( + { + "ResumeClusterResponse": { + "ResumeClusterResult": {"Cluster": cluster}, + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937a" + }, + } + } + ) + def restore_from_cluster_snapshot(self): enhanced_vpc_routing = self._get_bool_param("EnhancedVpcRouting") node_type = self._get_param("NodeType") diff --git a/tests/test_redshift/test_redshift.py b/tests/test_redshift/test_redshift.py index 465b686d5..8c6d41bce 100644 --- a/tests/test_redshift/test_redshift.py +++ b/tests/test_redshift/test_redshift.py @@ -2137,3 +2137,74 @@ def test_get_cluster_credentials(): assert time.mktime(response["Expiration"].timetuple()) == pytest.approx( expected_expiration ) + + +@mock_redshift +def test_pause_cluster(): + client = boto3.client("redshift", region_name="us-east-1") + response = client.create_cluster( + DBName="test", + ClusterIdentifier="test", + ClusterType="single-node", + NodeType="ds2.xlarge", + MasterUsername="user", + MasterUserPassword="password", + ) + cluster = response["Cluster"] + cluster["ClusterIdentifier"].should.equal("test") + + response = client.pause_cluster(ClusterIdentifier="test") + cluster = response["Cluster"] + cluster["ClusterIdentifier"].should.equal("test") + # Verify this call returns all properties + cluster["NodeType"].should.equal("ds2.xlarge") + cluster["ClusterStatus"].should.equal("paused") + cluster["ClusterVersion"].should.equal("1.0") + cluster["AllowVersionUpgrade"].should.equal(True) + cluster["Endpoint"]["Port"].should.equal(5439) + + +@mock_redshift +def test_pause_unknown_cluster(): + client = boto3.client("redshift", region_name="us-east-1") + + with pytest.raises(ClientError) as exc: + client.pause_cluster(ClusterIdentifier="test") + err = exc.value.response["Error"] + err["Code"].should.equal("ClusterNotFound") + err["Message"].should.equal("Cluster test not found.") + + +@mock_redshift +def test_resume_cluster(): + client = boto3.client("redshift", region_name="us-east-1") + client.create_cluster( + DBName="test", + ClusterIdentifier="test", + ClusterType="single-node", + NodeType="ds2.xlarge", + MasterUsername="user", + MasterUserPassword="password", + ) + + client.pause_cluster(ClusterIdentifier="test") + response = client.resume_cluster(ClusterIdentifier="test") + cluster = response["Cluster"] + cluster["ClusterIdentifier"].should.equal("test") + # Verify this call returns all properties + cluster["NodeType"].should.equal("ds2.xlarge") + cluster["ClusterStatus"].should.equal("available") + cluster["ClusterVersion"].should.equal("1.0") + cluster["AllowVersionUpgrade"].should.equal(True) + cluster["Endpoint"]["Port"].should.equal(5439) + + +@mock_redshift +def test_resume_unknown_cluster(): + client = boto3.client("redshift", region_name="us-east-1") + + with pytest.raises(ClientError) as exc: + client.resume_cluster(ClusterIdentifier="test") + err = exc.value.response["Error"] + err["Code"].should.equal("ClusterNotFound") + err["Message"].should.equal("Cluster test not found.")