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
|
import webob
|
||||||
from werkzeug import exceptions
|
from werkzeug import exceptions
|
||||||
|
|
||||||
|
from octavia.amphorae.backends.agent.api_server import util
|
||||||
from octavia.common import constants as consts
|
from octavia.common import constants as consts
|
||||||
|
|
||||||
|
|
||||||
@ -196,6 +197,8 @@ class Plug(object):
|
|||||||
fixed_ips=fixed_ips,
|
fixed_ips=fixed_ips,
|
||||||
mtu=mtu)
|
mtu=mtu)
|
||||||
self._osutils.bring_interface_up(existing_interface, 'network')
|
self._osutils.bring_interface_up(existing_interface, 'network')
|
||||||
|
|
||||||
|
util.send_member_advertisements(fixed_ips)
|
||||||
return webob.Response(json={
|
return webob.Response(json={
|
||||||
'message': "OK",
|
'message': "OK",
|
||||||
'details': "Updated existing interface {interface}".format(
|
'details': "Updated existing interface {interface}".format(
|
||||||
@ -236,6 +239,7 @@ class Plug(object):
|
|||||||
IFLA_IFNAME=netns_interface)
|
IFLA_IFNAME=netns_interface)
|
||||||
|
|
||||||
self._osutils.bring_interface_up(netns_interface, 'network')
|
self._osutils.bring_interface_up(netns_interface, 'network')
|
||||||
|
util.send_member_advertisements(fixed_ips)
|
||||||
|
|
||||||
return webob.Response(json={
|
return webob.Response(json={
|
||||||
'message': "OK",
|
'message': "OK",
|
||||||
|
@ -17,6 +17,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import stat
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import typing as tp
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
@ -411,3 +412,23 @@ def send_vip_advertisements(lb_id):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.debug('Send VIP advertisement failed due to :%s. '
|
LOG.debug('Send VIP advertisement failed due to :%s. '
|
||||||
'This amphora may not be the MASTER. Ignoring.', str(e))
|
'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")
|
"BaseOS.write_port_interface_file")
|
||||||
@mock.patch("octavia.amphorae.backends.agent.api_server.osutils."
|
@mock.patch("octavia.amphorae.backends.agent.api_server.osutils."
|
||||||
"BaseOS.bring_interface_up")
|
"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_netns, mock_iproute,
|
||||||
mock_by_mac, mock_interface_exists, mock_webob):
|
mock_by_mac, mock_interface_exists, mock_webob):
|
||||||
fixed_ips = [
|
fixed_ips = [
|
||||||
@ -284,6 +287,7 @@ class TestPlug(base.TestCase):
|
|||||||
mock_write_port_interface.assert_called_once_with(
|
mock_write_port_interface.assert_called_once_with(
|
||||||
interface='eth0', fixed_ips=fixed_ips, mtu=mtu)
|
interface='eth0', fixed_ips=fixed_ips, mtu=mtu)
|
||||||
mock_if_up.assert_called_once_with('eth0', 'network')
|
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(
|
mock_webob.Response.assert_any_call(
|
||||||
json={'message': 'OK',
|
json={'message': 'OK',
|
||||||
@ -300,7 +304,10 @@ class TestPlug(base.TestCase):
|
|||||||
"BaseOS.write_port_interface_file")
|
"BaseOS.write_port_interface_file")
|
||||||
@mock.patch("octavia.amphorae.backends.agent.api_server.osutils."
|
@mock.patch("octavia.amphorae.backends.agent.api_server.osutils."
|
||||||
"BaseOS.bring_interface_up")
|
"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_write_port_interface,
|
||||||
mock_netns, mock_by_mac,
|
mock_netns, mock_by_mac,
|
||||||
mock_interface_exists,
|
mock_interface_exists,
|
||||||
@ -327,6 +334,7 @@ class TestPlug(base.TestCase):
|
|||||||
mock_write_port_interface.assert_called_once_with(
|
mock_write_port_interface.assert_called_once_with(
|
||||||
interface=FAKE_INTERFACE, fixed_ips=fixed_ips, mtu=mtu)
|
interface=FAKE_INTERFACE, fixed_ips=fixed_ips, mtu=mtu)
|
||||||
mock_if_up.assert_called_once_with(FAKE_INTERFACE, 'network')
|
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(
|
mock_webob.Response.assert_any_call(
|
||||||
json={'message': 'OK',
|
json={'message': 'OK',
|
||||||
@ -344,8 +352,10 @@ class TestPlug(base.TestCase):
|
|||||||
"BaseOS.write_vip_interface_file")
|
"BaseOS.write_vip_interface_file")
|
||||||
@mock.patch("octavia.amphorae.backends.agent.api_server.osutils."
|
@mock.patch("octavia.amphorae.backends.agent.api_server.osutils."
|
||||||
"BaseOS.bring_interface_up")
|
"BaseOS.bring_interface_up")
|
||||||
|
@mock.patch("octavia.amphorae.backends.agent.api_server.util."
|
||||||
|
"send_member_advertisements")
|
||||||
def test_plug_network_on_vip(
|
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):
|
mock_netns, mock_by_mac, mock_interface_exists, mock_webob):
|
||||||
fixed_ips = [
|
fixed_ips = [
|
||||||
{'ip_address': FAKE_IP_IPV4,
|
{'ip_address': FAKE_IP_IPV4,
|
||||||
@ -394,6 +404,7 @@ class TestPlug(base.TestCase):
|
|||||||
fixed_ips=fixed_ips, mtu=mtu)
|
fixed_ips=fixed_ips, mtu=mtu)
|
||||||
|
|
||||||
mock_if_up.assert_called_once_with(FAKE_INTERFACE, 'vip')
|
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(
|
mock_webob.Response.assert_any_call(
|
||||||
json={'message': 'OK',
|
json={'message': 'OK',
|
||||||
|
@ -406,3 +406,38 @@ class TestUtil(base.TestCase):
|
|||||||
util.send_vip_advertisements(LB_ID1)
|
util.send_vip_advertisements(LB_ID1)
|
||||||
mock_get_int_name.assert_not_called()
|
mock_get_int_name.assert_not_called()
|
||||||
mock_send_advert.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