RDS: Support PreferredMaintenanceWindow for DBInstance (#5839)
This commit is contained in:
parent
afeebd8993
commit
e47a2fd120
@ -35,7 +35,13 @@ from .exceptions import (
|
|||||||
SubscriptionNotFoundError,
|
SubscriptionNotFoundError,
|
||||||
SubscriptionAlreadyExistError,
|
SubscriptionAlreadyExistError,
|
||||||
)
|
)
|
||||||
from .utils import FilterDef, apply_filter, merge_filters, validate_filters
|
from .utils import (
|
||||||
|
FilterDef,
|
||||||
|
apply_filter,
|
||||||
|
merge_filters,
|
||||||
|
validate_filters,
|
||||||
|
valid_preferred_maintenance_window,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Cluster:
|
class Cluster:
|
||||||
@ -446,9 +452,15 @@ class Database(CloudFormationModel):
|
|||||||
self.db_subnet_group = None
|
self.db_subnet_group = None
|
||||||
self.security_groups = kwargs.get("security_groups", [])
|
self.security_groups = kwargs.get("security_groups", [])
|
||||||
self.vpc_security_group_ids = kwargs.get("vpc_security_group_ids", [])
|
self.vpc_security_group_ids = kwargs.get("vpc_security_group_ids", [])
|
||||||
self.preferred_maintenance_window = kwargs.get(
|
self.preferred_maintenance_window = kwargs.get("preferred_maintenance_window")
|
||||||
"preferred_maintenance_window", "wed:06:38-wed:07:08"
|
self.preferred_backup_window = kwargs.get("preferred_backup_window")
|
||||||
|
msg = valid_preferred_maintenance_window(
|
||||||
|
self.preferred_maintenance_window,
|
||||||
|
self.preferred_backup_window,
|
||||||
)
|
)
|
||||||
|
if msg:
|
||||||
|
raise RDSClientError("InvalidParameterValue", msg)
|
||||||
|
|
||||||
self.db_parameter_group_name = kwargs.get("db_parameter_group_name")
|
self.db_parameter_group_name = kwargs.get("db_parameter_group_name")
|
||||||
if (
|
if (
|
||||||
self.db_parameter_group_name
|
self.db_parameter_group_name
|
||||||
@ -458,9 +470,6 @@ class Database(CloudFormationModel):
|
|||||||
):
|
):
|
||||||
raise DBParameterGroupNotFoundError(self.db_parameter_group_name)
|
raise DBParameterGroupNotFoundError(self.db_parameter_group_name)
|
||||||
|
|
||||||
self.preferred_backup_window = kwargs.get(
|
|
||||||
"preferred_backup_window", "13:14-13:44"
|
|
||||||
)
|
|
||||||
self.license_model = kwargs.get("license_model", "general-public-license")
|
self.license_model = kwargs.get("license_model", "general-public-license")
|
||||||
self.option_group_name = kwargs.get("option_group_name", None)
|
self.option_group_name = kwargs.get("option_group_name", None)
|
||||||
self.option_group_supplied = self.option_group_name is not None
|
self.option_group_supplied = self.option_group_name is not None
|
||||||
@ -554,8 +563,8 @@ class Database(CloudFormationModel):
|
|||||||
<DBInstanceIdentifier>{{ database.db_instance_identifier }}</DBInstanceIdentifier>
|
<DBInstanceIdentifier>{{ database.db_instance_identifier }}</DBInstanceIdentifier>
|
||||||
<DbiResourceId>{{ database.dbi_resource_id }}</DbiResourceId>
|
<DbiResourceId>{{ database.dbi_resource_id }}</DbiResourceId>
|
||||||
<InstanceCreateTime>{{ database.instance_create_time }}</InstanceCreateTime>
|
<InstanceCreateTime>{{ database.instance_create_time }}</InstanceCreateTime>
|
||||||
<PreferredBackupWindow>03:50-04:20</PreferredBackupWindow>
|
<PreferredBackupWindow>{{ database.preferred_backup_window }}</PreferredBackupWindow>
|
||||||
<PreferredMaintenanceWindow>wed:06:38-wed:07:08</PreferredMaintenanceWindow>
|
<PreferredMaintenanceWindow>{{ database.preferred_maintenance_window }}</PreferredMaintenanceWindow>
|
||||||
<ReadReplicaDBInstanceIdentifiers>
|
<ReadReplicaDBInstanceIdentifiers>
|
||||||
{% for replica_id in database.replicas %}
|
{% for replica_id in database.replicas %}
|
||||||
<ReadReplicaDBInstanceIdentifier>{{ replica_id }}</ReadReplicaDBInstanceIdentifier>
|
<ReadReplicaDBInstanceIdentifier>{{ replica_id }}</ReadReplicaDBInstanceIdentifier>
|
||||||
@ -771,6 +780,12 @@ class Database(CloudFormationModel):
|
|||||||
"db_instance_class": properties.get("DBInstanceClass"),
|
"db_instance_class": properties.get("DBInstanceClass"),
|
||||||
"db_instance_identifier": resource_name,
|
"db_instance_identifier": resource_name,
|
||||||
"db_name": properties.get("DBName"),
|
"db_name": properties.get("DBName"),
|
||||||
|
"preferred_backup_window": properties.get(
|
||||||
|
"PreferredBackupWindow", "13:14-13:44"
|
||||||
|
),
|
||||||
|
"preferred_maintenance_window": properties.get(
|
||||||
|
"PreferredMaintenanceWindow", "wed:06:38-wed:07:08"
|
||||||
|
).lower(),
|
||||||
"db_subnet_group_name": db_subnet_group_name,
|
"db_subnet_group_name": db_subnet_group_name,
|
||||||
"engine": properties.get("Engine"),
|
"engine": properties.get("Engine"),
|
||||||
"engine_version": properties.get("EngineVersion"),
|
"engine_version": properties.get("EngineVersion"),
|
||||||
@ -1448,6 +1463,13 @@ class RDSBackend(BaseBackend):
|
|||||||
"db_instance_identifier"
|
"db_instance_identifier"
|
||||||
] = db_kwargs.pop("new_db_instance_identifier")
|
] = db_kwargs.pop("new_db_instance_identifier")
|
||||||
self.databases[db_instance_identifier] = database
|
self.databases[db_instance_identifier] = database
|
||||||
|
preferred_backup_window = db_kwargs.get("preferred_backup_window")
|
||||||
|
preferred_maintenance_window = db_kwargs.get("preferred_maintenance_window")
|
||||||
|
msg = valid_preferred_maintenance_window(
|
||||||
|
preferred_maintenance_window, preferred_backup_window
|
||||||
|
)
|
||||||
|
if msg:
|
||||||
|
raise RDSClientError("InvalidParameterValue", msg)
|
||||||
database.update(db_kwargs)
|
database.update(db_kwargs)
|
||||||
return database
|
return database
|
||||||
|
|
||||||
|
@ -44,8 +44,12 @@ class RDSResponse(BaseResponse):
|
|||||||
"multi_az": self._get_bool_param("MultiAZ"),
|
"multi_az": self._get_bool_param("MultiAZ"),
|
||||||
"option_group_name": self._get_param("OptionGroupName"),
|
"option_group_name": self._get_param("OptionGroupName"),
|
||||||
"port": self._get_param("Port"),
|
"port": self._get_param("Port"),
|
||||||
# PreferredBackupWindow
|
"preferred_backup_window": self._get_param(
|
||||||
# PreferredMaintenanceWindow
|
"PreferredBackupWindow", "13:14-13:44"
|
||||||
|
),
|
||||||
|
"preferred_maintenance_window": self._get_param(
|
||||||
|
"PreferredMaintenanceWindow", "wed:06:38-wed:07:08"
|
||||||
|
).lower(),
|
||||||
"publicly_accessible": self._get_param("PubliclyAccessible"),
|
"publicly_accessible": self._get_param("PubliclyAccessible"),
|
||||||
"account_id": self.current_account,
|
"account_id": self.current_account,
|
||||||
"region": self.region,
|
"region": self.region,
|
||||||
|
@ -3,7 +3,10 @@ from collections import namedtuple
|
|||||||
from botocore.utils import merge_dicts
|
from botocore.utils import merge_dicts
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
|
||||||
|
SECONDS_IN_ONE_DAY = 24 * 60 * 60
|
||||||
FilterDef = namedtuple(
|
FilterDef = namedtuple(
|
||||||
"FilterDef",
|
"FilterDef",
|
||||||
[
|
[
|
||||||
@ -133,3 +136,155 @@ def apply_filter(resources, filters, filter_defs):
|
|||||||
if matches_filter:
|
if matches_filter:
|
||||||
resources_filtered[identifier] = obj
|
resources_filtered[identifier] = obj
|
||||||
return resources_filtered
|
return resources_filtered
|
||||||
|
|
||||||
|
|
||||||
|
def get_start_date_end_date(base_date, window):
|
||||||
|
"""Gets the start date and end date given DDD:HH24:MM-DDD:HH24:MM.
|
||||||
|
|
||||||
|
:param base_date:
|
||||||
|
type datetime
|
||||||
|
:param window:
|
||||||
|
DDD:HH24:MM-DDD:HH24:MM
|
||||||
|
:returns:
|
||||||
|
Start and End Date in datetime format
|
||||||
|
:rtype:
|
||||||
|
tuple
|
||||||
|
"""
|
||||||
|
days = {"mon": 1, "tue": 2, "wed": 3, "thu": 4, "fri": 5, "sat": 6, "sun": 7}
|
||||||
|
start = datetime.datetime.strptime(
|
||||||
|
base_date + " " + window[4:9], "%d/%m/%y %H:%M"
|
||||||
|
) + datetime.timedelta(days=days[window[0:3]])
|
||||||
|
end = datetime.datetime.strptime(
|
||||||
|
base_date + " " + window[14::], "%d/%m/%y %H:%M"
|
||||||
|
) + datetime.timedelta(days=days[window[10:13]])
|
||||||
|
return start, end
|
||||||
|
|
||||||
|
|
||||||
|
def get_start_date_end_date_from_time(base_date, window):
|
||||||
|
"""Gets the start date and end date given HH24:MM-HH24:MM.
|
||||||
|
|
||||||
|
:param base_date:
|
||||||
|
type datetime
|
||||||
|
:param window:
|
||||||
|
HH24:MM-HH24:MM
|
||||||
|
:returns:
|
||||||
|
Start and End Date in datetime format
|
||||||
|
along with flag for spills over a day
|
||||||
|
This is useful when determine time overlaps
|
||||||
|
:rtype:
|
||||||
|
tuple
|
||||||
|
"""
|
||||||
|
times = window.split("-")
|
||||||
|
spillover = False
|
||||||
|
start = datetime.datetime.strptime(base_date + " " + times[0], "%d/%m/%y %H:%M")
|
||||||
|
end = datetime.datetime.strptime(base_date + " " + times[1], "%d/%m/%y %H:%M")
|
||||||
|
if end < start:
|
||||||
|
end += datetime.timedelta(days=1)
|
||||||
|
spillover = True
|
||||||
|
return start, end, spillover
|
||||||
|
|
||||||
|
|
||||||
|
def get_overlap_between_two_date_ranges(
|
||||||
|
start_time_1, end_time_1, start_time_2, end_time_2
|
||||||
|
):
|
||||||
|
"""Determines overlap between 2 date ranges.
|
||||||
|
|
||||||
|
:param start_time_1:
|
||||||
|
type datetime
|
||||||
|
:param start_time_2:
|
||||||
|
type datetime
|
||||||
|
:param end_time_1:
|
||||||
|
type datetime
|
||||||
|
:param end_time_2:
|
||||||
|
type datetime
|
||||||
|
:returns:
|
||||||
|
overlap in seconds
|
||||||
|
:rtype:
|
||||||
|
int
|
||||||
|
"""
|
||||||
|
latest_start = max(start_time_1, start_time_2)
|
||||||
|
earliest_end = min(end_time_1, end_time_2)
|
||||||
|
delta = earliest_end - latest_start
|
||||||
|
overlap = (delta.days * SECONDS_IN_ONE_DAY) + delta.seconds
|
||||||
|
return overlap
|
||||||
|
|
||||||
|
|
||||||
|
def valid_preferred_maintenance_window(maintenance_window, backup_window):
|
||||||
|
"""Determines validity of preferred_maintenance_window
|
||||||
|
|
||||||
|
:param maintenance_windown:
|
||||||
|
type DDD:HH24:MM-DDD:HH24:MM
|
||||||
|
:param backup_window:
|
||||||
|
type HH24:MM-HH24:MM
|
||||||
|
:returns:
|
||||||
|
message
|
||||||
|
:rtype:
|
||||||
|
str
|
||||||
|
"""
|
||||||
|
MINUTES_30 = 1800
|
||||||
|
HOURS_24 = 86400
|
||||||
|
base_date = datetime.datetime.now().strftime("%d/%m/%y")
|
||||||
|
try:
|
||||||
|
p = re.compile(
|
||||||
|
"([a-z]{3}):([0-9]{2}):([0-9]{2})-([a-z]{3}):([0-9]{2}):([0-9]{2})"
|
||||||
|
)
|
||||||
|
if len(maintenance_window) != 19 or re.search(p, maintenance_window) is None:
|
||||||
|
return f"Invalid maintenance window format: {maintenance_window}. Should be specified as a range ddd:hh24:mi-ddd:hh24:mi (24H Clock UTC). Example: Sun:23:45-Mon:00:15"
|
||||||
|
if backup_window:
|
||||||
|
(
|
||||||
|
backup_window_start,
|
||||||
|
backup_window_end,
|
||||||
|
backup_spill,
|
||||||
|
) = get_start_date_end_date_from_time(base_date, backup_window)
|
||||||
|
(
|
||||||
|
maintenance_window_start,
|
||||||
|
maintenance_window_end,
|
||||||
|
maintenance_spill,
|
||||||
|
) = get_start_date_end_date_from_time(
|
||||||
|
base_date, maintenance_window[4:10] + maintenance_window[14::]
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
get_overlap_between_two_date_ranges(
|
||||||
|
backup_window_start,
|
||||||
|
backup_window_end,
|
||||||
|
maintenance_window_start,
|
||||||
|
maintenance_window_end,
|
||||||
|
)
|
||||||
|
>= 0
|
||||||
|
):
|
||||||
|
return "The backup window and maintenance window must not overlap."
|
||||||
|
|
||||||
|
# Due to spill overs, adjust the windows
|
||||||
|
elif maintenance_spill:
|
||||||
|
backup_window_start += datetime.timedelta(days=1)
|
||||||
|
backup_window_end += datetime.timedelta(days=1)
|
||||||
|
elif backup_spill:
|
||||||
|
maintenance_window_start += datetime.timedelta(days=1)
|
||||||
|
maintenance_window_end += datetime.timedelta(days=1)
|
||||||
|
|
||||||
|
# If spills, rerun overlap test with adjusted windows
|
||||||
|
if maintenance_spill or backup_spill:
|
||||||
|
if (
|
||||||
|
get_overlap_between_two_date_ranges(
|
||||||
|
backup_window_start,
|
||||||
|
backup_window_end,
|
||||||
|
maintenance_window_start,
|
||||||
|
maintenance_window_end,
|
||||||
|
)
|
||||||
|
>= 0
|
||||||
|
):
|
||||||
|
return "The backup window and maintenance window must not overlap."
|
||||||
|
|
||||||
|
maintenance_window_start, maintenance_window_end = get_start_date_end_date(
|
||||||
|
base_date, maintenance_window
|
||||||
|
)
|
||||||
|
delta = maintenance_window_end - maintenance_window_start
|
||||||
|
delta_seconds = delta.seconds + (delta.days * SECONDS_IN_ONE_DAY)
|
||||||
|
if delta_seconds >= MINUTES_30 and delta_seconds <= HOURS_24:
|
||||||
|
return
|
||||||
|
elif delta_seconds >= 0 and delta_seconds <= MINUTES_30:
|
||||||
|
return "The maintenance window must be at least 30 minutes."
|
||||||
|
else:
|
||||||
|
return "Maintenance window must be less than 24 hours."
|
||||||
|
except Exception:
|
||||||
|
return f"Invalid day:hour:minute value: {maintenance_window}"
|
||||||
|
@ -75,6 +75,179 @@ def test_create_database_no_allocated_storage():
|
|||||||
db_instance["Engine"].should.equal("postgres")
|
db_instance["Engine"].should.equal("postgres")
|
||||||
db_instance["StorageType"].should.equal("gp2")
|
db_instance["StorageType"].should.equal("gp2")
|
||||||
db_instance["AllocatedStorage"].should.equal(20)
|
db_instance["AllocatedStorage"].should.equal(20)
|
||||||
|
db_instance["PreferredMaintenanceWindow"].should.equal("wed:06:38-wed:07:08")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_create_database_invalid_preferred_maintenance_window_more_24_hours():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
Engine="postgres",
|
||||||
|
DBName="staging-postgres",
|
||||||
|
DBInstanceClass="db.m1.small",
|
||||||
|
PreferredMaintenanceWindow="mon:16:00-tue:17:00",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.equal("Maintenance window must be less than 24 hours.")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_create_database_invalid_preferred_maintenance_window_less_30_mins():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
Engine="postgres",
|
||||||
|
DBName="staging-postgres",
|
||||||
|
DBInstanceClass="db.m1.small",
|
||||||
|
PreferredMaintenanceWindow="mon:16:00-mon:16:05",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.equal("The maintenance window must be at least 30 minutes.")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_create_database_invalid_preferred_maintenance_window_value():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
Engine="postgres",
|
||||||
|
DBName="staging-postgres",
|
||||||
|
DBInstanceClass="db.m1.small",
|
||||||
|
PreferredMaintenanceWindow="sim:16:00-mon:16:30",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.contain("Invalid day:hour:minute")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_create_database_invalid_preferred_maintenance_window_format():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
Engine="postgres",
|
||||||
|
DBName="staging-postgres",
|
||||||
|
DBInstanceClass="db.m1.small",
|
||||||
|
PreferredMaintenanceWindow="mon:16tue:17:00",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.contain(
|
||||||
|
"Should be specified as a range ddd:hh24:mi-ddd:hh24:mi (24H Clock UTC). Example: Sun:23:45-Mon:00:15"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_create_database_preferred_backup_window_overlap_no_spill():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
Engine="postgres",
|
||||||
|
DBName="staging-postgres",
|
||||||
|
DBInstanceClass="db.m1.small",
|
||||||
|
PreferredMaintenanceWindow="wed:18:00-wed:22:00",
|
||||||
|
PreferredBackupWindow="20:00-20:30",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.contain(
|
||||||
|
"The backup window and maintenance window must not overlap."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_create_database_preferred_backup_window_overlap_maintenance_window_spill():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
Engine="postgres",
|
||||||
|
DBName="staging-postgres",
|
||||||
|
DBInstanceClass="db.m1.small",
|
||||||
|
PreferredMaintenanceWindow="wed:18:00-thu:01:00",
|
||||||
|
PreferredBackupWindow="00:00-00:30",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.contain(
|
||||||
|
"The backup window and maintenance window must not overlap."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_create_database_preferred_backup_window_overlap_backup_window_spill():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
Engine="postgres",
|
||||||
|
DBName="staging-postgres",
|
||||||
|
DBInstanceClass="db.m1.small",
|
||||||
|
PreferredMaintenanceWindow="thu:00:00-thu:14:00",
|
||||||
|
PreferredBackupWindow="23:50-00:20",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.contain(
|
||||||
|
"The backup window and maintenance window must not overlap."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_create_database_preferred_backup_window_overlap_both_spill():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
Engine="postgres",
|
||||||
|
DBName="staging-postgres",
|
||||||
|
DBInstanceClass="db.m1.small",
|
||||||
|
PreferredMaintenanceWindow="wed:18:00-thu:01:00",
|
||||||
|
PreferredBackupWindow="23:50-00:20",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.contain(
|
||||||
|
"The backup window and maintenance window must not overlap."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_create_database_valid_preferred_maintenance_window_format():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
database = conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
Engine="postgres",
|
||||||
|
DBName="staging-postgres",
|
||||||
|
DBInstanceClass="db.m1.small",
|
||||||
|
PreferredMaintenanceWindow="sun:16:00-sun:16:30",
|
||||||
|
)
|
||||||
|
db_instance = database["DBInstance"]
|
||||||
|
db_instance["DBInstanceClass"].should.equal("db.m1.small")
|
||||||
|
db_instance["PreferredMaintenanceWindow"].should.equal("sun:16:00-sun:16:30")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_create_database_valid_preferred_maintenance_window_uppercase_format():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
database = conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
Engine="postgres",
|
||||||
|
DBName="staging-postgres",
|
||||||
|
DBInstanceClass="db.m1.small",
|
||||||
|
PreferredMaintenanceWindow="MON:16:00-TUE:01:30",
|
||||||
|
)
|
||||||
|
db_instance = database["DBInstance"]
|
||||||
|
db_instance["DBInstanceClass"].should.equal("db.m1.small")
|
||||||
|
db_instance["PreferredMaintenanceWindow"].should.equal("mon:16:00-tue:01:30")
|
||||||
|
|
||||||
|
|
||||||
@mock_rds
|
@mock_rds
|
||||||
@ -397,6 +570,9 @@ def test_modify_db_instance():
|
|||||||
)
|
)
|
||||||
instances = conn.describe_db_instances(DBInstanceIdentifier="db-master-1")
|
instances = conn.describe_db_instances(DBInstanceIdentifier="db-master-1")
|
||||||
instances["DBInstances"][0]["AllocatedStorage"].should.equal(20)
|
instances["DBInstances"][0]["AllocatedStorage"].should.equal(20)
|
||||||
|
instances["DBInstances"][0]["PreferredMaintenanceWindow"].should.equal(
|
||||||
|
"wed:06:38-wed:07:08"
|
||||||
|
)
|
||||||
instances["DBInstances"][0]["VpcSecurityGroups"][0][
|
instances["DBInstances"][0]["VpcSecurityGroups"][0][
|
||||||
"VpcSecurityGroupId"
|
"VpcSecurityGroupId"
|
||||||
].should.equal("sg-123456")
|
].should.equal("sg-123456")
|
||||||
@ -423,6 +599,252 @@ def test_modify_db_instance_not_existent_db_parameter_group_name():
|
|||||||
).should.throw(ClientError)
|
).should.throw(ClientError)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_modify_db_instance_valid_preferred_maintenance_window():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
AllocatedStorage=10,
|
||||||
|
DBInstanceClass="postgres",
|
||||||
|
Engine="db.m1.small",
|
||||||
|
MasterUsername="root",
|
||||||
|
MasterUserPassword="hunter2",
|
||||||
|
Port=1234,
|
||||||
|
DBSecurityGroups=["my_sg"],
|
||||||
|
)
|
||||||
|
instances = conn.describe_db_instances(DBInstanceIdentifier="db-master-1")
|
||||||
|
conn.modify_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
PreferredMaintenanceWindow="sun:16:00-sun:16:30",
|
||||||
|
)
|
||||||
|
instances = conn.describe_db_instances(DBInstanceIdentifier="db-master-1")
|
||||||
|
instances["DBInstances"][0]["PreferredMaintenanceWindow"].should.equal(
|
||||||
|
"sun:16:00-sun:16:30"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_modify_db_instance_valid_preferred_maintenance_window_uppercase():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
AllocatedStorage=10,
|
||||||
|
DBInstanceClass="postgres",
|
||||||
|
Engine="db.m1.small",
|
||||||
|
MasterUsername="root",
|
||||||
|
MasterUserPassword="hunter2",
|
||||||
|
Port=1234,
|
||||||
|
DBSecurityGroups=["my_sg"],
|
||||||
|
)
|
||||||
|
instances = conn.describe_db_instances(DBInstanceIdentifier="db-master-1")
|
||||||
|
conn.modify_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
PreferredMaintenanceWindow="SUN:16:00-SUN:16:30",
|
||||||
|
)
|
||||||
|
instances = conn.describe_db_instances(DBInstanceIdentifier="db-master-1")
|
||||||
|
instances["DBInstances"][0]["PreferredMaintenanceWindow"].should.equal(
|
||||||
|
"sun:16:00-sun:16:30"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_modify_db_instance_invalid_preferred_maintenance_window_more_than_24_hours():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
AllocatedStorage=10,
|
||||||
|
DBInstanceClass="postgres",
|
||||||
|
Engine="db.m1.small",
|
||||||
|
MasterUsername="root",
|
||||||
|
MasterUserPassword="hunter2",
|
||||||
|
Port=1234,
|
||||||
|
DBSecurityGroups=["my_sg"],
|
||||||
|
)
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.modify_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
PreferredMaintenanceWindow="sun:16:00-sat:16:30",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.equal("Maintenance window must be less than 24 hours.")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_modify_db_instance_invalid_preferred_maintenance_window_less_than_30_mins():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
AllocatedStorage=10,
|
||||||
|
DBInstanceClass="postgres",
|
||||||
|
Engine="db.m1.small",
|
||||||
|
MasterUsername="root",
|
||||||
|
MasterUserPassword="hunter2",
|
||||||
|
Port=1234,
|
||||||
|
DBSecurityGroups=["my_sg"],
|
||||||
|
)
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.modify_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
PreferredMaintenanceWindow="sun:16:00-sun:16:10",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.equal("The maintenance window must be at least 30 minutes.")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_modify_db_instance_invalid_preferred_maintenance_window_value():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
AllocatedStorage=10,
|
||||||
|
DBInstanceClass="postgres",
|
||||||
|
Engine="db.m1.small",
|
||||||
|
MasterUsername="root",
|
||||||
|
MasterUserPassword="hunter2",
|
||||||
|
Port=1234,
|
||||||
|
DBSecurityGroups=["my_sg"],
|
||||||
|
)
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.modify_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
PreferredMaintenanceWindow="sin:16:00-sun:16:30",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.contain("Invalid day:hour:minute value")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_modify_db_instance_invalid_preferred_maintenance_window_format():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
AllocatedStorage=10,
|
||||||
|
DBInstanceClass="postgres",
|
||||||
|
Engine="db.m1.small",
|
||||||
|
MasterUsername="root",
|
||||||
|
MasterUserPassword="hunter2",
|
||||||
|
Port=1234,
|
||||||
|
DBSecurityGroups=["my_sg"],
|
||||||
|
)
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.modify_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
PreferredMaintenanceWindow="sun:16:00sun:16:30",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.contain(
|
||||||
|
"Should be specified as a range ddd:hh24:mi-ddd:hh24:mi (24H Clock UTC). Example: Sun:23:45-Mon:00:15"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_modify_db_instance_maintenance_backup_window_no_spill():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
AllocatedStorage=10,
|
||||||
|
DBInstanceClass="postgres",
|
||||||
|
Engine="db.m1.small",
|
||||||
|
MasterUsername="root",
|
||||||
|
MasterUserPassword="hunter2",
|
||||||
|
Port=1234,
|
||||||
|
DBSecurityGroups=["my_sg"],
|
||||||
|
)
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.modify_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
PreferredMaintenanceWindow="sun:16:00-sun:16:30",
|
||||||
|
PreferredBackupWindow="15:50-16:20",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
"The backup window and maintenance window must not overlap."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_modify_db_instance_maintenance_backup_window_maintenance_spill():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
AllocatedStorage=10,
|
||||||
|
DBInstanceClass="postgres",
|
||||||
|
Engine="db.m1.small",
|
||||||
|
MasterUsername="root",
|
||||||
|
MasterUserPassword="hunter2",
|
||||||
|
Port=1234,
|
||||||
|
DBSecurityGroups=["my_sg"],
|
||||||
|
)
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.modify_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
PreferredMaintenanceWindow="sun:16:00-mon:15:00",
|
||||||
|
PreferredBackupWindow="00:00-00:30",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
"The backup window and maintenance window must not overlap."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_modify_db_instance_maintenance_backup_window_backup_spill():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
AllocatedStorage=10,
|
||||||
|
DBInstanceClass="postgres",
|
||||||
|
Engine="db.m1.small",
|
||||||
|
MasterUsername="root",
|
||||||
|
MasterUserPassword="hunter2",
|
||||||
|
Port=1234,
|
||||||
|
DBSecurityGroups=["my_sg"],
|
||||||
|
)
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.modify_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
PreferredMaintenanceWindow="mon:00:00-mon:15:00",
|
||||||
|
PreferredBackupWindow="23:50-00:20",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
"The backup window and maintenance window must not overlap."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_rds
|
||||||
|
def test_modify_db_instance_maintenance_backup_window_both_spill():
|
||||||
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
conn.create_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
AllocatedStorage=10,
|
||||||
|
DBInstanceClass="postgres",
|
||||||
|
Engine="db.m1.small",
|
||||||
|
MasterUsername="root",
|
||||||
|
MasterUserPassword="hunter2",
|
||||||
|
Port=1234,
|
||||||
|
DBSecurityGroups=["my_sg"],
|
||||||
|
)
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
conn.modify_db_instance(
|
||||||
|
DBInstanceIdentifier="db-master-1",
|
||||||
|
PreferredMaintenanceWindow="sun:16:00-mon:15:00",
|
||||||
|
PreferredBackupWindow="23:20-00:20",
|
||||||
|
)
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("InvalidParameterValue")
|
||||||
|
err["Message"].should.equal(
|
||||||
|
"The backup window and maintenance window must not overlap."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_rds
|
@mock_rds
|
||||||
def test_rename_db_instance():
|
def test_rename_db_instance():
|
||||||
conn = boto3.client("rds", region_name="us-west-2")
|
conn = boto3.client("rds", region_name="us-west-2")
|
||||||
|
Loading…
Reference in New Issue
Block a user