class TestEtcdWatcher(unittest.TestCase):
    def setUp(self):
        super(TestEtcdWatcher, self).setUp()
        self.reconnect_patch = patch(
            "networking_calico.etcdutils.EtcdWatcher.reconnect"
        )
        self.m_reconnect = self.reconnect_patch.start()
        self.watcher = EtcdWatcher(["foobar:4001"], "/calico")
        self.m_client = Mock()
        self.watcher.client = self.m_client
        self.m_dispatcher = Mock(spec=PathDispatcher)
        self.watcher.dispatcher = self.m_dispatcher

    @patch("time.sleep", autospec=True)
    def test_mainline(self, m_sleep):
        m_snap_response = Mock()
        m_snap_response.etcd_index = 1
        m_poll_response = Mock()
        m_poll_response.modifiedIndex = 2
        responses = [
            m_snap_response, m_poll_response, ResyncRequired(),  # Loop 1
            EtcdException(),  # Loop 2
            ExpectedException(),  # Loop 3, Break out of loop.
        ]
        self.m_client.read.side_effect = iter(responses)
        with patch.object(self.watcher, "_on_pre_resync",
                          autospec=True) as m_pre_r:
            with patch.object(self.watcher, "_on_snapshot_loaded",
                              autospec=True) as m_snap_load:
                self.assertRaises(ExpectedException, self.watcher.loop)
        # _on_pre_resync() called once per loop.
        self.assertEqual(m_pre_r.mock_calls, [call(), call(), call()])
        # The snapshot only loads successfully the first time.
        self.assertEqual(m_snap_load.mock_calls, [call(m_snap_response)])
        self.assertEqual(self.m_dispatcher.handle_event.mock_calls,
                         [call(m_poll_response)])
        # Should sleep after exception.
        m_sleep.assert_called_once_with(1)

    def test_loop_stopped(self):
        self.watcher._stopped = True

        with patch.object(self.watcher, "_on_pre_resync",
                          autospec=True) as m_pre_r:
            self.watcher.loop()
        self.assertFalse(m_pre_r.called)

    def test_register(self):
        self.watcher.register_path("key", foo="bar")
        self.assertEqual(self.m_dispatcher.register.mock_calls,
                         [call("key", foo="bar")])

    @patch("time.sleep", autospec=True)
    def test_wait_for_ready(self, m_sleep):
        m_resp_1 = Mock()
        m_resp_1.value = "false"
        m_resp_2 = Mock()
        m_resp_2.value = "true"
        responses = [
            etcd.EtcdException(),
            etcd.EtcdKeyNotFound(),
            m_resp_1,
            m_resp_2,
        ]
        self.m_client.read.side_effect = iter(responses)
        self.watcher.wait_for_ready(1)
        self.assertEqual(m_sleep.mock_calls, [call(1)] * 3)

    def test_load_initial_dump(self):
        m_response = Mock(spec=etcd.EtcdResult)
        m_response.etcd_index = 10000
        self.m_client.read.side_effect = [
            etcd.EtcdKeyNotFound(),
            m_response
        ]
        with patch("time.sleep") as m_sleep:
            self.assertEqual(self.watcher.load_initial_dump(), m_response)

        m_sleep.assert_called_once_with(1)
        self.m_client.read.assert_has_calls([
            call("/calico", recursive=True),
            call("/calico", recursive=True),
        ])
        self.assertEqual(self.watcher.next_etcd_index, 10001)

    def test_load_initial_dump_stopped(self):
        self.watcher.stop()
        self.m_client.read.side_effect = etcd.EtcdKeyNotFound()
        self.assertRaises(etcd.EtcdKeyNotFound, self.watcher.load_initial_dump)

    def test_resync_set(self):
        self.watcher.next_etcd_index = 1
        self.watcher.resync_after_current_poll = True
        self.assertRaises(ResyncRequired, self.watcher.wait_for_etcd_event)
        self.assertFalse(self.watcher.resync_after_current_poll)

    @patch("time.sleep", autospec=True)
    def test_wait_for_etcd_event_conn_failed(self, m_sleep):
        self.watcher.next_etcd_index = 1
        m_resp = Mock()
        m_resp.modifiedIndex = 123
        read_timeout = etcd.EtcdConnectionFailed()
        read_timeout.cause = ReadTimeoutError(Mock(), "", "")
        other_error = etcd.EtcdConnectionFailed()
        other_error.cause = ExpectedException()
        responses = [
            read_timeout,
            other_error,
            m_resp,
        ]
        self.m_client.read.side_effect = iter(responses)
        event = self.watcher.wait_for_etcd_event()
        self.assertEqual(event, m_resp)
        self.assertEqual(m_sleep.mock_calls, [call(1)])

    def test_wait_for_etcd_event_cluster_id_changed(self):
        self.watcher.next_etcd_index = 1
        responses = [
            etcd.EtcdClusterIdChanged(),
        ]
        self.m_client.read.side_effect = iter(responses)
        self.assertRaises(ResyncRequired, self.watcher.wait_for_etcd_event)

    def test_wait_for_etcd_event_index_cleared(self):
        self.watcher.next_etcd_index = 1
        responses = [
            etcd.EtcdEventIndexCleared(),
        ]
        self.m_client.read.side_effect = iter(responses)
        self.assertRaises(ResyncRequired, self.watcher.wait_for_etcd_event)

    @patch("time.sleep", autospec=True)
    def test_wait_for_etcd_event_unexpected_error(self, m_sleep):
        self.watcher.next_etcd_index = 1
        responses = [
            etcd.EtcdException(),
        ]
        self.m_client.read.side_effect = iter(responses)
        self.assertRaises(ResyncRequired, self.watcher.wait_for_etcd_event)
        self.assertEqual(m_sleep.mock_calls, [call(1)])

    def test_coverage(self):
        # These methods are no-ops.
        self.watcher._on_pre_resync()
        self.watcher._on_snapshot_loaded(Mock())

    def tearDown(self):
        self.reconnect_patch.stop()
        super(TestEtcdWatcher, self).tearDown()
class TestEtcdWatcher(unittest.TestCase):
    def setUp(self):
        super(TestEtcdWatcher, self).setUp()
        self.reconnect_patch = patch(
            "networking_calico.etcdutils.EtcdWatcher.reconnect")
        self.m_reconnect = self.reconnect_patch.start()
        self.watcher = EtcdWatcher(["foobar:4001"], "/calico")
        self.m_client = Mock()
        self.watcher.client = self.m_client
        self.m_dispatcher = Mock(spec=PathDispatcher)
        self.watcher.dispatcher = self.m_dispatcher

    @patch("time.sleep", autospec=True)
    def test_mainline(self, m_sleep):
        m_snap_response = Mock()
        m_snap_response.etcd_index = 1
        m_poll_response = Mock()
        m_poll_response.modifiedIndex = 2
        responses = [
            m_snap_response,
            m_poll_response,
            ResyncRequired(),  # Loop 1
            EtcdException(),  # Loop 2
            ExpectedException(),  # Loop 3, Break out of loop.
        ]
        self.m_client.read.side_effect = iter(responses)
        with patch.object(self.watcher, "_on_pre_resync",
                          autospec=True) as m_pre_r:
            with patch.object(self.watcher,
                              "_on_snapshot_loaded",
                              autospec=True) as m_snap_load:
                self.assertRaises(ExpectedException, self.watcher.loop)
        # _on_pre_resync() called once per loop.
        self.assertEqual(m_pre_r.mock_calls, [call(), call(), call()])
        # The snapshot only loads successfully the first time.
        self.assertEqual(m_snap_load.mock_calls, [call(m_snap_response)])
        self.assertEqual(self.m_dispatcher.handle_event.mock_calls,
                         [call(m_poll_response)])
        # Should sleep after exception.
        m_sleep.assert_called_once_with(1)

    def test_loop_stopped(self):
        self.watcher._stopped = True

        with patch.object(self.watcher, "_on_pre_resync",
                          autospec=True) as m_pre_r:
            self.watcher.loop()
        self.assertFalse(m_pre_r.called)

    def test_register(self):
        self.watcher.register_path("key", foo="bar")
        self.assertEqual(self.m_dispatcher.register.mock_calls,
                         [call("key", foo="bar")])

    @patch("time.sleep", autospec=True)
    def test_wait_for_ready(self, m_sleep):
        m_resp_1 = Mock()
        m_resp_1.value = "false"
        m_resp_2 = Mock()
        m_resp_2.value = "true"
        responses = [
            etcd.EtcdException(),
            etcd.EtcdKeyNotFound(),
            m_resp_1,
            m_resp_2,
        ]
        self.m_client.read.side_effect = iter(responses)
        self.watcher.wait_for_ready(1)
        self.assertEqual(m_sleep.mock_calls, [call(1)] * 3)

    def test_load_initial_dump(self):
        m_response = Mock(spec=etcd.EtcdResult)
        m_response.etcd_index = 10000
        self.m_client.read.side_effect = [etcd.EtcdKeyNotFound(), m_response]
        with patch("time.sleep") as m_sleep:
            self.assertEqual(self.watcher.load_initial_dump(), m_response)

        m_sleep.assert_called_once_with(1)
        self.m_client.read.assert_has_calls([
            call("/calico", recursive=True),
            call("/calico", recursive=True),
        ])
        self.assertEqual(self.watcher.next_etcd_index, 10001)

    def test_load_initial_dump_stopped(self):
        self.watcher.stop()
        self.m_client.read.side_effect = etcd.EtcdKeyNotFound()
        self.assertRaises(etcd.EtcdKeyNotFound, self.watcher.load_initial_dump)

    def test_resync_set(self):
        self.watcher.next_etcd_index = 1
        self.watcher.resync_after_current_poll = True
        self.assertRaises(ResyncRequired, self.watcher.wait_for_etcd_event)
        self.assertFalse(self.watcher.resync_after_current_poll)

    @patch("time.sleep", autospec=True)
    def test_wait_for_etcd_event_conn_failed(self, m_sleep):
        self.watcher.next_etcd_index = 1
        m_resp = Mock()
        m_resp.modifiedIndex = 123
        read_timeout = etcd.EtcdConnectionFailed()
        read_timeout.cause = ReadTimeoutError(Mock(), "", "")
        other_error = etcd.EtcdConnectionFailed()
        other_error.cause = ExpectedException()
        responses = [
            read_timeout,
            other_error,
            m_resp,
        ]
        self.m_client.read.side_effect = iter(responses)
        event = self.watcher.wait_for_etcd_event()
        self.assertEqual(event, m_resp)
        self.assertEqual(m_sleep.mock_calls, [call(1)])

    def test_wait_for_etcd_event_cluster_id_changed(self):
        self.watcher.next_etcd_index = 1
        responses = [
            etcd.EtcdClusterIdChanged(),
        ]
        self.m_client.read.side_effect = iter(responses)
        self.assertRaises(ResyncRequired, self.watcher.wait_for_etcd_event)

    def test_wait_for_etcd_event_index_cleared(self):
        self.watcher.next_etcd_index = 1
        responses = [
            etcd.EtcdEventIndexCleared(),
        ]
        self.m_client.read.side_effect = iter(responses)
        self.assertRaises(ResyncRequired, self.watcher.wait_for_etcd_event)

    @patch("time.sleep", autospec=True)
    def test_wait_for_etcd_event_unexpected_error(self, m_sleep):
        self.watcher.next_etcd_index = 1
        responses = [
            etcd.EtcdException(),
        ]
        self.m_client.read.side_effect = iter(responses)
        self.assertRaises(ResyncRequired, self.watcher.wait_for_etcd_event)
        self.assertEqual(m_sleep.mock_calls, [call(1)])

    def test_coverage(self):
        # These methods are no-ops.
        self.watcher._on_pre_resync()
        self.watcher._on_snapshot_loaded(Mock())

    def tearDown(self):
        self.reconnect_patch.stop()
        super(TestEtcdWatcher, self).tearDown()
Beispiel #3
0
class TestEtcdWatcher(unittest.TestCase):
    def setUp(self):
        super(TestEtcdWatcher, self).setUp()
        self.m_client = Mock()
        etcdv3._client = self.m_client
        self.watcher = EtcdWatcher("/calico")
        self.m_dispatcher = Mock(spec=PathDispatcher)
        self.watcher.dispatcher = self.m_dispatcher

    def test_mainline(self):
        # Set up 3 iterations through the watcher's main loop.
        #
        # 1. No data for snapshot.  Watch throws exception.
        #
        # 2. Data for snapshot.  Watch throws exception.
        #
        # 3. Throw ExpectedException(), to exit.
        status = {'header': {'cluster_id': '1234', 'revision': '10'}}
        self.m_client.status.side_effect = iter([
            # Iteration 1.
            status,
            # Iteration 2.
            status,
            # Iteration 3.
            status,
        ])
        rsp1 = Response(action='set',
                        key='foo',
                        value='bar',
                        mod_revision='12')
        self.m_client.get.side_effect = iter([[], [_rsp_to_tuple(rsp1)],
                                              ExpectedException()])
        self.m_client.watch_prefix.side_effect = etcdv3.KeyNotFound()

        with patch.object(self.watcher, "_pre_snapshot_hook",
                          autospec=True) as m_pre:
            m_pre.return_value = None
            with patch.object(self.watcher,
                              "_post_snapshot_hook",
                              autospec=True) as m_post:
                self.assertRaises(ExpectedException, self.watcher.start)

        # _pre_snapshot_hook() called 3 times.
        self.assertEqual(m_pre.mock_calls, [call(), call(), call()])

        # _post_snapshot_hook() called twice.
        self.assertEqual(m_post.mock_calls, [call(None), call(None)])

        # watch_prefix called twice.
        self.assertEqual(self.m_client.watch_prefix.mock_calls, [
            call('/calico', start_revision='11'),
            call('/calico', start_revision='11')
        ])

        # Snapshot event dispatched once.
        self.assertEqual(self.m_dispatcher.handle_event.mock_calls,
                         [call(rsp1)])

    def test_register(self):
        self.watcher.register_path("key", foo="bar")
        self.assertEqual(self.m_dispatcher.register.mock_calls,
                         [call("key", foo="bar")])