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 Change-Id: Iecedb9e9c8f293cd4c067900e1696f5050769ee8
This commit is contained in:
parent
cad146f1bb
commit
18cd1f34c8
@ -24,6 +24,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
|
||||
|
||||
|
||||
@ -196,6 +197,8 @@ 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={
|
||||
'message': "OK",
|
||||
'details': "Updated existing interface {interface}".format(
|
||||
@ -236,6 +239,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={
|
||||
'message': "OK",
|
||||
|
@ -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))
|
||||
|
@ -265,7 +265,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 = [
|
||||
@ -284,6 +287,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',
|
||||
@ -300,7 +304,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,
|
||||
@ -327,6 +334,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',
|
||||
@ -344,8 +352,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,
|
||||
@ -394,6 +404,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',
|
||||
|
@ -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()
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user