9d9fc01dd3
This change adds the ability to monitor the local ovsdb for interface changes so that the l2 agent can avoid unnecessary polling. Minimal changes are made to the agent so the risk of breakage should be low. Future efforts to make the agent entirely event-based may be able to use OvsdbMonitor as a starting point. By default polling minimization is not done, and can only be enabled by setting 'minimize_polling = True' in the ovs section of the l2 agent's config file. Closes-Bug: #1177973 Change-Id: I26c035b48a74df2148696869c5a9affae5ab3d27
151 lines
4.9 KiB
Python
151 lines
4.9 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2013 Red Hat, Inc.
|
|
#
|
|
# 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.
|
|
|
|
"""
|
|
Tests in this module will be skipped unless:
|
|
|
|
- ovsdb-client is installed
|
|
|
|
- ovsdb-client can be invoked via password-less sudo
|
|
|
|
- OS_SUDO_TESTING is set to '1' or 'True' in the test execution
|
|
environment
|
|
|
|
|
|
The jenkins gate does not allow direct sudo invocation during test
|
|
runs, but configuring OS_SUDO_TESTING ensures that developers are
|
|
still able to execute tests that require the capability.
|
|
"""
|
|
|
|
import os
|
|
import random
|
|
|
|
import eventlet
|
|
|
|
from neutron.agent.linux import ovs_lib
|
|
from neutron.agent.linux import ovsdb_monitor
|
|
from neutron.agent.linux import utils
|
|
from neutron.tests import base
|
|
|
|
|
|
def get_rand_name(name='test'):
|
|
return name + str(random.randint(1, 0x7fffffff))
|
|
|
|
|
|
def create_ovs_resource(name_prefix, creation_func):
|
|
"""Create a new ovs resource that does not already exist.
|
|
|
|
:param name_prefix: The prefix for a randomly generated name
|
|
:param creation_func: A function taking the name of the resource
|
|
to be created. An error is assumed to indicate a name
|
|
collision.
|
|
"""
|
|
while True:
|
|
name = get_rand_name(name_prefix)
|
|
try:
|
|
return creation_func(name)
|
|
except RuntimeError:
|
|
continue
|
|
break
|
|
|
|
|
|
class BaseMonitorTest(base.BaseTestCase):
|
|
|
|
def setUp(self):
|
|
super(BaseMonitorTest, self).setUp()
|
|
|
|
self._check_test_requirements()
|
|
|
|
self.root_helper = 'sudo'
|
|
self.ovs = ovs_lib.BaseOVS(self.root_helper)
|
|
self.bridge = create_ovs_resource('test-br-', self.ovs.add_bridge)
|
|
|
|
def cleanup_bridge():
|
|
self.bridge.destroy()
|
|
self.addCleanup(cleanup_bridge)
|
|
|
|
def _check_command(self, cmd, error_text, skip_msg):
|
|
try:
|
|
utils.execute(cmd)
|
|
except RuntimeError as e:
|
|
if error_text in str(e):
|
|
self.skipTest(skip_msg)
|
|
raise
|
|
|
|
def _check_test_requirements(self):
|
|
if os.environ.get('OS_SUDO_TESTING') not in base.TRUE_STRING:
|
|
self.skipTest('testing with sudo is not enabled')
|
|
self._check_command(['which', 'ovsdb-client'],
|
|
'Exit code: 1',
|
|
'ovsdb-client is not installed')
|
|
self._check_command(['sudo', '-n', 'ovsdb-client', 'list-dbs'],
|
|
'Exit code: 1',
|
|
'password-less sudo not granted for ovsdb-client')
|
|
|
|
|
|
class TestOvsdbMonitor(BaseMonitorTest):
|
|
|
|
def setUp(self):
|
|
super(TestOvsdbMonitor, self).setUp()
|
|
|
|
self.monitor = ovsdb_monitor.OvsdbMonitor('Bridge',
|
|
root_helper=self.root_helper)
|
|
self.addCleanup(self.monitor.stop)
|
|
self.monitor.start()
|
|
|
|
def collect_initial_output(self):
|
|
while True:
|
|
output = list(self.monitor.iter_stdout())
|
|
if output:
|
|
return output[0]
|
|
eventlet.sleep(0.01)
|
|
|
|
def test_killed_monitor_respawns(self):
|
|
with self.assert_max_execution_time():
|
|
self.monitor.respawn_interval = 0
|
|
old_pid = self.monitor._process.pid
|
|
output1 = self.collect_initial_output()
|
|
pid = self.monitor._get_pid_to_kill()
|
|
self.monitor._reset_queues()
|
|
self.monitor._kill_process(pid)
|
|
while (self.monitor._process.pid == old_pid):
|
|
eventlet.sleep(0.01)
|
|
output2 = self.collect_initial_output()
|
|
# Initial output should appear twice
|
|
self.assertEqual(output1, output2)
|
|
|
|
|
|
class TestSimpleInterfaceMonitor(BaseMonitorTest):
|
|
|
|
def setUp(self):
|
|
super(TestSimpleInterfaceMonitor, self).setUp()
|
|
|
|
self.monitor = ovsdb_monitor.SimpleInterfaceMonitor(
|
|
root_helper=self.root_helper)
|
|
self.addCleanup(self.monitor.stop)
|
|
self.monitor.start(block=True)
|
|
|
|
def test_has_updates(self):
|
|
self.assertTrue(self.monitor.has_updates,
|
|
'Initial call should always be true')
|
|
self.assertFalse(self.monitor.has_updates,
|
|
'has_updates without port addition should be False')
|
|
create_ovs_resource('test-port-', self.bridge.add_port)
|
|
with self.assert_max_execution_time():
|
|
# has_updates after port addition should become True
|
|
while not self.monitor.has_updates:
|
|
eventlet.sleep(0.01)
|