Redshift Updates
- Implement create_cluster_snapshot endpoint - Implement describe_cluster_snapshots endpoint - Implement delete_cluster_snapshot endpoint - Implement restore_from_cluster_snapshot endpoint - Implement limited support for describe_tags endpoint - Correctly serialize errors to json (for boto) or xml (for boto3) - Simulate cluster spin up by returning initial status as 'creating' and subsequent statuses as 'available' - Fix issue with modify_cluster endpoint where cluster values get set to None when omitted from request - Add 'Endpoint' key to describe_clusters response syntax
This commit is contained in:
parent
efc0a71d96
commit
0a03a7237e
@ -56,3 +56,18 @@ class InvalidSubnetError(RedshiftClientError):
|
|||||||
super(InvalidSubnetError, self).__init__(
|
super(InvalidSubnetError, self).__init__(
|
||||||
'InvalidSubnet',
|
'InvalidSubnet',
|
||||||
"Subnet {0} not found.".format(subnet_identifier))
|
"Subnet {0} not found.".format(subnet_identifier))
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterSnapshotNotFoundError(RedshiftClientError):
|
||||||
|
def __init__(self, snapshot_identifier):
|
||||||
|
super(ClusterSnapshotNotFoundError, self).__init__(
|
||||||
|
'ClusterSnapshotNotFound',
|
||||||
|
"Snapshot {0} not found.".format(snapshot_identifier))
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterSnapshotAlreadyExistsError(RedshiftClientError):
|
||||||
|
def __init__(self, snapshot_identifier):
|
||||||
|
super(ClusterSnapshotAlreadyExistsError, self).__init__(
|
||||||
|
'ClusterSnapshotAlreadyExists',
|
||||||
|
"Cannot create the snapshot because a snapshot with the "
|
||||||
|
"identifier {0} already exists".format(snapshot_identifier))
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import datetime
|
||||||
|
|
||||||
import boto.redshift
|
import boto.redshift
|
||||||
|
from moto.compat import OrderedDict
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
|
from moto.core.utils import iso_8601_datetime_with_milliseconds
|
||||||
from moto.ec2 import ec2_backends
|
from moto.ec2 import ec2_backends
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
ClusterNotFoundError,
|
ClusterNotFoundError,
|
||||||
ClusterParameterGroupNotFoundError,
|
ClusterParameterGroupNotFoundError,
|
||||||
ClusterSecurityGroupNotFoundError,
|
ClusterSecurityGroupNotFoundError,
|
||||||
|
ClusterSnapshotAlreadyExistsError,
|
||||||
|
ClusterSnapshotNotFoundError,
|
||||||
ClusterSubnetGroupNotFoundError,
|
ClusterSubnetGroupNotFoundError,
|
||||||
InvalidSubnetError,
|
InvalidSubnetError,
|
||||||
)
|
)
|
||||||
@ -23,6 +30,7 @@ class Cluster(BaseModel):
|
|||||||
encrypted, region):
|
encrypted, region):
|
||||||
self.redshift_backend = redshift_backend
|
self.redshift_backend = redshift_backend
|
||||||
self.cluster_identifier = cluster_identifier
|
self.cluster_identifier = cluster_identifier
|
||||||
|
self.status = 'available'
|
||||||
self.node_type = node_type
|
self.node_type = node_type
|
||||||
self.master_username = master_username
|
self.master_username = master_username
|
||||||
self.master_user_password = master_user_password
|
self.master_user_password = master_user_password
|
||||||
@ -152,7 +160,7 @@ class Cluster(BaseModel):
|
|||||||
} for group in self.vpc_security_groups],
|
} for group in self.vpc_security_groups],
|
||||||
"ClusterSubnetGroupName": self.cluster_subnet_group_name,
|
"ClusterSubnetGroupName": self.cluster_subnet_group_name,
|
||||||
"AvailabilityZone": self.availability_zone,
|
"AvailabilityZone": self.availability_zone,
|
||||||
"ClusterStatus": "creating",
|
"ClusterStatus": self.status,
|
||||||
"NumberOfNodes": self.number_of_nodes,
|
"NumberOfNodes": self.number_of_nodes,
|
||||||
"AutomatedSnapshotRetentionPeriod": self.automated_snapshot_retention_period,
|
"AutomatedSnapshotRetentionPeriod": self.automated_snapshot_retention_period,
|
||||||
"PubliclyAccessible": self.publicly_accessible,
|
"PubliclyAccessible": self.publicly_accessible,
|
||||||
@ -171,6 +179,13 @@ class Cluster(BaseModel):
|
|||||||
"NodeType": self.node_type,
|
"NodeType": self.node_type,
|
||||||
"ClusterIdentifier": self.cluster_identifier,
|
"ClusterIdentifier": self.cluster_identifier,
|
||||||
"AllowVersionUpgrade": self.allow_version_upgrade,
|
"AllowVersionUpgrade": self.allow_version_upgrade,
|
||||||
|
"Endpoint": {
|
||||||
|
"Address": '{}.{}.redshift.amazonaws.com'.format(
|
||||||
|
self.cluster_identifier,
|
||||||
|
self.region),
|
||||||
|
"Port": self.port
|
||||||
|
},
|
||||||
|
"PendingModifiedValues": []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -262,6 +277,42 @@ class ParameterGroup(BaseModel):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Snapshot(BaseModel):
|
||||||
|
|
||||||
|
def __init__(self, cluster, snapshot_identifier, tags=None):
|
||||||
|
self.cluster = copy.copy(cluster)
|
||||||
|
self.snapshot_identifier = snapshot_identifier
|
||||||
|
self.snapshot_type = 'manual'
|
||||||
|
self.status = 'available'
|
||||||
|
self.tags = tags or []
|
||||||
|
self.create_time = iso_8601_datetime_with_milliseconds(
|
||||||
|
datetime.datetime.now())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def arn(self):
|
||||||
|
return "arn:aws:redshift:{0}:1234567890:snapshot:{1}/{2}".format(
|
||||||
|
self.cluster.region,
|
||||||
|
self.cluster.cluster_identifier,
|
||||||
|
self.snapshot_identifier)
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return {
|
||||||
|
'SnapshotIdentifier': self.snapshot_identifier,
|
||||||
|
'ClusterIdentifier': self.cluster.cluster_identifier,
|
||||||
|
'SnapshotCreateTime': self.create_time,
|
||||||
|
'Status': self.status,
|
||||||
|
'Port': self.cluster.port,
|
||||||
|
'AvailabilityZone': self.cluster.availability_zone,
|
||||||
|
'MasterUsername': self.cluster.master_username,
|
||||||
|
'ClusterVersion': self.cluster.cluster_version,
|
||||||
|
'SnapshotType': self.snapshot_type,
|
||||||
|
'NodeType': self.cluster.node_type,
|
||||||
|
'NumberOfNodes': self.cluster.number_of_nodes,
|
||||||
|
'DBName': self.cluster.db_name,
|
||||||
|
'Tags': self.tags
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class RedshiftBackend(BaseBackend):
|
class RedshiftBackend(BaseBackend):
|
||||||
|
|
||||||
def __init__(self, ec2_backend):
|
def __init__(self, ec2_backend):
|
||||||
@ -278,6 +329,7 @@ class RedshiftBackend(BaseBackend):
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
self.ec2_backend = ec2_backend
|
self.ec2_backend = ec2_backend
|
||||||
|
self.snapshots = OrderedDict()
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
ec2_backend = self.ec2_backend
|
ec2_backend = self.ec2_backend
|
||||||
@ -383,6 +435,54 @@ class RedshiftBackend(BaseBackend):
|
|||||||
return self.parameter_groups.pop(parameter_group_name)
|
return self.parameter_groups.pop(parameter_group_name)
|
||||||
raise ClusterParameterGroupNotFoundError(parameter_group_name)
|
raise ClusterParameterGroupNotFoundError(parameter_group_name)
|
||||||
|
|
||||||
|
def create_snapshot(self, cluster_identifier, snapshot_identifier, tags):
|
||||||
|
cluster = self.clusters.get(cluster_identifier)
|
||||||
|
if not cluster:
|
||||||
|
raise ClusterNotFoundError(cluster_identifier)
|
||||||
|
if self.snapshots.get(snapshot_identifier) is not None:
|
||||||
|
raise ClusterSnapshotAlreadyExistsError(snapshot_identifier)
|
||||||
|
snapshot = Snapshot(cluster, snapshot_identifier, tags)
|
||||||
|
self.snapshots[snapshot_identifier] = snapshot
|
||||||
|
return snapshot
|
||||||
|
|
||||||
|
def describe_snapshots(self, cluster_identifier, snapshot_identifier):
|
||||||
|
if cluster_identifier:
|
||||||
|
for snapshot in self.snapshots.values():
|
||||||
|
if snapshot.cluster.cluster_identifier == cluster_identifier:
|
||||||
|
return [snapshot]
|
||||||
|
raise ClusterNotFoundError(cluster_identifier)
|
||||||
|
|
||||||
|
if snapshot_identifier:
|
||||||
|
if snapshot_identifier in self.snapshots:
|
||||||
|
return [self.snapshots[snapshot_identifier]]
|
||||||
|
raise ClusterSnapshotNotFoundError(snapshot_identifier)
|
||||||
|
|
||||||
|
return self.snapshots.values()
|
||||||
|
|
||||||
|
def delete_snapshot(self, snapshot_identifier):
|
||||||
|
if snapshot_identifier not in self.snapshots:
|
||||||
|
raise ClusterSnapshotNotFoundError(snapshot_identifier)
|
||||||
|
|
||||||
|
deleted_snapshot = self.snapshots.pop(snapshot_identifier)
|
||||||
|
deleted_snapshot.status = 'deleted'
|
||||||
|
return deleted_snapshot
|
||||||
|
|
||||||
|
def describe_tags_for_resource_type(self, resource_type):
|
||||||
|
tagged_resources = []
|
||||||
|
if resource_type == 'Snapshot':
|
||||||
|
for snapshot in self.snapshots.values():
|
||||||
|
for tag in snapshot.tags:
|
||||||
|
data = {
|
||||||
|
'ResourceName': snapshot.arn,
|
||||||
|
'ResourceType': 'snapshot',
|
||||||
|
'Tag': {
|
||||||
|
'Key': tag['Key'],
|
||||||
|
'Value': tag['Value']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tagged_resources.append(data)
|
||||||
|
return tagged_resources
|
||||||
|
|
||||||
|
|
||||||
redshift_backends = {}
|
redshift_backends = {}
|
||||||
for region in boto.redshift.regions():
|
for region in boto.redshift.regions():
|
||||||
|
@ -1,12 +1,31 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import dicttoxml
|
import dicttoxml
|
||||||
|
from jinja2 import Template
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
from moto.core.responses import BaseResponse
|
from moto.core.responses import BaseResponse
|
||||||
from .models import redshift_backends
|
from .models import redshift_backends
|
||||||
|
|
||||||
|
|
||||||
|
def convert_json_error_to_xml(json_error):
|
||||||
|
error = json.loads(json_error)
|
||||||
|
code = error['Error']['Code']
|
||||||
|
message = error['Error']['Message']
|
||||||
|
template = Template("""
|
||||||
|
<RedshiftClientError>
|
||||||
|
<Error>
|
||||||
|
<Code>{{ code }}</Code>
|
||||||
|
<Message>{{ message }}</Message>
|
||||||
|
<Type>Sender</Type>
|
||||||
|
</Error>
|
||||||
|
<RequestId>6876f774-7273-11e4-85dc-39e55ca848d1</RequestId>
|
||||||
|
</RedshiftClientError>""")
|
||||||
|
return template.render(code=code, message=message)
|
||||||
|
|
||||||
|
|
||||||
class RedshiftResponse(BaseResponse):
|
class RedshiftResponse(BaseResponse):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -20,6 +39,24 @@ class RedshiftResponse(BaseResponse):
|
|||||||
xml = dicttoxml.dicttoxml(response, attr_type=False, root=False)
|
xml = dicttoxml.dicttoxml(response, attr_type=False, root=False)
|
||||||
return xml.decode("utf-8")
|
return xml.decode("utf-8")
|
||||||
|
|
||||||
|
def call_action(self):
|
||||||
|
status, headers, body = super(RedshiftResponse, self).call_action()
|
||||||
|
if status >= 400 and not self.request_json:
|
||||||
|
body = convert_json_error_to_xml(body)
|
||||||
|
return status, headers, body
|
||||||
|
|
||||||
|
def unpack_complex_list_params(self, label, names):
|
||||||
|
unpacked_list = list()
|
||||||
|
count = 1
|
||||||
|
while self._get_param('{0}.{1}.{2}'.format(label, count, names[0])):
|
||||||
|
param = dict()
|
||||||
|
for i in range(len(names)):
|
||||||
|
param[names[i]] = self._get_param(
|
||||||
|
'{0}.{1}.{2}'.format(label, count, names[i]))
|
||||||
|
unpacked_list.append(param)
|
||||||
|
count += 1
|
||||||
|
return unpacked_list
|
||||||
|
|
||||||
def create_cluster(self):
|
def create_cluster(self):
|
||||||
cluster_kwargs = {
|
cluster_kwargs = {
|
||||||
"cluster_identifier": self._get_param('ClusterIdentifier'),
|
"cluster_identifier": self._get_param('ClusterIdentifier'),
|
||||||
@ -43,12 +80,66 @@ class RedshiftResponse(BaseResponse):
|
|||||||
"encrypted": self._get_param("Encrypted"),
|
"encrypted": self._get_param("Encrypted"),
|
||||||
"region": self.region,
|
"region": self.region,
|
||||||
}
|
}
|
||||||
cluster = self.redshift_backend.create_cluster(**cluster_kwargs)
|
cluster = self.redshift_backend.create_cluster(**cluster_kwargs).to_json()
|
||||||
|
cluster['ClusterStatus'] = 'creating'
|
||||||
return self.get_response({
|
return self.get_response({
|
||||||
"CreateClusterResponse": {
|
"CreateClusterResponse": {
|
||||||
"CreateClusterResult": {
|
"CreateClusterResult": {
|
||||||
"Cluster": cluster.to_json(),
|
"Cluster": cluster,
|
||||||
|
},
|
||||||
|
"ResponseMetadata": {
|
||||||
|
"RequestId": "384ac68d-3775-11df-8963-01868b7c937a",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def restore_from_cluster_snapshot(self):
|
||||||
|
snapshot_identifier = self._get_param('SnapshotIdentifier')
|
||||||
|
snapshots = self.redshift_backend.describe_snapshots(
|
||||||
|
None,
|
||||||
|
snapshot_identifier)
|
||||||
|
snapshot = snapshots[0]
|
||||||
|
kwargs_from_snapshot = {
|
||||||
|
"node_type": snapshot.cluster.node_type,
|
||||||
|
"master_username": snapshot.cluster.master_username,
|
||||||
|
"master_user_password": snapshot.cluster.master_user_password,
|
||||||
|
"db_name": snapshot.cluster.db_name,
|
||||||
|
"cluster_type": 'multi-node' if snapshot.cluster.number_of_nodes > 1 else 'single-node',
|
||||||
|
"availability_zone": snapshot.cluster.availability_zone,
|
||||||
|
"port": snapshot.cluster.port,
|
||||||
|
"cluster_version": snapshot.cluster.cluster_version,
|
||||||
|
"number_of_nodes": snapshot.cluster.number_of_nodes,
|
||||||
|
}
|
||||||
|
kwargs_from_request = {
|
||||||
|
"cluster_identifier": self._get_param('ClusterIdentifier'),
|
||||||
|
"port": self._get_int_param('Port'),
|
||||||
|
"availability_zone": self._get_param('AvailabilityZone'),
|
||||||
|
"allow_version_upgrade": self._get_bool_param(
|
||||||
|
'AllowVersionUpgrade'),
|
||||||
|
"cluster_subnet_group_name": self._get_param(
|
||||||
|
'ClusterSubnetGroupName'),
|
||||||
|
"publicly_accessible": self._get_param("PubliclyAccessible"),
|
||||||
|
"cluster_parameter_group_name": self._get_param(
|
||||||
|
'ClusterParameterGroupName'),
|
||||||
|
"cluster_security_groups": self._get_multi_param(
|
||||||
|
'ClusterSecurityGroups.member'),
|
||||||
|
"vpc_security_group_ids": self._get_multi_param(
|
||||||
|
'VpcSecurityGroupIds.member'),
|
||||||
|
"preferred_maintenance_window": self._get_param(
|
||||||
|
'PreferredMaintenanceWindow'),
|
||||||
|
"automated_snapshot_retention_period": self._get_int_param(
|
||||||
|
'AutomatedSnapshotRetentionPeriod'),
|
||||||
|
"region": self.region,
|
||||||
|
"encrypted": False,
|
||||||
|
}
|
||||||
|
kwargs_from_snapshot.update(kwargs_from_request)
|
||||||
|
cluster_kwargs = kwargs_from_snapshot
|
||||||
|
cluster = self.redshift_backend.create_cluster(**cluster_kwargs).to_json()
|
||||||
|
cluster['ClusterStatus'] = 'creating'
|
||||||
|
return self.get_response({
|
||||||
|
"RestoreFromClusterSnapshotResponse": {
|
||||||
|
"RestoreFromClusterSnapshotResult": {
|
||||||
|
"Cluster": cluster,
|
||||||
},
|
},
|
||||||
"ResponseMetadata": {
|
"ResponseMetadata": {
|
||||||
"RequestId": "384ac68d-3775-11df-8963-01868b7c937a",
|
"RequestId": "384ac68d-3775-11df-8963-01868b7c937a",
|
||||||
@ -72,7 +163,7 @@ class RedshiftResponse(BaseResponse):
|
|||||||
})
|
})
|
||||||
|
|
||||||
def modify_cluster(self):
|
def modify_cluster(self):
|
||||||
cluster_kwargs = {
|
request_kwargs = {
|
||||||
"cluster_identifier": self._get_param('ClusterIdentifier'),
|
"cluster_identifier": self._get_param('ClusterIdentifier'),
|
||||||
"new_cluster_identifier": self._get_param('NewClusterIdentifier'),
|
"new_cluster_identifier": self._get_param('NewClusterIdentifier'),
|
||||||
"node_type": self._get_param('NodeType'),
|
"node_type": self._get_param('NodeType'),
|
||||||
@ -90,6 +181,19 @@ class RedshiftResponse(BaseResponse):
|
|||||||
"publicly_accessible": self._get_param("PubliclyAccessible"),
|
"publicly_accessible": self._get_param("PubliclyAccessible"),
|
||||||
"encrypted": self._get_param("Encrypted"),
|
"encrypted": self._get_param("Encrypted"),
|
||||||
}
|
}
|
||||||
|
# There's a bug in boto3 where the security group ids are not passed
|
||||||
|
# according to the AWS documentation
|
||||||
|
if not request_kwargs['vpc_security_group_ids']:
|
||||||
|
request_kwargs['vpc_security_group_ids'] = self._get_multi_param(
|
||||||
|
'VpcSecurityGroupIds.VpcSecurityGroupId')
|
||||||
|
|
||||||
|
cluster_kwargs = {}
|
||||||
|
# We only want parameters that were actually passed in, otherwise
|
||||||
|
# we'll stomp all over our cluster metadata with None values.
|
||||||
|
for (key, value) in iteritems(request_kwargs):
|
||||||
|
if value is not None and value != []:
|
||||||
|
cluster_kwargs[key] = value
|
||||||
|
|
||||||
cluster = self.redshift_backend.modify_cluster(**cluster_kwargs)
|
cluster = self.redshift_backend.modify_cluster(**cluster_kwargs)
|
||||||
|
|
||||||
return self.get_response({
|
return self.get_response({
|
||||||
@ -273,3 +377,71 @@ class RedshiftResponse(BaseResponse):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def create_cluster_snapshot(self):
|
||||||
|
cluster_identifier = self._get_param('ClusterIdentifier')
|
||||||
|
snapshot_identifier = self._get_param('SnapshotIdentifier')
|
||||||
|
tags = self.unpack_complex_list_params(
|
||||||
|
'Tags.Tag', ('Key', 'Value'))
|
||||||
|
snapshot = self.redshift_backend.create_snapshot(cluster_identifier,
|
||||||
|
snapshot_identifier,
|
||||||
|
tags)
|
||||||
|
return self.get_response({
|
||||||
|
'CreateClusterSnapshotResponse': {
|
||||||
|
"CreateClusterSnapshotResult": {
|
||||||
|
"Snapshot": snapshot.to_json(),
|
||||||
|
},
|
||||||
|
"ResponseMetadata": {
|
||||||
|
"RequestId": "384ac68d-3775-11df-8963-01868b7c937a",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def describe_cluster_snapshots(self):
|
||||||
|
cluster_identifier = self._get_param('ClusterIdentifier')
|
||||||
|
snapshot_identifier = self._get_param('DBSnapshotIdentifier')
|
||||||
|
snapshots = self.redshift_backend.describe_snapshots(cluster_identifier,
|
||||||
|
snapshot_identifier)
|
||||||
|
return self.get_response({
|
||||||
|
"DescribeClusterSnapshotsResponse": {
|
||||||
|
"DescribeClusterSnapshotsResult": {
|
||||||
|
"Snapshots": [snapshot.to_json() for snapshot in snapshots]
|
||||||
|
},
|
||||||
|
"ResponseMetadata": {
|
||||||
|
"RequestId": "384ac68d-3775-11df-8963-01868b7c937a",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def delete_cluster_snapshot(self):
|
||||||
|
snapshot_identifier = self._get_param('SnapshotIdentifier')
|
||||||
|
snapshot = self.redshift_backend.delete_snapshot(snapshot_identifier)
|
||||||
|
|
||||||
|
return self.get_response({
|
||||||
|
"DeleteClusterSnapshotResponse": {
|
||||||
|
"DeleteClusterSnapshotResult": {
|
||||||
|
"Snapshot": snapshot.to_json()
|
||||||
|
},
|
||||||
|
"ResponseMetadata": {
|
||||||
|
"RequestId": "384ac68d-3775-11df-8963-01868b7c937a",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def describe_tags(self):
|
||||||
|
resource_type = self._get_param('ResourceType')
|
||||||
|
if resource_type != 'Snapshot':
|
||||||
|
raise NotImplementedError(
|
||||||
|
"The describe_tags action has not been fully implemented.")
|
||||||
|
tagged_resources = \
|
||||||
|
self.redshift_backend.describe_tags_for_resource_type(resource_type)
|
||||||
|
return self.get_response({
|
||||||
|
"DescribeTagsResponse": {
|
||||||
|
"DescribeTagsResult": {
|
||||||
|
"TaggedResources": tagged_resources
|
||||||
|
},
|
||||||
|
"ResponseMetadata": {
|
||||||
|
"RequestId": "384ac68d-3775-11df-8963-01868b7c937a",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@ -9,6 +9,9 @@ from boto.redshift.exceptions import (
|
|||||||
ClusterSubnetGroupNotFound,
|
ClusterSubnetGroupNotFound,
|
||||||
InvalidSubnet,
|
InvalidSubnet,
|
||||||
)
|
)
|
||||||
|
from botocore.exceptions import (
|
||||||
|
ClientError
|
||||||
|
)
|
||||||
import sure # noqa
|
import sure # noqa
|
||||||
|
|
||||||
from moto import mock_ec2
|
from moto import mock_ec2
|
||||||
@ -36,7 +39,7 @@ def test_create_cluster():
|
|||||||
conn = boto.redshift.connect_to_region("us-east-1")
|
conn = boto.redshift.connect_to_region("us-east-1")
|
||||||
cluster_identifier = 'my_cluster'
|
cluster_identifier = 'my_cluster'
|
||||||
|
|
||||||
conn.create_cluster(
|
cluster_response = conn.create_cluster(
|
||||||
cluster_identifier,
|
cluster_identifier,
|
||||||
node_type="dw.hs1.xlarge",
|
node_type="dw.hs1.xlarge",
|
||||||
master_username="username",
|
master_username="username",
|
||||||
@ -51,6 +54,8 @@ def test_create_cluster():
|
|||||||
allow_version_upgrade=True,
|
allow_version_upgrade=True,
|
||||||
number_of_nodes=3,
|
number_of_nodes=3,
|
||||||
)
|
)
|
||||||
|
cluster_response['CreateClusterResponse']['CreateClusterResult'][
|
||||||
|
'Cluster']['ClusterStatus'].should.equal('creating')
|
||||||
|
|
||||||
cluster_response = conn.describe_clusters(cluster_identifier)
|
cluster_response = conn.describe_clusters(cluster_identifier)
|
||||||
cluster = cluster_response['DescribeClustersResponse'][
|
cluster = cluster_response['DescribeClustersResponse'][
|
||||||
@ -320,7 +325,6 @@ def test_modify_cluster():
|
|||||||
cluster_identifier,
|
cluster_identifier,
|
||||||
cluster_type="multi-node",
|
cluster_type="multi-node",
|
||||||
node_type="dw.hs1.xlarge",
|
node_type="dw.hs1.xlarge",
|
||||||
number_of_nodes=2,
|
|
||||||
cluster_security_groups="security_group",
|
cluster_security_groups="security_group",
|
||||||
master_user_password="new_password",
|
master_user_password="new_password",
|
||||||
cluster_parameter_group_name="my_parameter_group",
|
cluster_parameter_group_name="my_parameter_group",
|
||||||
@ -343,7 +347,8 @@ def test_modify_cluster():
|
|||||||
'ParameterGroupName'].should.equal("my_parameter_group")
|
'ParameterGroupName'].should.equal("my_parameter_group")
|
||||||
cluster['AutomatedSnapshotRetentionPeriod'].should.equal(7)
|
cluster['AutomatedSnapshotRetentionPeriod'].should.equal(7)
|
||||||
cluster['AllowVersionUpgrade'].should.equal(False)
|
cluster['AllowVersionUpgrade'].should.equal(False)
|
||||||
cluster['NumberOfNodes'].should.equal(2)
|
# This one should remain unmodified.
|
||||||
|
cluster['NumberOfNodes'].should.equal(1)
|
||||||
|
|
||||||
|
|
||||||
@mock_redshift_deprecated
|
@mock_redshift_deprecated
|
||||||
@ -523,3 +528,177 @@ def test_delete_cluster_parameter_group():
|
|||||||
# Delete invalid id
|
# Delete invalid id
|
||||||
conn.delete_cluster_parameter_group.when.called_with(
|
conn.delete_cluster_parameter_group.when.called_with(
|
||||||
"not-a-parameter-group").should.throw(ClusterParameterGroupNotFound)
|
"not-a-parameter-group").should.throw(ClusterParameterGroupNotFound)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_redshift
|
||||||
|
def test_create_cluster_snapshot():
|
||||||
|
client = boto3.client('redshift', region_name='us-east-1')
|
||||||
|
cluster_identifier = 'my_cluster'
|
||||||
|
snapshot_identifier = 'my_snapshot'
|
||||||
|
|
||||||
|
cluster_response = client.create_cluster(
|
||||||
|
DBName='test-db',
|
||||||
|
ClusterIdentifier=cluster_identifier,
|
||||||
|
ClusterType='single-node',
|
||||||
|
NodeType='ds2.xlarge',
|
||||||
|
MasterUsername='username',
|
||||||
|
MasterUserPassword='password',
|
||||||
|
)
|
||||||
|
cluster_response['Cluster']['NodeType'].should.equal('ds2.xlarge')
|
||||||
|
|
||||||
|
snapshot_response = client.create_cluster_snapshot(
|
||||||
|
SnapshotIdentifier=snapshot_identifier,
|
||||||
|
ClusterIdentifier=cluster_identifier,
|
||||||
|
Tags=[{'Key': 'test-tag-key',
|
||||||
|
'Value': 'test-tag-value'}]
|
||||||
|
)
|
||||||
|
snapshot = snapshot_response['Snapshot']
|
||||||
|
snapshot['SnapshotIdentifier'].should.equal(snapshot_identifier)
|
||||||
|
snapshot['ClusterIdentifier'].should.equal(cluster_identifier)
|
||||||
|
snapshot['NumberOfNodes'].should.equal(1)
|
||||||
|
snapshot['NodeType'].should.equal('ds2.xlarge')
|
||||||
|
snapshot['MasterUsername'].should.equal('username')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_redshift
|
||||||
|
def test_delete_cluster_snapshot():
|
||||||
|
client = boto3.client('redshift', region_name='us-east-1')
|
||||||
|
cluster_identifier = 'my_cluster'
|
||||||
|
snapshot_identifier = 'my_snapshot'
|
||||||
|
|
||||||
|
client.create_cluster(
|
||||||
|
ClusterIdentifier=cluster_identifier,
|
||||||
|
ClusterType='single-node',
|
||||||
|
NodeType='ds2.xlarge',
|
||||||
|
MasterUsername='username',
|
||||||
|
MasterUserPassword='password',
|
||||||
|
)
|
||||||
|
client.create_cluster_snapshot(
|
||||||
|
SnapshotIdentifier=snapshot_identifier,
|
||||||
|
ClusterIdentifier=cluster_identifier
|
||||||
|
)
|
||||||
|
|
||||||
|
snapshots = client.describe_cluster_snapshots()['Snapshots']
|
||||||
|
list(snapshots).should.have.length_of(1)
|
||||||
|
|
||||||
|
client.delete_cluster_snapshot(SnapshotIdentifier=snapshot_identifier)[
|
||||||
|
'Snapshot']['Status'].should.equal('deleted')
|
||||||
|
|
||||||
|
snapshots = client.describe_cluster_snapshots()['Snapshots']
|
||||||
|
list(snapshots).should.have.length_of(0)
|
||||||
|
|
||||||
|
# Delete invalid id
|
||||||
|
client.delete_cluster_snapshot.when.called_with(
|
||||||
|
SnapshotIdentifier="not-a-snapshot").should.throw(ClientError)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_redshift
|
||||||
|
def test_cluster_snapshot_already_exists():
|
||||||
|
client = boto3.client('redshift', region_name='us-east-1')
|
||||||
|
cluster_identifier = 'my_cluster'
|
||||||
|
snapshot_identifier = 'my_snapshot'
|
||||||
|
|
||||||
|
client.create_cluster(
|
||||||
|
DBName='test-db',
|
||||||
|
ClusterIdentifier=cluster_identifier,
|
||||||
|
ClusterType='single-node',
|
||||||
|
NodeType='ds2.xlarge',
|
||||||
|
MasterUsername='username',
|
||||||
|
MasterUserPassword='password',
|
||||||
|
)
|
||||||
|
|
||||||
|
client.create_cluster_snapshot(
|
||||||
|
SnapshotIdentifier=snapshot_identifier,
|
||||||
|
ClusterIdentifier=cluster_identifier
|
||||||
|
)
|
||||||
|
|
||||||
|
client.create_cluster_snapshot.when.called_with(
|
||||||
|
SnapshotIdentifier=snapshot_identifier,
|
||||||
|
ClusterIdentifier=cluster_identifier
|
||||||
|
).should.throw(ClientError)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_redshift
|
||||||
|
def test_create_cluster_from_snapshot():
|
||||||
|
client = boto3.client('redshift', region_name='us-east-1')
|
||||||
|
original_cluster_identifier = 'original-cluster'
|
||||||
|
original_snapshot_identifier = 'original-snapshot'
|
||||||
|
new_cluster_identifier = 'new-cluster'
|
||||||
|
|
||||||
|
client.create_cluster(
|
||||||
|
ClusterIdentifier=original_cluster_identifier,
|
||||||
|
ClusterType='single-node',
|
||||||
|
NodeType='ds2.xlarge',
|
||||||
|
MasterUsername='username',
|
||||||
|
MasterUserPassword='password',
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
Port=1234
|
||||||
|
)
|
||||||
|
response['Cluster']['ClusterStatus'].should.equal('creating')
|
||||||
|
|
||||||
|
response = client.describe_clusters(
|
||||||
|
ClusterIdentifier=new_cluster_identifier
|
||||||
|
)
|
||||||
|
new_cluster = response['Clusters'][0]
|
||||||
|
new_cluster['NodeType'].should.equal('ds2.xlarge')
|
||||||
|
new_cluster['MasterUsername'].should.equal('username')
|
||||||
|
new_cluster['Endpoint']['Port'].should.equal(1234)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_redshift
|
||||||
|
def test_create_cluster_status_update():
|
||||||
|
client = boto3.client('redshift', region_name='us-east-1')
|
||||||
|
cluster_identifier = 'test-cluster'
|
||||||
|
|
||||||
|
response = client.create_cluster(
|
||||||
|
ClusterIdentifier=cluster_identifier,
|
||||||
|
ClusterType='single-node',
|
||||||
|
NodeType='ds2.xlarge',
|
||||||
|
MasterUsername='username',
|
||||||
|
MasterUserPassword='password',
|
||||||
|
)
|
||||||
|
response['Cluster']['ClusterStatus'].should.equal('creating')
|
||||||
|
|
||||||
|
response = client.describe_clusters(
|
||||||
|
ClusterIdentifier=cluster_identifier
|
||||||
|
)
|
||||||
|
response['Clusters'][0]['ClusterStatus'].should.equal('available')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_redshift
|
||||||
|
def test_describe_snapshot_tags():
|
||||||
|
client = boto3.client('redshift', region_name='us-east-1')
|
||||||
|
cluster_identifier = 'my_cluster'
|
||||||
|
snapshot_identifier = 'my_snapshot'
|
||||||
|
tag_key = 'test-tag-key'
|
||||||
|
tag_value = 'teat-tag-value'
|
||||||
|
|
||||||
|
client.create_cluster(
|
||||||
|
DBName='test-db',
|
||||||
|
ClusterIdentifier=cluster_identifier,
|
||||||
|
ClusterType='single-node',
|
||||||
|
NodeType='ds2.xlarge',
|
||||||
|
MasterUsername='username',
|
||||||
|
MasterUserPassword='password',
|
||||||
|
)
|
||||||
|
|
||||||
|
client.create_cluster_snapshot(
|
||||||
|
SnapshotIdentifier=snapshot_identifier,
|
||||||
|
ClusterIdentifier=cluster_identifier,
|
||||||
|
Tags=[{'Key': tag_key,
|
||||||
|
'Value': tag_value}]
|
||||||
|
)
|
||||||
|
|
||||||
|
tags_response = client.describe_tags(ResourceType='Snapshot')
|
||||||
|
tagged_resources = tags_response['TaggedResources']
|
||||||
|
list(tagged_resources).should.have.length_of(1)
|
||||||
|
tag = tagged_resources[0]['Tag']
|
||||||
|
tag['Key'].should.equal(tag_key)
|
||||||
|
tag['Value'].should.equal(tag_value)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user