Skip to content

Commit 1a22739

Browse files
oschwartTafkaMax
authored andcommitted
Fix deletion of floating IPs with managed DNS records
Neutron's Designate external DNS driver does not pass edit_managed=True when constructing the admin designateclient. When a floating IP has associated managed DNS records in Designate, deleting the floating IP fails with HTTP 500 because Designate rejects the deletion with "Managed records may not be deleted". Pass edit_managed=True only to the admin_client, which has admin credentials and passes Designate's edit_managed_records policy check. The regular user client must not set this flag because Designate requires SYSTEM_ADMIN role for it. For forward DNS deletion of managed records, fall back to admin_client when the user client gets a BadRequest from Designate. Closes-Bug: #2149807 Assisted-By: Claude Code 4.6 Opus Change-Id: Iedf0f26708560fe063a71925d5d39542ca181154 Signed-off-by: Omer <oschwart@redhat.com> (cherry picked from commit 44d061a)
1 parent d57e150 commit 1a22739

3 files changed

Lines changed: 62 additions & 2 deletions

File tree

neutron/services/externaldns/drivers/designate/driver.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ def get_clients(context):
4646
client = d_client.Client(session=_SESSION, auth=auth)
4747
admin_auth = loading.load_auth_from_conf_options(CONF, 'designate')
4848
admin_client = d_client.Client(session=_SESSION, auth=admin_auth,
49-
endpoint_override=CONF.designate.url)
49+
endpoint_override=CONF.designate.url,
50+
edit_managed=True)
5051
return client, admin_client
5152

5253

@@ -149,7 +150,12 @@ def delete_record_set(self, context, dns_domain, dns_name, records):
149150
dns_name, dns_domain), records, client)
150151

151152
for _id in ids_to_delete:
152-
client.recordsets.delete(dns_domain, _id)
153+
# Try user client first: admin_client can't see user-owned zones.
154+
# Fall back to admin_client for managed records (edit_managed).
155+
try:
156+
client.recordsets.delete(dns_domain, _id)
157+
except d_exc.BadRequest:
158+
admin_client.recordsets.delete(dns_domain, _id)
153159
if not CONF.designate.allow_reverse_dns_lookup:
154160
return
155161

neutron/tests/unit/services/externaldns/drivers/designate/test_driver.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,51 @@ def test_delete_record_set_zone_not_found(self):
314314
'test', ['192.168.0.10']
315315
)
316316

317+
@mock.patch.object(driver, '_SESSION', new=mock.Mock())
318+
@mock.patch.object(driver, 'd_client')
319+
@mock.patch('keystoneauth1.token_endpoint.Token')
320+
@mock.patch('keystoneauth1.loading.load_auth_from_conf_options')
321+
@mock.patch.object(driver, 'get_clients',
322+
side_effect=driver.get_clients)
323+
def test_admin_client_passes_edit_managed(
324+
self, mock_get_clients, mock_load_auth, mock_token,
325+
mock_d_client):
326+
mock_get_clients(self.context)
327+
self.assertEqual(2, mock_d_client.Client.call_count)
328+
user_call, admin_call = mock_d_client.Client.call_args_list
329+
self.assertNotIn('edit_managed', user_call.kwargs)
330+
self.assertTrue(admin_call.kwargs.get('edit_managed'))
331+
332+
@mock.patch.object(driver, '_SESSION', new=mock.Mock())
333+
@mock.patch.object(driver, 'd_client')
334+
@mock.patch('keystoneauth1.token_endpoint.Token')
335+
@mock.patch.object(driver, 'get_all_projects_client',
336+
side_effect=driver.get_all_projects_client)
337+
def test_all_projects_client_no_edit_managed(
338+
self, mock_get_all, mock_token, mock_d_client):
339+
mock_get_all(self.context)
340+
call = mock_d_client.Client.call_args
341+
self.assertNotIn('edit_managed', call.kwargs)
342+
343+
def test_delete_managed_record_falls_back_to_admin(self):
344+
self.client.recordsets.list.return_value = [
345+
{'id': 123, 'records': ['192.168.0.10']}
346+
]
347+
self.client.recordsets.delete.side_effect = d_exc.BadRequest
348+
349+
cfg.CONF.set_override(
350+
'allow_reverse_dns_lookup', False, group='designate'
351+
)
352+
353+
self.driver.delete_record_set(
354+
self.context, 'example.test.', 'test', ['192.168.0.10']
355+
)
356+
357+
self.client.recordsets.delete.assert_called_once_with(
358+
'example.test.', 123)
359+
self.admin_client.recordsets.delete.assert_called_once_with(
360+
'example.test.', 123)
361+
317362
def test_ipv4_ptr_is_misconfigured(self):
318363
self.assertRaises(
319364
ValueError,
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed an issue where deleting a floating IP with ``dns_name`` and
5+
``dns_domain`` set would fail with a 500 error when the associated
6+
Designate DNS records were managed. The Designate external DNS driver
7+
now passes ``edit_managed=True`` to the designateclient, allowing
8+
deletion of managed records. For more information see bug
9+
`2149807 <https://bugs.launchpad.net/neutron/+bug/2149807>`_.

0 commit comments

Comments
 (0)