diff --git a/moto/redshift/models.py b/moto/redshift/models.py index c0b783bde..8a2b7e6b6 100644 --- a/moto/redshift/models.py +++ b/moto/redshift/models.py @@ -74,7 +74,7 @@ class Cluster(TaggableResourceMixin, BaseModel): automated_snapshot_retention_period, port, cluster_version, allow_version_upgrade, number_of_nodes, publicly_accessible, encrypted, region_name, tags=None, iam_roles_arn=None, - restored_from_snapshot=False): + enhanced_vpc_routing=None, restored_from_snapshot=False): super(Cluster, self).__init__(region_name, tags) self.redshift_backend = redshift_backend self.cluster_identifier = cluster_identifier @@ -85,6 +85,7 @@ class Cluster(TaggableResourceMixin, BaseModel): self.master_user_password = master_user_password self.db_name = db_name if db_name else "dev" self.vpc_security_group_ids = vpc_security_group_ids + self.enhanced_vpc_routing = enhanced_vpc_routing if enhanced_vpc_routing is not None else False self.cluster_subnet_group_name = cluster_subnet_group_name self.publicly_accessible = publicly_accessible self.encrypted = encrypted @@ -154,6 +155,7 @@ class Cluster(TaggableResourceMixin, BaseModel): port=properties.get('Port'), cluster_version=properties.get('ClusterVersion'), allow_version_upgrade=properties.get('AllowVersionUpgrade'), + enhanced_vpc_routing=properties.get('EnhancedVpcRouting'), number_of_nodes=properties.get('NumberOfNodes'), publicly_accessible=properties.get("PubliclyAccessible"), encrypted=properties.get("Encrypted"), @@ -241,6 +243,7 @@ class Cluster(TaggableResourceMixin, BaseModel): 'ClusterCreateTime': self.create_time, "PendingModifiedValues": [], "Tags": self.tags, + "EnhancedVpcRouting": self.enhanced_vpc_routing, "IamRoles": [{ "ApplyStatus": "in-sync", "IamRoleArn": iam_role_arn @@ -427,6 +430,7 @@ class Snapshot(TaggableResourceMixin, BaseModel): 'NumberOfNodes': self.cluster.number_of_nodes, 'DBName': self.cluster.db_name, 'Tags': self.tags, + 'EnhancedVpcRouting': self.cluster.enhanced_vpc_routing, "IamRoles": [{ "ApplyStatus": "in-sync", "IamRoleArn": iam_role_arn @@ -678,7 +682,8 @@ class RedshiftBackend(BaseBackend): "number_of_nodes": snapshot.cluster.number_of_nodes, "encrypted": snapshot.cluster.encrypted, "tags": snapshot.cluster.tags, - "restored_from_snapshot": True + "restored_from_snapshot": True, + "enhanced_vpc_routing": snapshot.cluster.enhanced_vpc_routing } create_kwargs.update(kwargs) return self.create_cluster(**create_kwargs) diff --git a/moto/redshift/responses.py b/moto/redshift/responses.py index a7758febb..7ac73d470 100644 --- a/moto/redshift/responses.py +++ b/moto/redshift/responses.py @@ -135,6 +135,7 @@ class RedshiftResponse(BaseResponse): "region_name": self.region, "tags": self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value')), "iam_roles_arn": self._get_iam_roles(), + "enhanced_vpc_routing": self._get_param('EnhancedVpcRouting'), } cluster = self.redshift_backend.create_cluster(**cluster_kwargs).to_json() cluster['ClusterStatus'] = 'creating' @@ -150,6 +151,7 @@ class RedshiftResponse(BaseResponse): }) def restore_from_cluster_snapshot(self): + enhanced_vpc_routing = self._get_bool_param('EnhancedVpcRouting') restore_kwargs = { "snapshot_identifier": self._get_param('SnapshotIdentifier'), "cluster_identifier": self._get_param('ClusterIdentifier'), @@ -171,6 +173,8 @@ class RedshiftResponse(BaseResponse): "region_name": self.region, "iam_roles_arn": self._get_iam_roles(), } + if enhanced_vpc_routing is not None: + restore_kwargs['enhanced_vpc_routing'] = enhanced_vpc_routing cluster = self.redshift_backend.restore_from_cluster_snapshot(**restore_kwargs).to_json() cluster['ClusterStatus'] = 'creating' return self.get_response({ @@ -218,6 +222,7 @@ class RedshiftResponse(BaseResponse): "publicly_accessible": self._get_param("PubliclyAccessible"), "encrypted": self._get_param("Encrypted"), "iam_roles_arn": self._get_iam_roles(), + "enhanced_vpc_routing": self._get_param("EnhancedVpcRouting") } cluster_kwargs = {} # We only want parameters that were actually passed in, otherwise diff --git a/tests/test_redshift/test_redshift.py b/tests/test_redshift/test_redshift.py index 2c9b42a1d..79e283e5b 100644 --- a/tests/test_redshift/test_redshift.py +++ b/tests/test_redshift/test_redshift.py @@ -37,6 +37,25 @@ def test_create_cluster_boto3(): create_time = response['Cluster']['ClusterCreateTime'] create_time.should.be.lower_than(datetime.datetime.now(create_time.tzinfo)) create_time.should.be.greater_than(datetime.datetime.now(create_time.tzinfo) - datetime.timedelta(minutes=1)) + response['Cluster']['EnhancedVpcRouting'].should.equal(False) + +@mock_redshift +def test_create_cluster_boto3(): + 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', + EnhancedVpcRouting=True + ) + response['Cluster']['NodeType'].should.equal('ds2.xlarge') + create_time = response['Cluster']['ClusterCreateTime'] + create_time.should.be.lower_than(datetime.datetime.now(create_time.tzinfo)) + create_time.should.be.greater_than(datetime.datetime.now(create_time.tzinfo) - datetime.timedelta(minutes=1)) + response['Cluster']['EnhancedVpcRouting'].should.equal(True) @mock_redshift @@ -425,6 +444,58 @@ def test_delete_cluster(): "not-a-cluster").should.throw(ClusterNotFound) +@mock_redshift +def test_modify_cluster_vpc_routing(): + iam_roles_arn = ['arn:aws:iam:::role/my-iam-role', ] + client = boto3.client('redshift', region_name='us-east-1') + cluster_identifier = 'my_cluster' + + client.create_cluster( + ClusterIdentifier=cluster_identifier, + NodeType="single-node", + MasterUsername="username", + MasterUserPassword="password", + IamRoles=iam_roles_arn + ) + + cluster_response = client.describe_clusters(ClusterIdentifier=cluster_identifier) + cluster = cluster_response['Clusters'][0] + cluster['EnhancedVpcRouting'].should.equal(False) + + client.create_cluster_security_group(ClusterSecurityGroupName='security_group', + Description='security_group') + + client.create_cluster_parameter_group(ParameterGroupName='my_parameter_group', + ParameterGroupFamily='redshift-1.0', + Description='my_parameter_group') + + client.modify_cluster( + ClusterIdentifier=cluster_identifier, + ClusterType='multi-node', + NodeType="ds2.8xlarge", + NumberOfNodes=3, + ClusterSecurityGroups=["security_group"], + MasterUserPassword="new_password", + ClusterParameterGroupName="my_parameter_group", + AutomatedSnapshotRetentionPeriod=7, + PreferredMaintenanceWindow="Tue:03:00-Tue:11:00", + AllowVersionUpgrade=False, + NewClusterIdentifier=cluster_identifier, + EnhancedVpcRouting=True + ) + + cluster_response = client.describe_clusters(ClusterIdentifier=cluster_identifier) + cluster = cluster_response['Clusters'][0] + cluster['ClusterIdentifier'].should.equal(cluster_identifier) + cluster['NodeType'].should.equal("ds2.8xlarge") + cluster['PreferredMaintenanceWindow'].should.equal("Tue:03:00-Tue:11:00") + cluster['AutomatedSnapshotRetentionPeriod'].should.equal(7) + cluster['AllowVersionUpgrade'].should.equal(False) + # This one should remain unmodified. + cluster['NumberOfNodes'].should.equal(3) + cluster['EnhancedVpcRouting'].should.equal(True) + + @mock_redshift_deprecated def test_modify_cluster(): conn = boto.connect_redshift() @@ -446,6 +517,10 @@ def test_modify_cluster(): master_user_password="password", ) + cluster_response = conn.describe_clusters(cluster_identifier) + cluster = cluster_response['DescribeClustersResponse']['DescribeClustersResult']['Clusters'][0] + cluster['EnhancedVpcRouting'].should.equal(False) + conn.modify_cluster( cluster_identifier, cluster_type="multi-node", @@ -456,14 +531,13 @@ def test_modify_cluster(): automated_snapshot_retention_period=7, preferred_maintenance_window="Tue:03:00-Tue:11:00", allow_version_upgrade=False, - new_cluster_identifier="new_identifier", + new_cluster_identifier=cluster_identifier, ) - cluster_response = conn.describe_clusters("new_identifier") + cluster_response = conn.describe_clusters(cluster_identifier) cluster = cluster_response['DescribeClustersResponse'][ 'DescribeClustersResult']['Clusters'][0] - - cluster['ClusterIdentifier'].should.equal("new_identifier") + cluster['ClusterIdentifier'].should.equal(cluster_identifier) cluster['NodeType'].should.equal("dw.hs1.xlarge") cluster['ClusterSecurityGroups'][0][ 'ClusterSecurityGroupName'].should.equal("security_group") @@ -674,6 +748,7 @@ def test_create_cluster_snapshot(): NodeType='ds2.xlarge', MasterUsername='username', MasterUserPassword='password', + EnhancedVpcRouting=True ) cluster_response['Cluster']['NodeType'].should.equal('ds2.xlarge') @@ -823,11 +898,14 @@ def test_create_cluster_from_snapshot(): NodeType='ds2.xlarge', MasterUsername='username', MasterUserPassword='password', + EnhancedVpcRouting=True, ) + client.create_cluster_snapshot( SnapshotIdentifier=original_snapshot_identifier, ClusterIdentifier=original_cluster_identifier ) + response = client.restore_from_cluster_snapshot( ClusterIdentifier=new_cluster_identifier, SnapshotIdentifier=original_snapshot_identifier, @@ -842,7 +920,7 @@ def test_create_cluster_from_snapshot(): new_cluster['NodeType'].should.equal('ds2.xlarge') new_cluster['MasterUsername'].should.equal('username') new_cluster['Endpoint']['Port'].should.equal(1234) - + new_cluster['EnhancedVpcRouting'].should.equal(True) @mock_redshift def test_create_cluster_from_snapshot_with_waiter(): @@ -857,6 +935,7 @@ def test_create_cluster_from_snapshot_with_waiter(): NodeType='ds2.xlarge', MasterUsername='username', MasterUserPassword='password', + EnhancedVpcRouting=True ) client.create_cluster_snapshot( SnapshotIdentifier=original_snapshot_identifier, @@ -883,6 +962,7 @@ def test_create_cluster_from_snapshot_with_waiter(): new_cluster = response['Clusters'][0] new_cluster['NodeType'].should.equal('ds2.xlarge') new_cluster['MasterUsername'].should.equal('username') + new_cluster['EnhancedVpcRouting'].should.equal(True) new_cluster['Endpoint']['Port'].should.equal(1234)