From 5bbcc4505ff6e21d27de925ea708efb12bd37558 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sun, 23 Nov 2014 23:03:10 -0500 Subject: [PATCH] Add cluster parameter groups. --- moto/redshift/exceptions.py | 7 +++ moto/redshift/models.py | 63 ++++++++++++++++++++- moto/redshift/responses.py | 50 ++++++++++++++++- tests/test_redshift/test_redshift.py | 83 ++++++++++++++++++++++++++-- 4 files changed, 195 insertions(+), 8 deletions(-) diff --git a/moto/redshift/exceptions.py b/moto/redshift/exceptions.py index 6d1d3c90c..6d1b2c3bb 100644 --- a/moto/redshift/exceptions.py +++ b/moto/redshift/exceptions.py @@ -38,6 +38,13 @@ class ClusterSecurityGroupNotFoundError(RedshiftClientError): "Security group {0} not found.".format(group_identifier)) +class ClusterParameterGroupNotFoundError(RedshiftClientError): + def __init__(self, group_identifier): + super(ClusterParameterGroupNotFoundError, self).__init__( + 'ClusterParameterGroupNotFound', + "Parameter group {0} not found.".format(group_identifier)) + + class InvalidSubnetError(RedshiftClientError): def __init__(self, subnet_identifier): super(InvalidSubnetError, self).__init__( diff --git a/moto/redshift/models.py b/moto/redshift/models.py index c5332215b..c3189c951 100644 --- a/moto/redshift/models.py +++ b/moto/redshift/models.py @@ -5,6 +5,7 @@ from moto.core import BaseBackend from moto.ec2 import ec2_backends from .exceptions import ( ClusterNotFoundError, + ClusterParameterGroupNotFoundError, ClusterSecurityGroupNotFoundError, ClusterSubnetGroupNotFoundError, InvalidSubnetError, @@ -27,7 +28,6 @@ class Cluster(object): self.db_name = db_name if db_name else "dev" self.vpc_security_group_ids = vpc_security_group_ids self.cluster_subnet_group_name = cluster_subnet_group_name - self.cluster_parameter_group_name = cluster_parameter_group_name self.publicly_accessible = publicly_accessible self.encrypted = encrypted @@ -37,6 +37,11 @@ class Cluster(object): self.automated_snapshot_retention_period = automated_snapshot_retention_period if automated_snapshot_retention_period else 1 self.preferred_maintenance_window = preferred_maintenance_window if preferred_maintenance_window else "Mon:03:00-Mon:03:30" + if cluster_parameter_group_name: + self.cluster_parameter_group_name = [cluster_parameter_group_name] + else: + self.cluster_parameter_group_name = ['default.redshift-1.0'] + if cluster_security_groups: self.cluster_security_groups = cluster_security_groups else: @@ -72,6 +77,14 @@ class Cluster(object): if security_group.id in self.vpc_security_group_ids ] + @property + def parameter_groups(self): + return [ + parameter_group for parameter_group + in self.redshift_backend.describe_cluster_parameter_groups() + if parameter_group.cluster_parameter_group_name in self.cluster_parameter_group_name + ] + def to_json(self): return { "MasterUsername": self.master_username, @@ -90,7 +103,10 @@ class Cluster(object): "Encrypted": self.encrypted, "DBName": self.db_name, "PreferredMaintenanceWindow": self.preferred_maintenance_window, - "ClusterParameterGroups": [], + "ClusterParameterGroups": [{ + "ParameterApplyStatus": "in-sync", + "ParameterGroupName": group.cluster_parameter_group_name, + } for group in self.parameter_groups], "ClusterSecurityGroups": [{ "Status": "active", "ClusterSecurityGroupName": group.cluster_security_group_name, @@ -150,6 +166,21 @@ class SecurityGroup(object): } +class ParameterGroup(object): + + def __init__(self, cluster_parameter_group_name, group_family, description): + self.cluster_parameter_group_name = cluster_parameter_group_name + self.group_family = group_family + self.description = description + + def to_json(self): + return { + "ParameterGroupFamily": self.group_family, + "Description": self.description, + "ParameterGroupName": self.cluster_parameter_group_name, + } + + class RedshiftBackend(BaseBackend): def __init__(self, ec2_backend): @@ -158,6 +189,13 @@ class RedshiftBackend(BaseBackend): self.security_groups = { "Default": SecurityGroup("Default", "Default Redshift Security Group") } + self.parameter_groups = { + "default.redshift-1.0": ParameterGroup( + "default.redshift-1.0", + "redshift-1.0", + "Default Redshift parameter group", + ) + } self.ec2_backend = ec2_backend def reset(self): @@ -239,6 +277,27 @@ class RedshiftBackend(BaseBackend): return self.security_groups.pop(security_group_identifier) raise ClusterSecurityGroupNotFoundError(security_group_identifier) + def create_cluster_parameter_group(self, cluster_parameter_group_name, + group_family, description): + parameter_group = ParameterGroup(cluster_parameter_group_name, group_family, description) + self.parameter_groups[cluster_parameter_group_name] = parameter_group + + return parameter_group + + def describe_cluster_parameter_groups(self, parameter_group_name=None): + parameter_groups = self.parameter_groups.values() + if parameter_group_name: + if parameter_group_name in self.parameter_groups: + return [self.parameter_groups[parameter_group_name]] + else: + raise ClusterParameterGroupNotFoundError(parameter_group_name) + return parameter_groups + + def delete_cluster_parameter_group(self, parameter_group_name): + if parameter_group_name in self.parameter_groups: + return self.parameter_groups.pop(parameter_group_name) + raise ClusterParameterGroupNotFoundError(parameter_group_name) + redshift_backends = {} for region in boto.redshift.regions(): diff --git a/moto/redshift/responses.py b/moto/redshift/responses.py index 697e76a2b..a9c977b4e 100644 --- a/moto/redshift/responses.py +++ b/moto/redshift/responses.py @@ -82,7 +82,6 @@ class RedshiftResponse(BaseResponse): "publicly_accessible": self._get_param("PubliclyAccessible"), "encrypted": self._get_param("Encrypted"), } - cluster = self.redshift_backend.modify_cluster(**cluster_kwargs) return json.dumps({ @@ -206,3 +205,52 @@ class RedshiftResponse(BaseResponse): } } }) + + def create_cluster_parameter_group(self): + cluster_parameter_group_name = self._get_param('ParameterGroupName') + group_family = self._get_param('ParameterGroupFamily') + description = self._get_param('Description') + + parameter_group = self.redshift_backend.create_cluster_parameter_group( + cluster_parameter_group_name, + group_family, + description, + ) + + return json.dumps({ + "CreateClusterParameterGroupResponse": { + "CreateClusterParameterGroupResult": { + "ClusterParameterGroup": parameter_group.to_json(), + }, + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937a", + } + } + }) + + def describe_cluster_parameter_groups(self): + cluster_parameter_group_name = self._get_param("ParameterGroupName") + parameter_groups = self.redshift_backend.describe_cluster_parameter_groups(cluster_parameter_group_name) + + return json.dumps({ + "DescribeClusterParameterGroupsResponse": { + "DescribeClusterParameterGroupsResult": { + "ParameterGroups": [parameter_group.to_json() for parameter_group in parameter_groups] + }, + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937a", + } + } + }) + + def delete_cluster_parameter_group(self): + cluster_parameter_group_name = self._get_param("ParameterGroupName") + self.redshift_backend.delete_cluster_parameter_group(cluster_parameter_group_name) + + return json.dumps({ + "DeleteClusterParameterGroupResponse": { + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937a", + } + } + }) diff --git a/tests/test_redshift/test_redshift.py b/tests/test_redshift/test_redshift.py index 756cf0d9f..bb4b2ce10 100644 --- a/tests/test_redshift/test_redshift.py +++ b/tests/test_redshift/test_redshift.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import boto from boto.redshift.exceptions import ( ClusterNotFound, + ClusterParameterGroupNotFound, ClusterSecurityGroupNotFound, ClusterSubnetGroupNotFound, ) @@ -25,7 +26,6 @@ def test_create_cluster(): cluster_type="multi-node", availability_zone="us-east-1d", preferred_maintenance_window="Mon:03:00-Mon:11:00", - # cluster_parameter_group_name=None, automated_snapshot_retention_period=10, port=1234, cluster_version="1.0", @@ -45,7 +45,7 @@ def test_create_cluster(): cluster['ClusterSubnetGroupName'].should.equal(None) cluster['AvailabilityZone'].should.equal("us-east-1d") cluster['PreferredMaintenanceWindow'].should.equal("Mon:03:00-Mon:11:00") - cluster['ClusterParameterGroups'].should.equal([]) + cluster['ClusterParameterGroups'][0]['ParameterGroupName'].should.equal("default.redshift-1.0") cluster['AutomatedSnapshotRetentionPeriod'].should.equal(10) cluster['Port'].should.equal(1234) cluster['ClusterVersion'].should.equal("1.0") @@ -96,7 +96,7 @@ def test_default_cluster_attibutes(): cluster['ClusterSubnetGroupName'].should.equal(None) assert "us-east-" in cluster['AvailabilityZone'] cluster['PreferredMaintenanceWindow'].should.equal("Mon:03:00-Mon:03:30") - # cluster['ClusterParameterGroups'].should.equal([]) + cluster['ClusterParameterGroups'][0]['ParameterGroupName'].should.equal("default.redshift-1.0") cluster['AutomatedSnapshotRetentionPeriod'].should.equal(1) cluster['Port'].should.equal(5439) cluster['ClusterVersion'].should.equal("1.0") @@ -180,6 +180,28 @@ def test_create_cluster_with_vpc_security_groups(): list(group_ids).should.equal([security_group.id]) +@mock_redshift +def test_create_cluster_with_parameter_group(): + conn = boto.connect_redshift() + conn.create_cluster_parameter_group( + "my_parameter_group", + "redshift-1.0", + "This is my parameter group", + ) + + conn.create_cluster( + "my_cluster", + node_type="dw.hs1.xlarge", + master_username="username", + master_user_password="password", + cluster_parameter_group_name='my_parameter_group', + ) + + cluster_response = conn.describe_clusters("my_cluster") + cluster = cluster_response['DescribeClustersResponse']['DescribeClustersResult']['Clusters'][0] + cluster['ClusterParameterGroups'][0]['ParameterGroupName'].should.equal("my_parameter_group") + + @mock_redshift def test_describe_non_existant_cluster(): conn = boto.redshift.connect_to_region("us-east-1") @@ -218,6 +240,11 @@ def test_modify_cluster(): "security_group", "This is my security group", ) + conn.create_cluster_parameter_group( + "my_parameter_group", + "redshift-1.0", + "This is my parameter group", + ) conn.create_cluster( cluster_identifier, @@ -233,7 +260,7 @@ def test_modify_cluster(): number_of_nodes=2, cluster_security_groups="security_group", master_user_password="new_password", - # cluster_parameter_group_name=None, + cluster_parameter_group_name="my_parameter_group", automated_snapshot_retention_period=7, preferred_maintenance_window="Tue:03:00-Tue:11:00", allow_version_upgrade=False, @@ -247,7 +274,7 @@ def test_modify_cluster(): cluster['NodeType'].should.equal("dw.hs1.xlarge") cluster['ClusterSecurityGroups'][0]['ClusterSecurityGroupName'].should.equal("security_group") cluster['PreferredMaintenanceWindow'].should.equal("Tue:03:00-Tue:11:00") - # cluster['ClusterParameterGroups'].should.equal([]) + cluster['ClusterParameterGroups'][0]['ParameterGroupName'].should.equal("my_parameter_group") cluster['AutomatedSnapshotRetentionPeriod'].should.equal(7) cluster['AllowVersionUpgrade'].should.equal(False) cluster['NumberOfNodes'].should.equal(2) @@ -354,3 +381,49 @@ def test_delete_cluster_security_group(): # Delete invalid id conn.delete_cluster_security_group.when.called_with("not-a-security-group").should.throw(ClusterSecurityGroupNotFound) + + +@mock_redshift +def test_create_cluster_parameter_group(): + conn = boto.connect_redshift() + conn.create_cluster_parameter_group( + "my_parameter_group", + "redshift-1.0", + "This is my parameter group", + ) + + groups_response = conn.describe_cluster_parameter_groups("my_parameter_group") + my_group = groups_response['DescribeClusterParameterGroupsResponse']['DescribeClusterParameterGroupsResult']['ParameterGroups'][0] + + my_group['ParameterGroupName'].should.equal("my_parameter_group") + my_group['ParameterGroupFamily'].should.equal("redshift-1.0") + my_group['Description'].should.equal("This is my parameter group") + + +@mock_redshift +def test_describe_non_existant_parameter_group(): + conn = boto.redshift.connect_to_region("us-east-1") + conn.describe_cluster_parameter_groups.when.called_with("not-a-parameter-group").should.throw(ClusterParameterGroupNotFound) + + +@mock_redshift +def test_delete_cluster_parameter_group(): + conn = boto.connect_redshift() + conn.create_cluster_parameter_group( + "my_parameter_group", + "redshift-1.0", + "This is my parameter group", + ) + + groups_response = conn.describe_cluster_parameter_groups() + groups = groups_response['DescribeClusterParameterGroupsResponse']['DescribeClusterParameterGroupsResult']['ParameterGroups'] + groups.should.have.length_of(2) # The default group already exists + + conn.delete_cluster_parameter_group("my_parameter_group") + + groups_response = conn.describe_cluster_parameter_groups() + groups = groups_response['DescribeClusterParameterGroupsResponse']['DescribeClusterParameterGroupsResult']['ParameterGroups'] + groups.should.have.length_of(1) + + # Delete invalid id + conn.delete_cluster_parameter_group.when.called_with("not-a-parameter-group").should.throw(ClusterParameterGroupNotFound)