RDS: Support PreferredMaintenanceWindow for DBInstance (#5839)

This commit is contained in:
mgshirali 2023-01-14 04:41:03 +05:30 committed by GitHub
parent afeebd8993
commit e47a2fd120
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 613 additions and 10 deletions

View File

@ -35,7 +35,13 @@ from .exceptions import (
SubscriptionNotFoundError,
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:
@ -446,9 +452,15 @@ class Database(CloudFormationModel):
self.db_subnet_group = None
self.security_groups = kwargs.get("security_groups", [])
self.vpc_security_group_ids = kwargs.get("vpc_security_group_ids", [])
self.preferred_maintenance_window = kwargs.get(
"preferred_maintenance_window", "wed:06:38-wed:07:08"
self.preferred_maintenance_window = kwargs.get("preferred_maintenance_window")
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")
if (
self.db_parameter_group_name
@ -458,9 +470,6 @@ class Database(CloudFormationModel):
):
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.option_group_name = kwargs.get("option_group_name", 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>
<DbiResourceId>{{ database.dbi_resource_id }}</DbiResourceId>
<InstanceCreateTime>{{ database.instance_create_time }}</InstanceCreateTime>
<PreferredBackupWindow>03:50-04:20</PreferredBackupWindow>
<PreferredMaintenanceWindow>wed:06:38-wed:07:08</PreferredMaintenanceWindow>
<PreferredBackupWindow>{{ database.preferred_backup_window }}</PreferredBackupWindow>
<PreferredMaintenanceWindow>{{ database.preferred_maintenance_window }}</PreferredMaintenanceWindow>
<ReadReplicaDBInstanceIdentifiers>
{% for replica_id in database.replicas %}
<ReadReplicaDBInstanceIdentifier>{{ replica_id }}</ReadReplicaDBInstanceIdentifier>
@ -771,6 +780,12 @@ class Database(CloudFormationModel):
"db_instance_class": properties.get("DBInstanceClass"),
"db_instance_identifier": resource_name,
"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,
"engine": properties.get("Engine"),
"engine_version": properties.get("EngineVersion"),
@ -1448,6 +1463,13 @@ class RDSBackend(BaseBackend):
"db_instance_identifier"
] = db_kwargs.pop("new_db_instance_identifier")
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)
return database

View File

@ -44,8 +44,12 @@ class RDSResponse(BaseResponse):
"multi_az": self._get_bool_param("MultiAZ"),
"option_group_name": self._get_param("OptionGroupName"),
"port": self._get_param("Port"),
# PreferredBackupWindow
# PreferredMaintenanceWindow
"preferred_backup_window": self._get_param(
"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"),
"account_id": self.current_account,
"region": self.region,

View File

@ -3,7 +3,10 @@ from collections import namedtuple
from botocore.utils import merge_dicts
from collections import OrderedDict
import datetime
import re
SECONDS_IN_ONE_DAY = 24 * 60 * 60
FilterDef = namedtuple(
"FilterDef",
[
@ -133,3 +136,155 @@ def apply_filter(resources, filters, filter_defs):
if matches_filter:
resources_filtered[identifier] = obj
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}"

View File

@ -75,6 +75,179 @@ def test_create_database_no_allocated_storage():
db_instance["Engine"].should.equal("postgres")
db_instance["StorageType"].should.equal("gp2")
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
@ -397,6 +570,9 @@ def test_modify_db_instance():
)
instances = conn.describe_db_instances(DBInstanceIdentifier="db-master-1")
instances["DBInstances"][0]["AllocatedStorage"].should.equal(20)
instances["DBInstances"][0]["PreferredMaintenanceWindow"].should.equal(
"wed:06:38-wed:07:08"
)
instances["DBInstances"][0]["VpcSecurityGroups"][0][
"VpcSecurityGroupId"
].should.equal("sg-123456")
@ -423,6 +599,252 @@ def test_modify_db_instance_not_existent_db_parameter_group_name():
).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
def test_rename_db_instance():
conn = boto3.client("rds", region_name="us-west-2")