From 23510bbd5a856eadcfac93877adbb31cb447f63c Mon Sep 17 00:00:00 2001 From: Erik Zilber Date: Mon, 9 Jun 2025 15:26:42 -0400 Subject: [PATCH 1/2] Added support for VPC DBaaS Integration (#560) * Implemented support for VPC DBaaS Integration * Added unit tests --- linode_api4/groups/database.py | 9 ++++ linode_api4/objects/database.py | 21 +++++++++ test/fixtures/databases_instances.json | 7 ++- test/fixtures/databases_mysql_instances.json | 5 ++ .../databases_postgresql_instances.json | 5 ++ test/unit/groups/database_test.py | 11 +++++ test/unit/objects/database_test.py | 46 +++++++++++++++++++ 7 files changed, 103 insertions(+), 1 deletion(-) diff --git a/linode_api4/groups/database.py b/linode_api4/groups/database.py index 9de02ac35..9546100a8 100644 --- a/linode_api4/groups/database.py +++ b/linode_api4/groups/database.py @@ -9,6 +9,7 @@ from linode_api4.objects import ( Database, DatabaseEngine, + DatabasePrivateNetwork, DatabaseType, MySQLDatabase, PostgreSQLDatabase, @@ -126,6 +127,7 @@ def mysql_create( engine, ltype, engine_config: Union[MySQLDatabaseConfigOptions, Dict[str, Any]] = None, + private_network: Union[DatabasePrivateNetwork, Dict[str, Any]] = None, **kwargs, ): """ @@ -159,6 +161,8 @@ def mysql_create( :type ltype: str or Type :param engine_config: The configuration options for this MySQL cluster :type engine_config: Dict[str, Any] or MySQLDatabaseConfigOptions + :param private_network: The private network settings to use for this cluster + :type private_network: Dict[str, Any] or DatabasePrivateNetwork """ params = { @@ -167,6 +171,7 @@ def mysql_create( "engine": engine, "type": ltype, "engine_config": engine_config, + "private_network": private_network, } params.update(kwargs) @@ -262,6 +267,7 @@ def postgresql_create( engine_config: Union[ PostgreSQLDatabaseConfigOptions, Dict[str, Any] ] = None, + private_network: Union[DatabasePrivateNetwork, Dict[str, Any]] = None, **kwargs, ): """ @@ -295,6 +301,8 @@ def postgresql_create( :type ltype: str or Type :param engine_config: The configuration options for this PostgreSQL cluster :type engine_config: Dict[str, Any] or PostgreSQLDatabaseConfigOptions + :param private_network: The private network settings to use for this cluster + :type private_network: Dict[str, Any] or DatabasePrivateNetwork """ params = { @@ -303,6 +311,7 @@ def postgresql_create( "engine": engine, "type": ltype, "engine_config": engine_config, + "private_network": private_network, } params.update(kwargs) diff --git a/linode_api4/objects/database.py b/linode_api4/objects/database.py index 39249bbf9..979990e8e 100644 --- a/linode_api4/objects/database.py +++ b/linode_api4/objects/database.py @@ -74,6 +74,18 @@ def invalidate(self): Base.invalidate(self) +@dataclass +class DatabasePrivateNetwork(JSONObject): + """ + DatabasePrivateNetwork is used to specify + a Database Cluster's private network settings during its creation. + """ + + vpc_id: Optional[int] = None + subnet_id: Optional[int] = None + public_access: Optional[bool] = None + + @deprecated( reason="Backups are not supported for non-legacy database clusters." ) @@ -304,6 +316,9 @@ class MySQLDatabase(Base): "engine_config": Property( mutable=True, json_object=MySQLDatabaseConfigOptions ), + "private_network": Property( + mutable=True, json_object=DatabasePrivateNetwork, nullable=True + ), } @property @@ -470,6 +485,9 @@ class PostgreSQLDatabase(Base): "engine_config": Property( mutable=True, json_object=PostgreSQLDatabaseConfigOptions ), + "private_network": Property( + mutable=True, json_object=DatabasePrivateNetwork, nullable=True + ), } @property @@ -636,6 +654,9 @@ class Database(Base): "updated": Property(), "updates": Property(), "version": Property(), + "private_network": Property( + json_object=DatabasePrivateNetwork, nullable=True + ), } @property diff --git a/test/fixtures/databases_instances.json b/test/fixtures/databases_instances.json index 3b3f4d602..5e92515a5 100644 --- a/test/fixtures/databases_instances.json +++ b/test/fixtures/databases_instances.json @@ -27,7 +27,12 @@ "hour_of_day": 0, "week_of_month": null }, - "version": "8.0.26" + "version": "8.0.26", + "private_network": { + "vpc_id": 1234, + "subnet_id": 5678, + "public_access": true + } } ], "page": 1, diff --git a/test/fixtures/databases_mysql_instances.json b/test/fixtures/databases_mysql_instances.json index d6e3f2e64..e60bfe019 100644 --- a/test/fixtures/databases_mysql_instances.json +++ b/test/fixtures/databases_mysql_instances.json @@ -61,6 +61,11 @@ "tmp_table_size": 16777216, "wait_timeout": 28800 } + }, + "private_network": { + "vpc_id": 1234, + "subnet_id": 5678, + "public_access": true } } ], diff --git a/test/fixtures/databases_postgresql_instances.json b/test/fixtures/databases_postgresql_instances.json index 92d5ce945..47573aa12 100644 --- a/test/fixtures/databases_postgresql_instances.json +++ b/test/fixtures/databases_postgresql_instances.json @@ -83,6 +83,11 @@ }, "shared_buffers_percentage": 41.5, "work_mem": 4 + }, + "private_network": { + "vpc_id": 1234, + "subnet_id": 5678, + "public_access": true } } ], diff --git a/test/unit/groups/database_test.py b/test/unit/groups/database_test.py index 9647fed82..5e2964c8d 100644 --- a/test/unit/groups/database_test.py +++ b/test/unit/groups/database_test.py @@ -61,6 +61,9 @@ def test_get_databases(self): self.assertEqual(dbs[0].region, "us-east") self.assertEqual(dbs[0].updates.duration, 3) self.assertEqual(dbs[0].version, "8.0.26") + self.assertEqual(dbs[0].private_network.vpc_id, 1234) + self.assertEqual(dbs[0].private_network.subnet_id, 5678) + self.assertEqual(dbs[0].private_network.public_access, True) def test_database_instance(self): """ @@ -1338,6 +1341,10 @@ def test_get_mysql_instances(self): self.assertEqual(dbs[0].engine_config.mysql.tmp_table_size, 16777216) self.assertEqual(dbs[0].engine_config.mysql.wait_timeout, 28800) + self.assertEqual(dbs[0].private_network.vpc_id, 1234) + self.assertEqual(dbs[0].private_network.subnet_id, 5678) + self.assertEqual(dbs[0].private_network.public_access, True) + def test_get_postgresql_instances(self): """ Test that postgresql instances can be retrieved properly @@ -1452,3 +1459,7 @@ def test_get_postgresql_instances(self): self.assertEqual(dbs[0].engine_config.pg.track_io_timing, "off") self.assertEqual(dbs[0].engine_config.pg.wal_sender_timeout, 60000) self.assertEqual(dbs[0].engine_config.pg.wal_writer_delay, 50) + + self.assertEqual(dbs[0].private_network.vpc_id, 1234) + self.assertEqual(dbs[0].private_network.subnet_id, 5678) + self.assertEqual(dbs[0].private_network.public_access, True) diff --git a/test/unit/objects/database_test.py b/test/unit/objects/database_test.py index c5abe3a58..535b2a336 100644 --- a/test/unit/objects/database_test.py +++ b/test/unit/objects/database_test.py @@ -2,6 +2,7 @@ from test.unit.base import ClientBaseCase from linode_api4 import ( + DatabasePrivateNetwork, MySQLDatabaseConfigMySQLOptions, MySQLDatabaseConfigOptions, PostgreSQLDatabase, @@ -41,6 +42,11 @@ def test_create(self): ), binlog_retention_period=200, ), + private_network=DatabasePrivateNetwork( + vpc_id=1234, + subnet_id=5678, + public_access=True, + ), ) except Exception as e: logger.warning( @@ -61,6 +67,12 @@ def test_create(self): m.call_data["engine_config"]["binlog_retention_period"], 200 ) + self.assertEqual(m.call_data["private_network"]["vpc_id"], 1234) + self.assertEqual(m.call_data["private_network"]["subnet_id"], 5678) + self.assertEqual( + m.call_data["private_network"]["public_access"], True + ) + def test_update(self): """ Test that the MySQL database can be updated @@ -78,6 +90,11 @@ def test_update(self): mysql=MySQLDatabaseConfigMySQLOptions(connect_timeout=20), binlog_retention_period=200, ) + db.private_network = DatabasePrivateNetwork( + vpc_id=1234, + subnet_id=5678, + public_access=True, + ) db.save() @@ -93,6 +110,12 @@ def test_update(self): m.call_data["engine_config"]["binlog_retention_period"], 200 ) + self.assertEqual(m.call_data["private_network"]["vpc_id"], 1234) + self.assertEqual(m.call_data["private_network"]["subnet_id"], 5678) + self.assertEqual( + m.call_data["private_network"]["public_access"], True + ) + def test_list_backups(self): """ Test that MySQL backups list properly @@ -259,6 +282,11 @@ def test_create(self): ), work_mem=4, ), + private_network=DatabasePrivateNetwork( + vpc_id=1234, + subnet_id=5678, + public_access=True, + ), ) except Exception: pass @@ -302,6 +330,12 @@ def test_create(self): ) self.assertEqual(m.call_data["engine_config"]["work_mem"], 4) + self.assertEqual(m.call_data["private_network"]["vpc_id"], 1234) + self.assertEqual(m.call_data["private_network"]["subnet_id"], 5678) + self.assertEqual( + m.call_data["private_network"]["public_access"], True + ) + def test_update(self): """ Test that the PostgreSQL database can be updated @@ -322,6 +356,12 @@ def test_update(self): work_mem=4, ) + db.private_network = DatabasePrivateNetwork( + vpc_id=1234, + subnet_id=5678, + public_access=True, + ) + db.save() self.assertEqual(m.method, "put") @@ -337,6 +377,12 @@ def test_update(self): ) self.assertEqual(m.call_data["engine_config"]["work_mem"], 4) + self.assertEqual(m.call_data["private_network"]["vpc_id"], 1234) + self.assertEqual(m.call_data["private_network"]["subnet_id"], 5678) + self.assertEqual( + m.call_data["private_network"]["public_access"], True + ) + def test_list_backups(self): """ Test that PostgreSQL backups list properly From 9f435667f9f23524905ad124900f88ab98420720 Mon Sep 17 00:00:00 2001 From: Lena Garber <114949949+lgarber-akamai@users.noreply.github.com> Date: Mon, 6 Oct 2025 15:01:47 -0400 Subject: [PATCH 2/2] Add support for VPCSubnet.databases field (#607) * Add support for VPCSubnet.databases field * ipv6_range -> ipv6_ranges * ipv6_range -> ipv6_ranges (list) --- linode_api4/objects/vpc.py | 8 ++++++++ test/fixtures/vpcs_123456_subnets.json | 9 +++++++++ test/fixtures/vpcs_123456_subnets_789.json | 9 +++++++++ test/unit/objects/vpc_test.py | 6 ++++++ 4 files changed, 32 insertions(+) diff --git a/linode_api4/objects/vpc.py b/linode_api4/objects/vpc.py index 3c9a4aaba..384a43f33 100644 --- a/linode_api4/objects/vpc.py +++ b/linode_api4/objects/vpc.py @@ -20,6 +20,13 @@ class VPCSubnetLinode(JSONObject): interfaces: Optional[List[VPCSubnetLinodeInterface]] = None +@dataclass +class VPCSubnetDatabase(JSONObject): + id: int = 0 + ipv4_range: Optional[str] = None + ipv6_ranges: Optional[List[str]] = None + + class VPCSubnet(DerivedBase): """ An instance of a VPC subnet. @@ -36,6 +43,7 @@ class VPCSubnet(DerivedBase): "label": Property(mutable=True), "ipv4": Property(), "linodes": Property(json_object=VPCSubnetLinode, unordered=True), + "databases": Property(json_object=VPCSubnetDatabase, unordered=True), "created": Property(is_datetime=True), "updated": Property(is_datetime=True), } diff --git a/test/fixtures/vpcs_123456_subnets.json b/test/fixtures/vpcs_123456_subnets.json index f846399df..4cecf3aef 100644 --- a/test/fixtures/vpcs_123456_subnets.json +++ b/test/fixtures/vpcs_123456_subnets.json @@ -19,6 +19,15 @@ ] } ], + "databases": [ + { + "id": 12345, + "ipv4_range": "10.0.0.0/24", + "ipv6_ranges": [ + "2001:db8::/64" + ] + } + ], "created": "2018-01-01T00:01:01", "updated": "2018-01-01T00:01:01" } diff --git a/test/fixtures/vpcs_123456_subnets_789.json b/test/fixtures/vpcs_123456_subnets_789.json index ba6973472..5f3275bc9 100644 --- a/test/fixtures/vpcs_123456_subnets_789.json +++ b/test/fixtures/vpcs_123456_subnets_789.json @@ -17,6 +17,15 @@ ] } ], + "databases": [ + { + "id": 12345, + "ipv4_range": "10.0.0.0/24", + "ipv6_ranges": [ + "2001:db8::/64" + ] + } + ], "created": "2018-01-01T00:01:01", "updated": "2018-01-01T00:01:01" } \ No newline at end of file diff --git a/test/unit/objects/vpc_test.py b/test/unit/objects/vpc_test.py index 5e7be1b69..34431a3b7 100644 --- a/test/unit/objects/vpc_test.py +++ b/test/unit/objects/vpc_test.py @@ -120,7 +120,13 @@ def validate_vpc_subnet_789(self, subnet: VPCSubnet): self.assertEqual(subnet.label, "test-subnet") self.assertEqual(subnet.ipv4, "10.0.0.0/24") + self.assertEqual(subnet.linodes[0].id, 12345) + + self.assertEqual(subnet.databases[0].id, 12345) + self.assertEqual(subnet.databases[0].ipv4_range, "10.0.0.0/24") + self.assertEqual(subnet.databases[0].ipv6_ranges, ["2001:db8::/64"]) + self.assertEqual(subnet.created, expected_dt) self.assertEqual(subnet.updated, expected_dt)