Check tap bridge timestamps to detect local changes
During a quick instance rebuild on Nova, it may remove a tap
interface and then plug it in within the agent polling window.
In this scenario the agent will not realize a device has changed
and will therefore not ask the server for info an subsequently
update its status. This will prevent the notification from being
sent back to Nova that the vif plugging has finished so the
VM will never resume state.
This adds a new timestamp collection method to the common agent
manager interface for devices that is used by the common agent
loop to determine if a device has locally changed.
The linux bridge implementation of it checks the timestamps on the
tap interface's 'bridge' directory, which will change whenever
the tap is added to bridge.
Conflicts:
neutron/agent/linux/bridge_lib.py
neutron/plugins/ml2/drivers/agent/_agent_manager_base.py
neutron/plugins/ml2/drivers/agent/_common_agent.py
neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py
neutron/plugins/ml2/drivers/macvtap/agent/macvtap_neutron_agent.py
neutron/tests/functional/agent/linux/test_bridge_lib.py
neutron/tests/unit/plugins/ml2/drivers/agent/test__common_agent.py
(cherry picked from commit 7afbd3a6b8
)
Change-Id: If172470e907848556b6a8aff13520f94245919bb
This commit is contained in:
parent
4b1d8dca9b
commit
a86274e8eb
|
@ -16,8 +16,22 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
|
||||
from neutron.agent.linux import ip_lib
|
||||
|
||||
# NOTE(toabctl): Don't use /sys/devices/virtual/net here because not all tap
|
||||
# devices are listed here (i.e. when using Xen)
|
||||
BRIDGE_FS = "/sys/class/net/"
|
||||
BRIDGE_PORT_FS_FOR_DEVICE = BRIDGE_FS + "%s/brport"
|
||||
|
||||
|
||||
def get_interface_bridged_time(interface):
|
||||
try:
|
||||
return os.stat(BRIDGE_PORT_FS_FOR_DEVICE % interface).st_mtime
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
class BridgeDevice(ip_lib.IPDevice):
|
||||
def _brctl(self, cmd):
|
||||
|
|
|
@ -615,6 +615,9 @@ class LinuxBridgeManager(object):
|
|||
device.link.delete()
|
||||
LOG.debug("Done deleting interface %s", interface)
|
||||
|
||||
def get_devices_modified_timestamps(self, devices):
|
||||
return {d: bridge_lib.get_interface_bridged_time(d) for d in devices}
|
||||
|
||||
def get_tap_devices(self):
|
||||
devices = set()
|
||||
for device in os.listdir(BRIDGE_FS):
|
||||
|
@ -1081,6 +1084,17 @@ class LinuxBridgeNeutronAgentRPC(service.Service):
|
|||
arp_protect.delete_arp_spoofing_protection(devices)
|
||||
return resync
|
||||
|
||||
@staticmethod
|
||||
def _get_devices_locally_modified(timestamps, previous_timestamps):
|
||||
"""Returns devices with previous timestamps that do not match new.
|
||||
|
||||
If a device did not have a timestamp previously, it will not be
|
||||
returned because this means it is new.
|
||||
"""
|
||||
return {device for device, timestamp in timestamps.items()
|
||||
if previous_timestamps.get(device) and
|
||||
timestamp != previous_timestamps.get(device)}
|
||||
|
||||
def scan_devices(self, previous, sync):
|
||||
device_info = {}
|
||||
|
||||
|
@ -1098,12 +1112,26 @@ class LinuxBridgeNeutronAgentRPC(service.Service):
|
|||
previous = {'added': set(),
|
||||
'current': set(),
|
||||
'updated': set(),
|
||||
'removed': set()}
|
||||
'removed': set(),
|
||||
'timestamps': {}}
|
||||
# clear any orphaned ARP spoofing rules (e.g. interface was
|
||||
# manually deleted)
|
||||
if self.prevent_arp_spoofing:
|
||||
arp_protect.delete_unreferenced_arp_protection(current_devices)
|
||||
|
||||
# check to see if any devices were locally modified based on their
|
||||
# timestamps changing since the previous iteration. If a timestamp
|
||||
# doesn't exist for a device, this calculation is skipped for that
|
||||
# device.
|
||||
device_info['timestamps'] = \
|
||||
self.br_mgr.get_devices_modified_timestamps(current_devices)
|
||||
locally_updated = self._get_devices_locally_modified(
|
||||
device_info['timestamps'], previous['timestamps'])
|
||||
if locally_updated:
|
||||
LOG.debug("Adding locally changed devices to updated set: %s",
|
||||
locally_updated)
|
||||
updated_devices |= locally_updated
|
||||
|
||||
if sync:
|
||||
# This is the first iteration, or the previous one had a problem.
|
||||
# Re-add all existing devices.
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# Copyright (c) 2015 Thales Services SAS
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
from neutron.agent.linux import bridge_lib
|
||||
from neutron.tests.common import net_helpers
|
||||
from neutron.tests.functional import base
|
||||
|
||||
|
||||
class BridgeLibTestCase(base.BaseSudoTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BridgeLibTestCase, self).setUp()
|
||||
self.bridge, self.port_fixture = self.create_bridge_port_fixture()
|
||||
|
||||
def create_bridge_port_fixture(self):
|
||||
bridge = self.useFixture(
|
||||
net_helpers.LinuxBridgeFixture(namespace=None)).bridge
|
||||
port_fixture = self.useFixture(
|
||||
net_helpers.LinuxBridgePortFixture(bridge))
|
||||
return bridge, port_fixture
|
||||
|
||||
def test_get_interface_bridged_time(self):
|
||||
port = self.port_fixture.br_port
|
||||
t1 = bridge_lib.get_interface_bridged_time(port)
|
||||
self.bridge.delif(port)
|
||||
self.bridge.addif(port)
|
||||
t2 = bridge_lib.get_interface_bridged_time(port)
|
||||
self.assertIsNotNone(t1)
|
||||
self.assertIsNotNone(t2)
|
||||
self.assertGreater(t2, t1)
|
|
@ -211,11 +211,22 @@ class TestLinuxBridgeAgent(base.BaseTestCase):
|
|||
agent.treat_devices_removed(devices)
|
||||
de_arp.assert_called_with(devices)
|
||||
|
||||
def test__get_devices_locally_modified(self):
|
||||
new_ts = {1: 1000, 2: 2000, 3: 3000}
|
||||
old_ts = {1: 10, 2: 2000, 4: 900}
|
||||
# 3 and 4 are not returned because 3 is a new device and 4 is a
|
||||
# removed device
|
||||
self.assertEqual(
|
||||
set([1]),
|
||||
self.agent._get_devices_locally_modified(new_ts, old_ts))
|
||||
|
||||
def _test_scan_devices(self, previous, updated,
|
||||
fake_current, expected, sync):
|
||||
fake_current, expected, sync,
|
||||
fake_ts_current=None):
|
||||
self.agent.br_mgr = mock.Mock()
|
||||
self.agent.br_mgr.get_tap_devices.return_value = fake_current
|
||||
|
||||
self.agent.br_mgr.get_devices_modified_timestamps.return_value = (
|
||||
fake_ts_current or {})
|
||||
self.agent.updated_devices = updated
|
||||
results = self.agent.scan_devices(previous, sync)
|
||||
self.assertEqual(expected, results)
|
||||
|
@ -224,28 +235,49 @@ class TestLinuxBridgeAgent(base.BaseTestCase):
|
|||
previous = {'current': set([1, 2]),
|
||||
'updated': set(),
|
||||
'added': set(),
|
||||
'removed': set()}
|
||||
'removed': set(),
|
||||
'timestamps': {}}
|
||||
fake_current = set([1, 2])
|
||||
updated = set()
|
||||
expected = {'current': set([1, 2]),
|
||||
'updated': set(),
|
||||
'added': set(),
|
||||
'removed': set()}
|
||||
'removed': set(),
|
||||
'timestamps': {}}
|
||||
|
||||
self._test_scan_devices(previous, updated, fake_current, expected,
|
||||
sync=False)
|
||||
|
||||
def test_scan_devices_timestamp_triggers_updated(self):
|
||||
previous = {'current': set([1, 2]),
|
||||
'updated': set(),
|
||||
'added': set(),
|
||||
'removed': set(),
|
||||
'timestamps': {2: 600}}
|
||||
fake_current = set([1, 2])
|
||||
updated = set()
|
||||
expected = {'current': set([1, 2]),
|
||||
'updated': set([2]),
|
||||
'added': set(),
|
||||
'removed': set(),
|
||||
'timestamps': {2: 1000}}
|
||||
|
||||
self._test_scan_devices(previous, updated, fake_current, expected,
|
||||
sync=False, fake_ts_current={2: 1000})
|
||||
|
||||
def test_scan_devices_added_removed(self):
|
||||
previous = {'current': set([1, 2]),
|
||||
'updated': set(),
|
||||
'added': set(),
|
||||
'removed': set()}
|
||||
'removed': set(),
|
||||
'timestamps': {}}
|
||||
fake_current = set([2, 3])
|
||||
updated = set()
|
||||
expected = {'current': set([2, 3]),
|
||||
'updated': set(),
|
||||
'added': set([3]),
|
||||
'removed': set([1])}
|
||||
'removed': set([1]),
|
||||
'timestamps': {}}
|
||||
|
||||
self._test_scan_devices(previous, updated, fake_current, expected,
|
||||
sync=False)
|
||||
|
@ -254,13 +286,15 @@ class TestLinuxBridgeAgent(base.BaseTestCase):
|
|||
previous = {'current': set([2, 3]),
|
||||
'updated': set(),
|
||||
'added': set(),
|
||||
'removed': set([1])}
|
||||
'removed': set([1]),
|
||||
'timestamps': {}}
|
||||
fake_current = set([2, 3])
|
||||
updated = set()
|
||||
expected = {'current': set([2, 3]),
|
||||
'updated': set(),
|
||||
'added': set([2, 3]),
|
||||
'removed': set([1])}
|
||||
'removed': set([1]),
|
||||
'timestamps': {}}
|
||||
|
||||
self._test_scan_devices(previous, updated, fake_current, expected,
|
||||
sync=True)
|
||||
|
@ -269,7 +303,8 @@ class TestLinuxBridgeAgent(base.BaseTestCase):
|
|||
previous = {'current': set([2, 3]),
|
||||
'updated': set(),
|
||||
'added': set(),
|
||||
'removed': set([1])}
|
||||
'removed': set([1]),
|
||||
'timestamps': {}}
|
||||
# Device 2 disappeared.
|
||||
fake_current = set([3])
|
||||
updated = set()
|
||||
|
@ -277,7 +312,8 @@ class TestLinuxBridgeAgent(base.BaseTestCase):
|
|||
expected = {'current': set([3]),
|
||||
'updated': set(),
|
||||
'added': set([3]),
|
||||
'removed': set([1, 2])}
|
||||
'removed': set([1, 2]),
|
||||
'timestamps': {}}
|
||||
|
||||
self._test_scan_devices(previous, updated, fake_current, expected,
|
||||
sync=True)
|
||||
|
@ -286,13 +322,15 @@ class TestLinuxBridgeAgent(base.BaseTestCase):
|
|||
previous = {'current': set([1, 2]),
|
||||
'updated': set(),
|
||||
'added': set(),
|
||||
'removed': set()}
|
||||
'removed': set(),
|
||||
'timestamps': {}}
|
||||
fake_current = set([1, 2])
|
||||
updated = set([1])
|
||||
expected = {'current': set([1, 2]),
|
||||
'updated': set([1]),
|
||||
'added': set(),
|
||||
'removed': set()}
|
||||
'removed': set(),
|
||||
'timestamps': {}}
|
||||
|
||||
self._test_scan_devices(previous, updated, fake_current, expected,
|
||||
sync=False)
|
||||
|
@ -301,13 +339,15 @@ class TestLinuxBridgeAgent(base.BaseTestCase):
|
|||
previous = {'current': set([1, 2]),
|
||||
'updated': set(),
|
||||
'added': set(),
|
||||
'removed': set()}
|
||||
'removed': set(),
|
||||
'timestamps': {}}
|
||||
fake_current = set([1, 2])
|
||||
updated = set([3])
|
||||
expected = {'current': set([1, 2]),
|
||||
'updated': set(),
|
||||
'added': set(),
|
||||
'removed': set()}
|
||||
'removed': set(),
|
||||
'timestamps': {}}
|
||||
|
||||
self._test_scan_devices(previous, updated, fake_current, expected,
|
||||
sync=False)
|
||||
|
@ -316,13 +356,15 @@ class TestLinuxBridgeAgent(base.BaseTestCase):
|
|||
previous = {'current': set([1, 2]),
|
||||
'updated': set([1]),
|
||||
'added': set(),
|
||||
'removed': set()}
|
||||
'removed': set(),
|
||||
'timestamps': {}}
|
||||
fake_current = set([1, 2])
|
||||
updated = set([2])
|
||||
expected = {'current': set([1, 2]),
|
||||
'updated': set([1, 2]),
|
||||
'added': set([1, 2]),
|
||||
'removed': set()}
|
||||
'removed': set(),
|
||||
'timestamps': {}}
|
||||
|
||||
self._test_scan_devices(previous, updated, fake_current, expected,
|
||||
sync=True)
|
||||
|
@ -335,7 +377,8 @@ class TestLinuxBridgeAgent(base.BaseTestCase):
|
|||
expected = {'current': set([1, 2]),
|
||||
'updated': set(),
|
||||
'added': set([1, 2]),
|
||||
'removed': set()}
|
||||
'removed': set(),
|
||||
'timestamps': {}}
|
||||
with mock.patch.object(arp_protect,
|
||||
'delete_unreferenced_arp_protection') as de_arp:
|
||||
self._test_scan_devices(previous, updated, fake_current, expected,
|
||||
|
|
Loading…
Reference in New Issue