From 44b792bdeaae37b4a14a5c4a256a7a8b98ee1de6 Mon Sep 17 00:00:00 2001 From: Gregory Thiemonge Date: Fri, 7 Apr 2023 08:04:45 -0400 Subject: [PATCH] Send IP advertisements when plugging a new member subnet When plugging a new subnet, the amphora sends a packet advertising the IP address of the port. In case previously deallocated IP addresses are re-used, it helps the other resources on the same L2 network to flush the old ARP entries. Closes-Bug: #2015572 Conflicts: octavia/amphorae/backends/agent/api_server/plug.py Change-Id: Iecedb9e9c8f293cd4c067900e1696f5050769ee8 (cherry picked from commit 18cd1f34c8a918ab6f969a3271ac74132c4ac736) (cherry picked from commit f03a2df9aa10f6901020292b2f2f4eaa912c4cb8) (cherry picked from commit 6a380e3775e10485f1ded67d12bab3725225c8bb) (cherry picked from commit 4b5849c8d8a673c6a4c2cf15b4fb5e69b2b6308f) --- .../backends/agent/api_server/plug.py | 3 ++ .../backends/agent/api_server/util.py | 21 +++++++++++ .../backends/agent/api_server/test_plug.py | 17 +++++++-- .../backends/agent/api_server/test_util.py | 35 +++++++++++++++++++ ...et-ip-advertisements-af2264844079ef6b.yaml | 6 ++++ 5 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/members-subnet-ip-advertisements-af2264844079ef6b.yaml diff --git a/octavia/amphorae/backends/agent/api_server/plug.py b/octavia/amphorae/backends/agent/api_server/plug.py index c624bd1007..472c84d3f3 100644 --- a/octavia/amphorae/backends/agent/api_server/plug.py +++ b/octavia/amphorae/backends/agent/api_server/plug.py @@ -25,6 +25,7 @@ import pyroute2 import webob from werkzeug import exceptions +from octavia.amphorae.backends.agent.api_server import util from octavia.common import constants as consts @@ -178,6 +179,7 @@ class Plug(object): fixed_ips=fixed_ips, mtu=mtu) self._osutils.bring_interface_up(existing_interface, 'network') + util.send_member_advertisements(fixed_ips) return webob.Response(json=dict( message="OK", details="Updated existing interface {interface}".format( @@ -218,6 +220,7 @@ class Plug(object): IFLA_IFNAME=netns_interface) self._osutils.bring_interface_up(netns_interface, 'network') + util.send_member_advertisements(fixed_ips) return webob.Response(json=dict( message="OK", diff --git a/octavia/amphorae/backends/agent/api_server/util.py b/octavia/amphorae/backends/agent/api_server/util.py index c648ee6488..e20cad2547 100644 --- a/octavia/amphorae/backends/agent/api_server/util.py +++ b/octavia/amphorae/backends/agent/api_server/util.py @@ -17,6 +17,7 @@ import os import re import stat import subprocess +import typing as tp import jinja2 from oslo_config import cfg @@ -411,3 +412,23 @@ def send_vip_advertisements(lb_id): except Exception as e: LOG.debug('Send VIP advertisement failed due to :%s. ' 'This amphora may not be the MASTER. Ignoring.', str(e)) + + +def send_member_advertisements(fixed_ips: tp.Iterable[tp.Dict[str, str]]): + """Sends advertisements for each fixed_ip of a list + + This method will send either GARP (IPv4) or neighbor advertisements (IPv6) + for the addresses of the subnets of the members. + + :param fixed_ips: a list of dicts that contain 'ip_address' elements + :returns: None + """ + try: + for fixed_ip in fixed_ips: + ip_address = fixed_ip[consts.IP_ADDRESS] + interface = network_utils.get_interface_name( + ip_address, net_ns=consts.AMPHORA_NAMESPACE) + ip_advertisement.send_ip_advertisement( + interface, ip_address, net_ns=consts.AMPHORA_NAMESPACE) + except Exception as e: + LOG.debug('Send member advertisement failed due to: %s', str(e)) diff --git a/octavia/tests/unit/amphorae/backends/agent/api_server/test_plug.py b/octavia/tests/unit/amphorae/backends/agent/api_server/test_plug.py index 217fd0f8b8..51da3ebccc 100644 --- a/octavia/tests/unit/amphorae/backends/agent/api_server/test_plug.py +++ b/octavia/tests/unit/amphorae/backends/agent/api_server/test_plug.py @@ -229,7 +229,10 @@ class TestPlug(base.TestCase): "BaseOS.write_port_interface_file") @mock.patch("octavia.amphorae.backends.agent.api_server.osutils." "BaseOS.bring_interface_up") - def test_plug_network(self, mock_if_up, mock_write_port_interface, + @mock.patch("octavia.amphorae.backends.agent.api_server.util." + "send_member_advertisements") + def test_plug_network(self, mock_send_member_adv, + mock_if_up, mock_write_port_interface, mock_netns, mock_iproute, mock_by_mac, mock_interface_exists, mock_webob): fixed_ips = [ @@ -248,6 +251,7 @@ class TestPlug(base.TestCase): mock_write_port_interface.assert_called_once_with( interface='eth0', fixed_ips=fixed_ips, mtu=mtu) mock_if_up.assert_called_once_with('eth0', 'network') + mock_send_member_adv.assert_called_once_with(fixed_ips) mock_webob.Response.assert_any_call( json={'message': 'OK', @@ -264,7 +268,10 @@ class TestPlug(base.TestCase): "BaseOS.write_port_interface_file") @mock.patch("octavia.amphorae.backends.agent.api_server.osutils." "BaseOS.bring_interface_up") - def test_plug_network_existing_interface(self, mock_if_up, + @mock.patch("octavia.amphorae.backends.agent.api_server.util." + "send_member_advertisements") + def test_plug_network_existing_interface(self, mock_send_member_adv, + mock_if_up, mock_write_port_interface, mock_netns, mock_by_mac, mock_interface_exists, @@ -291,6 +298,7 @@ class TestPlug(base.TestCase): mock_write_port_interface.assert_called_once_with( interface=FAKE_INTERFACE, fixed_ips=fixed_ips, mtu=mtu) mock_if_up.assert_called_once_with(FAKE_INTERFACE, 'network') + mock_send_member_adv.assert_called_once_with(fixed_ips) mock_webob.Response.assert_any_call( json={'message': 'OK', @@ -308,8 +316,10 @@ class TestPlug(base.TestCase): "BaseOS.write_vip_interface_file") @mock.patch("octavia.amphorae.backends.agent.api_server.osutils." "BaseOS.bring_interface_up") + @mock.patch("octavia.amphorae.backends.agent.api_server.util." + "send_member_advertisements") def test_plug_network_on_vip( - self, mock_if_up, mock_write_vip_interface, + self, mock_send_member_adv, mock_if_up, mock_write_vip_interface, mock_netns, mock_by_mac, mock_interface_exists, mock_webob): fixed_ips = [ {'ip_address': FAKE_IP_IPV4, @@ -350,6 +360,7 @@ class TestPlug(base.TestCase): fixed_ips=fixed_ips, mtu=mtu) mock_if_up.assert_called_once_with(FAKE_INTERFACE, 'vip') + mock_send_member_adv.assert_called_once_with(fixed_ips) mock_webob.Response.assert_any_call( json={'message': 'OK', diff --git a/octavia/tests/unit/amphorae/backends/agent/api_server/test_util.py b/octavia/tests/unit/amphorae/backends/agent/api_server/test_util.py index add80ca92c..83c62dd54b 100644 --- a/octavia/tests/unit/amphorae/backends/agent/api_server/test_util.py +++ b/octavia/tests/unit/amphorae/backends/agent/api_server/test_util.py @@ -406,3 +406,38 @@ class TestUtil(base.TestCase): util.send_vip_advertisements(LB_ID1) mock_get_int_name.assert_not_called() mock_send_advert.assert_not_called() + + @mock.patch('octavia.amphorae.backends.utils.ip_advertisement.' + 'send_ip_advertisement') + @mock.patch('octavia.amphorae.backends.utils.network_utils.' + 'get_interface_name') + def test_send_member_advertisements(self, mock_get_int_name, + mock_send_advert): + # IPv4 fixed_ips + mock_get_int_name.side_effect = ['fake0', 'fake1'] + fixed_ips = [{'ip_address': '192.0.2.1'}, + {'ip_address': '192.2.0.2'}] + util.send_member_advertisements(fixed_ips) + mock_send_advert.assert_has_calls( + [mock.call('fake0', fixed_ips[0]['ip_address'], + net_ns=consts.AMPHORA_NAMESPACE), + mock.call('fake1', fixed_ips[1]['ip_address'], + net_ns=consts.AMPHORA_NAMESPACE)]) + + # Mixed IPv4/IPv6 + mock_send_advert.reset_mock() + mock_get_int_name.side_effect = ['fake0', 'fake1'] + fixed_ips = [{'ip_address': '192.0.2.1'}, + {'ip_address': '2001:db8::2'}] + util.send_member_advertisements(fixed_ips) + mock_send_advert.assert_has_calls( + [mock.call('fake0', fixed_ips[0]['ip_address'], + net_ns=consts.AMPHORA_NAMESPACE), + mock.call('fake1', fixed_ips[1]['ip_address'], + net_ns=consts.AMPHORA_NAMESPACE)]) + + # Exception + mock_send_advert.reset_mock() + mock_get_int_name.side_effect = Exception('ERROR') + util.send_member_advertisements(fixed_ips) + mock_send_advert.assert_not_called() diff --git a/releasenotes/notes/members-subnet-ip-advertisements-af2264844079ef6b.yaml b/releasenotes/notes/members-subnet-ip-advertisements-af2264844079ef6b.yaml new file mode 100644 index 0000000000..2610ba8d20 --- /dev/null +++ b/releasenotes/notes/members-subnet-ip-advertisements-af2264844079ef6b.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + When plugging a new member subnet, the amphora sends an IP advertisement of + the newly allocated IP. It allows the servers on the same L2 network to + flush the ARP entries of a previously allocated IP address.