kuryr-kubernetes/kuryr_kubernetes/tests/unit/test_watcher.py

348 lines
12 KiB
Python

# Copyright (c) 2016 Mirantis, Inc.
# All Rights Reserved.
#
# 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 eventlet import greenlet
from unittest import mock
from kuryr_kubernetes.tests import base as test_base
from kuryr_kubernetes.tests.unit import kuryr_fixtures as kuryr_fixtures
from kuryr_kubernetes import watcher
from requests import exceptions
class TestWatcher(test_base.TestCase):
def setUp(self):
super(TestWatcher, self).setUp()
mock_client = self.useFixture(kuryr_fixtures.MockK8sClient())
self.client = mock_client.client
@mock.patch.object(watcher.Watcher, '_start_watch')
def test_add(self, m_start_watch):
paths = ['/test%s' % i for i in range(3)]
m_handler = mock.Mock()
watcher_obj = watcher.Watcher(m_handler)
for path in paths:
watcher_obj.add(path)
self.assertEqual(set(paths), watcher_obj._resources)
m_start_watch.assert_not_called()
@mock.patch.object(watcher.Watcher, '_start_watch')
def test_add_running(self, m_start_watch):
paths = ['/test%s' % i for i in range(3)]
m_handler = mock.Mock()
watcher_obj = watcher.Watcher(m_handler)
watcher_obj._running = True
for path in paths:
watcher_obj.add(path)
self.assertEqual(set(paths), watcher_obj._resources)
m_start_watch.assert_has_calls([mock.call(path) for path in paths],
any_order=True)
@mock.patch.object(watcher.Watcher, '_start_watch')
def test_add_watching(self, m_start_watch):
paths = ['/test%s' % i for i in range(3)]
m_handler = mock.Mock()
watcher_obj = watcher.Watcher(m_handler)
watcher_obj._running = True
m_watching = watcher_obj._watching = mock.MagicMock()
m_watching.__contains__.return_value = True
for path in paths:
watcher_obj.add(path)
self.assertEqual(set(paths), watcher_obj._resources)
m_start_watch.assert_not_called()
@mock.patch.object(watcher.Watcher, '_stop_watch')
def test_remove(self, m_stop_watch):
path = '/test'
m_handler = mock.Mock()
watcher_obj = watcher.Watcher(m_handler)
watcher_obj._resources.add(path)
watcher_obj.remove(path)
self.assertEqual(set(), watcher_obj._resources)
m_stop_watch.assert_not_called()
@mock.patch.object(watcher.Watcher, '_stop_watch')
def test_remove_watching(self, m_stop_watch):
path = '/test'
m_handler = mock.Mock()
watcher_obj = watcher.Watcher(m_handler)
watcher_obj._resources.add(path)
m_watching = watcher_obj._watching = mock.MagicMock()
m_watching.__contains__.return_value = True
watcher_obj.remove(path)
self.assertEqual(set(), watcher_obj._resources)
m_stop_watch.assert_called_once_with(path)
@mock.patch.object(watcher.Watcher, '_start_watch')
def test_start(self, m_start_watch):
paths = ['/test%s' % i for i in range(3)]
m_handler = mock.Mock()
watcher_obj = watcher.Watcher(m_handler)
watcher_obj._resources.update(paths)
watcher_obj.start()
self.assertTrue(watcher_obj._running)
m_start_watch.assert_has_calls([mock.call(path) for path in paths],
any_order=True)
@mock.patch.object(watcher.Watcher, '_start_watch')
def test_start_already_watching(self, m_start_watch):
paths = ['/test%s' % i for i in range(3)]
m_handler = mock.Mock()
watcher_obj = watcher.Watcher(m_handler)
watcher_obj._resources.update(paths)
m_watching = watcher_obj._watching = mock.MagicMock()
m_watching.__iter__.return_value = paths
watcher_obj.start()
self.assertTrue(watcher_obj._running)
m_start_watch.assert_not_called()
@mock.patch.object(watcher.Watcher, '_stop_watch')
def test_stop(self, m_stop_watch):
paths = ['/test%s' % i for i in range(3)]
m_handler = mock.Mock()
watcher_obj = watcher.Watcher(m_handler)
watcher_obj._resources.update(paths)
watcher_obj.stop()
self.assertFalse(watcher_obj._running)
m_stop_watch.assert_not_called()
@mock.patch.object(watcher.Watcher, '_stop_watch')
def test_stop_watching(self, m_stop_watch):
paths = ['/test%s' % i for i in range(3)]
m_handler = mock.Mock()
watcher_obj = watcher.Watcher(m_handler)
watcher_obj._resources.update(paths)
m_watching = watcher_obj._watching = mock.MagicMock()
m_watching.__iter__.return_value = paths
watcher_obj.stop()
self.assertFalse(watcher_obj._running)
m_stop_watch.assert_has_calls([mock.call(path) for path in paths],
any_order=True)
@mock.patch.object(watcher.Watcher, '_watch')
def test_start_watch(self, m_watch):
path = '/test'
m_handler = mock.Mock()
watcher_obj = watcher.Watcher(m_handler)
watcher_obj._start_watch(path)
m_watch.assert_called_once_with(path)
self.assertTrue(watcher_obj._idle.get(path))
self.assertIn(path, watcher_obj._watching)
def test_start_watch_threaded(self):
path = '/test'
m_tg = mock.Mock()
m_tg.add_thread.return_value = mock.sentinel.watch_thread
m_handler = mock.Mock()
watcher_obj = watcher.Watcher(m_handler, m_tg)
watcher_obj._start_watch(path)
m_tg.add_thread.assert_called_once_with(watcher_obj._watch, path)
self.assertTrue(watcher_obj._idle.get(path))
self.assertEqual(mock.sentinel.watch_thread,
watcher_obj._watching.get(path))
def test_stop_watch_threaded(self):
path = '/test'
m_tg = mock.Mock()
m_th = mock.Mock()
m_tt = mock.Mock()
m_handler = mock.Mock()
watcher_obj = watcher.Watcher(m_handler, m_tg)
watcher_obj._idle[path] = True
watcher_obj._watching[path] = m_th
watcher_obj._timers[path] = m_tt
watcher_obj._stop_watch(path)
m_tt.stop.assert_called()
m_th.stop.assert_called()
def test_stop_watch_idle(self):
path = '/test'
m_tg = mock.Mock()
m_th = mock.Mock()
m_handler = mock.Mock()
watcher_obj = watcher.Watcher(m_handler, m_tg)
watcher_obj._idle[path] = False
watcher_obj._watching[path] = m_th
watcher_obj._stop_watch(path)
m_th.kill.assert_not_called()
def _test_watch_mock_events(self, watcher_obj, events):
def client_watch(client_path):
for e in events:
self.assertTrue(watcher_obj._idle[client_path])
yield e
self.assertTrue(watcher_obj._idle[client_path])
self.client.watch.side_effect = client_watch
@staticmethod
def _test_watch_create_watcher(path, handler, timeout=0):
watcher_obj = watcher.Watcher(handler, timeout=timeout)
watcher_obj._running = True
watcher_obj._resources.add(path)
watcher_obj._idle[path] = True
watcher_obj._watching[path] = None
return watcher_obj
@mock.patch('sys.exit')
def test_watch(self, m_sys_exit):
path = '/test'
events = [{'e': i} for i in range(3)]
def handler(event):
self.assertFalse(watcher_obj._idle[path])
m_handler = mock.Mock()
m_handler.side_effect = handler
watcher_obj = self._test_watch_create_watcher(path, m_handler)
self._test_watch_mock_events(watcher_obj, events)
watcher_obj._watch(path)
self.assertEqual(0, watcher_obj._timeout)
m_handler.assert_has_calls([mock.call(e) for e in events])
# After all events have been "handled", since there is only
# one handler, we'll gracefully exit
m_sys_exit.assert_called_once_with(1)
@mock.patch('sys.exit')
def test_watch_stopped(self, m_sys_exit):
path = '/test'
events = [{'e': i} for i in range(3)]
def handler(event):
self.assertFalse(watcher_obj._idle[path])
watcher_obj._running = False
m_handler = mock.Mock()
m_handler.side_effect = handler
watcher_obj = self._test_watch_create_watcher(path, m_handler)
self._test_watch_mock_events(watcher_obj, events)
watcher_obj._watch(path)
m_handler.assert_called_once_with(events[0])
self.assertNotIn(path, watcher_obj._idle)
self.assertNotIn(path, watcher_obj._watching)
m_sys_exit.assert_called_once_with(1)
@mock.patch('sys.exit')
def test_watch_removed(self, m_sys_exit):
path = '/test'
events = [{'e': i} for i in range(3)]
def handler(event):
self.assertFalse(watcher_obj._idle[path])
watcher_obj._resources.remove(path)
m_handler = mock.Mock()
m_handler.side_effect = handler
watcher_obj = self._test_watch_create_watcher(path, m_handler)
self._test_watch_mock_events(watcher_obj, events)
watcher_obj._watch(path)
m_handler.assert_called_once_with(events[0])
self.assertNotIn(path, watcher_obj._idle)
self.assertNotIn(path, watcher_obj._watching)
m_sys_exit.assert_called_once_with(1)
@mock.patch('sys.exit')
def test_watch_interrupted(self, m_sys_exit):
path = '/test'
events = [{'e': i} for i in range(3)]
def handler(event):
self.assertFalse(watcher_obj._idle[path])
raise greenlet.GreenletExit()
m_handler = mock.Mock()
m_handler.side_effect = handler
watcher_obj = self._test_watch_create_watcher(path, m_handler)
self._test_watch_mock_events(watcher_obj, events)
self.assertRaises(greenlet.GreenletExit, watcher_obj._watch, path)
m_handler.assert_called_once_with(events[0])
self.assertNotIn(path, watcher_obj._idle)
self.assertNotIn(path, watcher_obj._watching)
m_sys_exit.assert_called_once_with(1)
@mock.patch('sys.exit')
def test_watch_client_request_failed(self, m_sys_exit):
path = '/test'
m_handler = mock.Mock()
watcher_obj = self._test_watch_create_watcher(path, m_handler)
watcher_obj._watch(path)
self.client.watch.side_effect = exceptions.ChunkedEncodingError(
"Connection Broken")
self.client.watch.assert_called_once()
self.assertFalse(watcher_obj._alive)
m_sys_exit.assert_called_once_with(1)
@mock.patch('sys.exit')
def test_watch_retry(self, m_sys_exit):
path = '/test'
events = [{'e': i} for i in range(3)]
side_effects = [exceptions.ChunkedEncodingError("Connection Broken")]
side_effects.extend(None for _ in events)
m_handler = mock.Mock()
m_handler.side_effect = side_effects
watcher_obj = self._test_watch_create_watcher(path, m_handler, 10)
self._test_watch_mock_events(watcher_obj, events)
watcher_obj._watch(path)
m_handler.assert_has_calls([mock.call(e) for e in events])
m_sys_exit.assert_called_once_with(1)
def test_watch_restart(self):
tg = mock.Mock()
w = watcher.Watcher(lambda e: None, tg)
w.add('/test')
w.start()
tg.add_thread.assert_called_once_with(mock.ANY, '/test')
w.stop()
tg.add_thread = mock.Mock() # Reset mock.
w.start()
tg.add_thread.assert_called_once_with(mock.ANY, '/test')