Avoid interface name collisions in the amphora
After unpluging a network, the octavia-agent may try to plug a new
network to a new interface while another interface with the same name
already exists.
Closes-Bug: #2017894
Change-Id: I7f150c2c1c3da706055aba5e02b5ad7c032806f1
(cherry picked from commit c4dd87f9b1
)
This commit is contained in:
parent
b61ba9809b
commit
1b17529b13
@ -14,6 +14,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
import itertools
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import stat
|
import stat
|
||||||
@ -212,13 +213,7 @@ class Plug(object):
|
|||||||
|
|
||||||
# We need to determine the interface name when inside the namespace
|
# We need to determine the interface name when inside the namespace
|
||||||
# to avoid name conflicts
|
# to avoid name conflicts
|
||||||
with pyroute2.NetNS(consts.AMPHORA_NAMESPACE,
|
netns_interface = self._netns_get_next_interface()
|
||||||
flags=os.O_CREAT) as netns:
|
|
||||||
|
|
||||||
# 1 means just loopback, but we should already have a VIP. This
|
|
||||||
# works for the add/delete/add case as we don't delete interfaces
|
|
||||||
# Note, eth0 is skipped because that is the VIP interface
|
|
||||||
netns_interface = 'eth{0}'.format(len(netns.get_links()))
|
|
||||||
|
|
||||||
LOG.info('Plugged interface %s will become %s in the namespace %s',
|
LOG.info('Plugged interface %s will become %s in the namespace %s',
|
||||||
default_netns_interface, netns_interface,
|
default_netns_interface, netns_interface,
|
||||||
@ -293,3 +288,16 @@ class Plug(object):
|
|||||||
|
|
||||||
def _netns_interface_exists(self, mac_address):
|
def _netns_interface_exists(self, mac_address):
|
||||||
return self._netns_interface_by_mac(mac_address) is not None
|
return self._netns_interface_by_mac(mac_address) is not None
|
||||||
|
|
||||||
|
def _netns_get_next_interface(self):
|
||||||
|
with pyroute2.NetNS(consts.AMPHORA_NAMESPACE,
|
||||||
|
flags=os.O_CREAT) as netns:
|
||||||
|
existing_ifaces = [
|
||||||
|
dict(link['attrs']).get(consts.IFLA_IFNAME)
|
||||||
|
for link in netns.get_links()]
|
||||||
|
# find the first unused ethXXX
|
||||||
|
for idx in itertools.count(start=2):
|
||||||
|
iface_name = f"eth{idx}"
|
||||||
|
if iface_name not in existing_ifaces:
|
||||||
|
break
|
||||||
|
return iface_name
|
||||||
|
@ -1008,7 +1008,9 @@ class TestServerTestCase(base.TestCase):
|
|||||||
|
|
||||||
mock_int_exists.return_value = False
|
mock_int_exists.return_value = False
|
||||||
netns_handle = mock_netns.return_value.__enter__.return_value
|
netns_handle = mock_netns.return_value.__enter__.return_value
|
||||||
netns_handle.get_links.return_value = [0] * test_int_num
|
netns_handle.get_links.return_value = [
|
||||||
|
{'attrs': [['IFLA_IFNAME', f'eth{idx}']]}
|
||||||
|
for idx in range(test_int_num)]
|
||||||
mock_isfile.return_value = True
|
mock_isfile.return_value = True
|
||||||
|
|
||||||
mock_check_output.return_value = b"1\n2\n3\n"
|
mock_check_output.return_value = b"1\n2\n3\n"
|
||||||
@ -1396,7 +1398,7 @@ class TestServerTestCase(base.TestCase):
|
|||||||
|
|
||||||
netns_handle = mock_netns.return_value.__enter__.return_value
|
netns_handle = mock_netns.return_value.__enter__.return_value
|
||||||
netns_handle.get_links.return_value = [{
|
netns_handle.get_links.return_value = [{
|
||||||
'attrs': [['IFLA_IFNAME', consts.NETNS_PRIMARY_INTERFACE]]}]
|
'attrs': [['IFLA_IFNAME', 'eth2']]}]
|
||||||
|
|
||||||
port_info = {'mac_address': MAC, 'mtu': 1450, 'fixed_ips': [
|
port_info = {'mac_address': MAC, 'mtu': 1450, 'fixed_ips': [
|
||||||
{'ip_address': IP, 'subnet_cidr': SUBNET_CIDR,
|
{'ip_address': IP, 'subnet_cidr': SUBNET_CIDR,
|
||||||
@ -1405,8 +1407,7 @@ class TestServerTestCase(base.TestCase):
|
|||||||
|
|
||||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||||
file_name = '/etc/octavia/interfaces/{}.json'.format(
|
file_name = '/etc/octavia/interfaces/eth3.json'
|
||||||
consts.NETNS_PRIMARY_INTERFACE)
|
|
||||||
|
|
||||||
m = self.useFixture(test_utils.OpenFixture(file_name)).mock_open
|
m = self.useFixture(test_utils.OpenFixture(file_name)).mock_open
|
||||||
with mock.patch('os.open') as mock_open, mock.patch.object(
|
with mock.patch('os.open') as mock_open, mock.patch.object(
|
||||||
@ -1436,7 +1437,7 @@ class TestServerTestCase(base.TestCase):
|
|||||||
mock_fdopen.assert_any_call(123, 'r+')
|
mock_fdopen.assert_any_call(123, 'r+')
|
||||||
|
|
||||||
expected_dict = {
|
expected_dict = {
|
||||||
consts.NAME: consts.NETNS_PRIMARY_INTERFACE,
|
consts.NAME: 'eth3',
|
||||||
consts.MTU: 1450,
|
consts.MTU: 1450,
|
||||||
consts.ADDRESSES: [
|
consts.ADDRESSES: [
|
||||||
{
|
{
|
||||||
@ -1458,13 +1459,12 @@ class TestServerTestCase(base.TestCase):
|
|||||||
consts.SCRIPTS: {
|
consts.SCRIPTS: {
|
||||||
consts.IFACE_UP: [{
|
consts.IFACE_UP: [{
|
||||||
consts.COMMAND: (
|
consts.COMMAND: (
|
||||||
"/usr/local/bin/lvs-masquerade.sh add ipv4 "
|
"/usr/local/bin/lvs-masquerade.sh add ipv4 eth3")
|
||||||
"{}".format(consts.NETNS_PRIMARY_INTERFACE))
|
|
||||||
}],
|
}],
|
||||||
consts.IFACE_DOWN: [{
|
consts.IFACE_DOWN: [{
|
||||||
consts.COMMAND: (
|
consts.COMMAND: (
|
||||||
"/usr/local/bin/lvs-masquerade.sh delete ipv4 "
|
"/usr/local/bin/lvs-masquerade.sh delete ipv4 "
|
||||||
"{}".format(consts.NETNS_PRIMARY_INTERFACE))
|
"eth3")
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1476,8 +1476,7 @@ class TestServerTestCase(base.TestCase):
|
|||||||
|
|
||||||
mock_check_output.assert_called_with(
|
mock_check_output.assert_called_with(
|
||||||
['ip', 'netns', 'exec', consts.AMPHORA_NAMESPACE,
|
['ip', 'netns', 'exec', consts.AMPHORA_NAMESPACE,
|
||||||
'amphora-interface', 'up',
|
'amphora-interface', 'up', 'eth3'], stderr=-2)
|
||||||
consts.NETNS_PRIMARY_INTERFACE], stderr=-2)
|
|
||||||
|
|
||||||
def test_ubuntu_plug_VIP4(self):
|
def test_ubuntu_plug_VIP4(self):
|
||||||
self._test_plug_VIP4(consts.UBUNTU)
|
self._test_plug_VIP4(consts.UBUNTU)
|
||||||
|
@ -285,13 +285,13 @@ class TestPlug(base.TestCase):
|
|||||||
self.test_plug.plug_network(FAKE_MAC_ADDRESS, fixed_ips, 1400)
|
self.test_plug.plug_network(FAKE_MAC_ADDRESS, fixed_ips, 1400)
|
||||||
|
|
||||||
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='eth2', fixed_ips=fixed_ips, mtu=mtu)
|
||||||
mock_if_up.assert_called_once_with('eth0', 'network')
|
mock_if_up.assert_called_once_with('eth2', 'network')
|
||||||
mock_send_member_adv.assert_called_once_with(fixed_ips)
|
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',
|
||||||
'details': 'Plugged on interface eth0'},
|
'details': 'Plugged on interface eth2'},
|
||||||
status=202)
|
status=202)
|
||||||
|
|
||||||
@mock.patch.object(plug, "webob")
|
@mock.patch.object(plug, "webob")
|
||||||
@ -411,3 +411,40 @@ class TestPlug(base.TestCase):
|
|||||||
'details': 'Updated existing interface {}'.format(
|
'details': 'Updated existing interface {}'.format(
|
||||||
FAKE_INTERFACE)},
|
FAKE_INTERFACE)},
|
||||||
status=202)
|
status=202)
|
||||||
|
|
||||||
|
@mock.patch('pyroute2.NetNS', create=True)
|
||||||
|
def test__netns_get_next_interface(self, mock_netns):
|
||||||
|
netns_handle = mock_netns.return_value.__enter__.return_value
|
||||||
|
|
||||||
|
netns_handle.get_links.return_value = [
|
||||||
|
{'attrs': [['IFLA_IFNAME', 'lo']]},
|
||||||
|
]
|
||||||
|
|
||||||
|
ifname = self.test_plug._netns_get_next_interface()
|
||||||
|
self.assertEqual('eth2', ifname)
|
||||||
|
|
||||||
|
netns_handle.get_links.return_value = [
|
||||||
|
{'attrs': [['IFLA_IFNAME', 'lo']]},
|
||||||
|
{'attrs': [['IFLA_IFNAME', 'eth1']]},
|
||||||
|
{'attrs': [['IFLA_IFNAME', 'eth2']]},
|
||||||
|
{'attrs': [['IFLA_IFNAME', 'eth3']]},
|
||||||
|
]
|
||||||
|
|
||||||
|
ifname = self.test_plug._netns_get_next_interface()
|
||||||
|
self.assertEqual('eth4', ifname)
|
||||||
|
|
||||||
|
netns_handle.get_links.return_value = [
|
||||||
|
{'attrs': [['IFLA_IFNAME', 'lo']]},
|
||||||
|
{'attrs': [['IFLA_IFNAME', 'eth1']]},
|
||||||
|
{'attrs': [['IFLA_IFNAME', 'eth3']]},
|
||||||
|
]
|
||||||
|
|
||||||
|
ifname = self.test_plug._netns_get_next_interface()
|
||||||
|
self.assertEqual('eth2', ifname)
|
||||||
|
|
||||||
|
netns_handle.get_links.return_value = [
|
||||||
|
{'attrs': [['IFLA_IFNAME', f'eth{idx}']]}
|
||||||
|
for idx in range(2, 1000)]
|
||||||
|
|
||||||
|
ifname = self.test_plug._netns_get_next_interface()
|
||||||
|
self.assertEqual('eth1000', ifname)
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Fixed a potential error when plugging a member from a new network after
|
||||||
|
deleting another member and unplugging its network. Octavia may have tried
|
||||||
|
to plug the new network to a new interface but with an already existing
|
||||||
|
name.
|
||||||
|
This fix requires to update the Amphora image.
|
Loading…
Reference in New Issue
Block a user