diff --git a/neutron/agent/linux/bridge_lib.py b/neutron/agent/linux/bridge_lib.py index e8176510f8f..51f8cdfbdcb 100644 --- a/neutron/agent/linux/bridge_lib.py +++ b/neutron/agent/linux/bridge_lib.py @@ -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): diff --git a/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py b/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py index b0048eba9e8..e008c66e345 100644 --- a/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py @@ -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. diff --git a/neutron/tests/functional/agent/linux/test_bridge_lib.py b/neutron/tests/functional/agent/linux/test_bridge_lib.py new file mode 100644 index 00000000000..a1ff108d530 --- /dev/null +++ b/neutron/tests/functional/agent/linux/test_bridge_lib.py @@ -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) diff --git a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py index 2fbd2e61766..b16fb4cd96e 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py @@ -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,