From cde5580bf1f2850c90733eea2a6a7e3b46e22b63 Mon Sep 17 00:00:00 2001 From: yatinkarel Date: Thu, 20 Feb 2025 20:26:54 +0530 Subject: [PATCH] [OVN] Add option to allow configuring dns ovn-owned Added a configuration option '[ovn]dns_records_ovn_owned' to allow setting 'ovn-owned' DNS option added as part of [1]. The Default is False so no change in the current behavior. If this option is set to True for OVN version 24.03 and above, DNS records will be treated local to the OVN controller and it will respond to the queries for the records and record types known to it else it will forward them to the configured DNS Server(s). Also added a maintenance task to update the option in all the DNS records as per the config option with neutron restart. [1] https://github.com/ovn-org/ovn/commit/1622526ff Depends-On: https://review.opendev.org/c/openstack/requirements/+/942797 Depends-On: https://review.opendev.org/c/openstack/ovsdbapp/+/942367 Related-Issue: https://issues.redhat.com/browse/OSPRH-10758 Related-Bug: #2059405 Change-Id: Ia645e8539753c03eb6ead9a868ba5bf194e9a724 --- neutron/common/ovn/constants.py | 1 + .../conf/plugins/ml2/drivers/ovn/ovn_conf.py | 12 +++++ .../ovn/mech_driver/ovsdb/maintenance.py | 23 +++++++++ .../ovn/mech_driver/ovsdb/ovn_client.py | 8 ++++ .../ovn/mech_driver/ovsdb/test_maintenance.py | 28 +++++++++++ .../ovn/mech_driver/ovsdb/test_maintenance.py | 47 +++++++++++++++++++ ...rds_ovn_owned-config-120ef08d5cf659f2.yaml | 12 +++++ requirements.txt | 2 +- 8 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-dns_records_ovn_owned-config-120ef08d5cf659f2.yaml diff --git a/neutron/common/ovn/constants.py b/neutron/common/ovn/constants.py index 8c860843101..70c44b9c65c 100644 --- a/neutron/common/ovn/constants.py +++ b/neutron/common/ovn/constants.py @@ -67,6 +67,7 @@ OVN_GATEWAY_NAT_ADDRESSES_KEY = 'nat-addresses' OVN_ROUTER_PORT_EXCLUDE_LB_VIPS_GARP = 'exclude-lb-vips-from-garp' OVN_DROP_PORT_GROUP_NAME = 'neutron_pg_drop' OVN_ROUTER_PORT_GW_MTU_OPTION = 'gateway_mtu' +OVN_OWNED = 'ovn-owned' OVN_PROVNET_PORT_NAME_PREFIX = 'provnet-' OVN_NAME_PREFIX = 'neutron-' diff --git a/neutron/conf/plugins/ml2/drivers/ovn/ovn_conf.py b/neutron/conf/plugins/ml2/drivers/ovn/ovn_conf.py index 41c49cd235b..c1a4222c23f 100644 --- a/neutron/conf/plugins/ml2/drivers/ovn/ovn_conf.py +++ b/neutron/conf/plugins/ml2/drivers/ovn/ovn_conf.py @@ -154,6 +154,14 @@ ovn_opts = [ "field is empty. If both subnet's dns_nameservers and " "this option are empty, then the DNS resolvers on the " "host running the neutron server will be used.")), + cfg.BoolOpt('dns_records_ovn_owned', + default=False, + help=_("Whether to consider DNS records local to OVN or not. " + "For OVN version 24.03 and above if this option is set " + "to True, DNS records will be treated local to the OVN " + "controller and it will respond to the queries for the " + "records and record types known to it, else it will " + "forward them to the configured DNS server(s).")), cfg.DictOpt('ovn_dhcp4_global_options', default={}, help=_("Dictionary of global DHCPv4 options which will be " @@ -369,6 +377,10 @@ def get_dns_servers(): return cfg.CONF.ovn.dns_servers +def is_dns_records_ovn_owned(): + return cfg.CONF.ovn.dns_records_ovn_owned + + def get_global_dhcpv4_opts(): return cfg.CONF.ovn.ovn_dhcp4_global_options diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py index df763bcea90..a7a3df8dd4e 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py @@ -1111,6 +1111,29 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase): check_error=True) raise periodics.NeverAgain() + @has_lock_periodic( + periodic_run_limit=ovn_const.MAINTENANCE_TASK_RETRY_LIMIT, + spacing=ovn_const.MAINTENANCE_ONE_RUN_TASK_SPACING, + run_immediately=True) + def set_ovn_owned_dns_option(self): + """Set the ovn_owned option as configured for the DNS records""" + cmds = [] + ovn_owned = ('true' if ovn_conf.is_dns_records_ovn_owned() + else 'false') + dns_options = {ovn_const.OVN_OWNED: ovn_owned} + for dns in self._nb_idl.dns_list().execute(check_error=True): + if ('ls_name' in dns.external_ids and + dns.options.get(ovn_const.OVN_OWNED) != ovn_owned): + cmds.append(self._nb_idl.dns_set_options( + dns.uuid, **dns_options)) + + if cmds: + with self._nb_idl.transaction(check_error=True) as txn: + for cmd in cmds: + txn.add(cmd) + + raise periodics.NeverAgain() + class HashRingHealthCheckPeriodics: diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py index aa1c989a63a..fc341b35ee7 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py @@ -2891,6 +2891,14 @@ class OVNClient: txn.add(self._nb_idl.ls_set_dns_records(ls.uuid, dns_add_txn)) return + # Only run when options column is available + if hasattr(ls_dns_record, 'options'): + ovn_owned = ('true' if ovn_conf.is_dns_records_ovn_owned() + else 'false') + dns_options = {ovn_const.OVN_OWNED: ovn_owned} + txn.add(self._nb_idl.dns_set_options(ls_dns_record.uuid, + **dns_options)) + if original_port: old_records = self.get_port_dns_records(original_port) diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py index 0ffb5e0db34..162894d7cd5 100644 --- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py +++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py @@ -1425,6 +1425,34 @@ class TestMaintenance(_TestMaintenanceHelper): original_value=True, config_value=True) + def test_set_ovn_owned_dns_option(self): + neutron_net = self._create_network('network1') + ls_name = utils.ovn_name(neutron_net['id']) + with mock.patch.object( + self._ovn_client, 'is_dns_required_for_port', + return_value=True): + self._create_port('portdns', neutron_net['id']) + + ls, ls_dns_record = self.nb_api.get_ls_and_dns_record(ls_name) + + # Assert that option is not set + self.assertNotEqual( + ls_dns_record.options.get('ovn-owned'), 'true') + + # Override config + cfg.CONF.set_override( + 'dns_records_ovn_owned', True, group='ovn') + + # Call the maintenance task and check that the option has been + # updated in the DNS record + self.assertRaises( + periodics.NeverAgain, + self.maint.set_ovn_owned_dns_option) + + # Assert that option is not set + self.assertEqual( + ls_dns_record.options.get('ovn-owned'), 'true') + class TestLogMaintenance(_TestMaintenanceHelper, test_log_driver.LogApiTestCaseBase): diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py index a891e99a3d1..613c6563a2d 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py @@ -978,3 +978,50 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight, mock.call(sroute_a, external_ids=external_ids), mock.call(sroute_b, external_ids=external_ids), ]) + + def _test_set_ovn_owned_dns_option(self, dns): + nb_idl = self.fake_ovn_client._nb_idl + nb_idl.dns_list.return_value.execute.return_value = [dns] + + self.assertRaises( + periodics.NeverAgain, + self.periodic.set_ovn_owned_dns_option) + + def test_set_ovn_owned_dns_option(self): + cfg.CONF.set_override('dns_records_ovn_owned', 'true', + group='ovn') + dns = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'external_ids': {'ls_name': 'neutron-foo'}, + 'options': {constants.OVN_OWNED: 'false'}}) + + self._test_set_ovn_owned_dns_option(dns) + + ovn_owned = ('true' if ovn_conf.is_dns_records_ovn_owned() + else 'false') + dns_options = {constants.OVN_OWNED: ovn_owned} + + self.fake_ovn_client._nb_idl.dns_set_options.assert_called_once_with( + dns.uuid, **dns_options) + + def test_set_ovn_owned_dns_option_already_set(self): + cfg.CONF.set_override('dns_records_ovn_owned', 'true', + group='ovn') + dns = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'external_ids': {'ls_name': 'neutron-foo'}, + 'options': {constants.OVN_OWNED: 'true'}}) + + self._test_set_ovn_owned_dns_option(dns) + + # Assert there was no transactions because the value was already set + self.fake_ovn_client._nb_idl.dns_set_options.assert_not_called() + + def test_set_ovn_owned_dns_option_ovn_direct_record(self): + dns = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'external_ids': {'ovn_direct': 'ovn-foo'}, + 'options': {constants.OVN_OWNED: 'true'}}) + + self._test_set_ovn_owned_dns_option(dns) + + # Assert there was no transactions because the record directly + # created in ovn i.e not created by neutron + self.fake_ovn_client._nb_idl.dns_set_options.assert_not_called() diff --git a/releasenotes/notes/add-dns_records_ovn_owned-config-120ef08d5cf659f2.yaml b/releasenotes/notes/add-dns_records_ovn_owned-config-120ef08d5cf659f2.yaml new file mode 100644 index 00000000000..107bac991fc --- /dev/null +++ b/releasenotes/notes/add-dns_records_ovn_owned-config-120ef08d5cf659f2.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + For OVN version 24.03 and above you can now configure DNS records to be + local to OVN by setting the new configuration option + ``[ovn]dns_records_ovn_owned``. + If this option is set to True, DNS records will be treated local to the OVN + controller and it will respond to the queries for the records and record + types known to it, else it will forward them to the configured DNS server(s). + For more information, see bug + `2059405 `_. + Default is False. diff --git a/requirements.txt b/requirements.txt index 4a54a021e3e..c9774e9d1b2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -45,7 +45,7 @@ osprofiler>=2.3.0 # Apache-2.0 os-ken>=2.11.2 # Apache-2.0 os-resource-classes>=1.1.0 # Apache-2.0 ovs>=2.12.0 # Apache-2.0 -ovsdbapp>=2.7.1 # Apache-2.0 +ovsdbapp>=2.11.0 # Apache-2.0 psutil>=6.1.0 # BSD pyroute2>=0.7.3;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2) pyOpenSSL>=17.1.0 # Apache-2.0