From e47a2fd120cb7d800aa19c41b88a6c3a907d8682 Mon Sep 17 00:00:00 2001
From: mgshirali <93970560+mgshirali@users.noreply.github.com>
Date: Sat, 14 Jan 2023 04:41:03 +0530
Subject: [PATCH] RDS: Support PreferredMaintenanceWindow for DBInstance
(#5839)
---
moto/rds/models.py | 38 +++-
moto/rds/responses.py | 8 +-
moto/rds/utils.py | 155 ++++++++++++++
tests/test_rds/test_rds.py | 422 +++++++++++++++++++++++++++++++++++++
4 files changed, 613 insertions(+), 10 deletions(-)
diff --git a/moto/rds/models.py b/moto/rds/models.py
index b55c97025..d394f933e 100644
--- a/moto/rds/models.py
+++ b/moto/rds/models.py
@@ -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):
{{ database.db_instance_identifier }}
{{ database.dbi_resource_id }}
{{ database.instance_create_time }}
- 03:50-04:20
- wed:06:38-wed:07:08
+ {{ database.preferred_backup_window }}
+ {{ database.preferred_maintenance_window }}
{% for replica_id in database.replicas %}
{{ replica_id }}
@@ -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
diff --git a/moto/rds/responses.py b/moto/rds/responses.py
index 814e9d828..61cf35441 100644
--- a/moto/rds/responses.py
+++ b/moto/rds/responses.py
@@ -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,
diff --git a/moto/rds/utils.py b/moto/rds/utils.py
index d5c3dca72..667aad638 100644
--- a/moto/rds/utils.py
+++ b/moto/rds/utils.py
@@ -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}"
diff --git a/tests/test_rds/test_rds.py b/tests/test_rds/test_rds.py
index 006903788..cb452c06b 100644
--- a/tests/test_rds/test_rds.py
+++ b/tests/test_rds/test_rds.py
@@ -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")