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:
Kevin Benton 2016-03-09 23:47:35 -08:00 committed by Andreas Scheuring
parent 4b1d8dca9b
commit a86274e8eb
4 changed files with 145 additions and 18 deletions

View File

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

View File

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

View File

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

View File

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