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 18cd1f34c8)
(cherry picked from commit f03a2df9aa)
(cherry picked from commit 6a380e3775)
(cherry picked from commit 4b5849c8d8)
(cherry picked from commit 44b792bdea)
This commit is contained in:
Gregory Thiemonge
2023-04-07 08:04:45 -04:00
parent a1c40d42dd
commit 38714459f6
5 changed files with 79 additions and 3 deletions

View File

@@ -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",

View File

@@ -17,6 +17,7 @@ import os
import re
import stat
import subprocess
import typing as tp
import jinja2
from oslo_config import cfg
@@ -409,3 +410,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))

View File

@@ -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',

View File

@@ -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()

View File

@@ -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.