Ejemplo n.º 1
0
class TestIptablesUpdater(BaseTestCase):

    def setUp(self):
        super(TestIptablesUpdater, self).setUp()
        self.stub = IptablesStub("filter")
        env_dict = {"FELIX_REFRESHINTERVAL": "0"}
        self.config = load_config("felix_default.cfg", env_dict=env_dict)
        self.ipt = IptablesUpdater("filter", self.config, 4)
        self.ipt._execute_iptables = self.stub.apply_iptables_restore
        self.check_output_patch = patch("gevent.subprocess.check_output",
                                        autospec=True)
        self.m_check_output = self.check_output_patch.start()
        self.m_check_output.side_effect = self.fake_check_output

    def fake_check_output(self, cmd, *args, **kwargs):
        _log.info("Stubbing out call to %s", cmd)
        if cmd == ["iptables-save", "--table", "filter"]:
            return self.stub.generate_iptables_save()
        elif cmd == ['iptables', '--wait', '--list', '--numeric',
                     '--table', 'filter']:
            return self.stub.generate_iptables_list()
        else:
            raise AssertionError("Unexpected call %r" % cmd)

    def tearDown(self):
        self.check_output_patch.stop()
        super(TestIptablesUpdater, self).tearDown()

    def test_rewrite_chains_stub(self):
        """
        Tests that referencing a chain causes it to get stubbed out.
        """
        self.ipt.rewrite_chains(
            {"foo": ["--append foo --jump bar"]},
            {"foo": set(["bar"])},
            async=True,
        )
        self.step_actor(self.ipt)
        self.assertEqual(self.stub.chains_contents,
            {"foo": ["--append foo --jump bar"],
             'bar': drop_rules("bar")})

    def test_rewrite_chains_cover(self):
        """
        Hits remaining code paths in rewrite chains.
        """
        cb = Mock()
        self.ipt.rewrite_chains(
            {"foo": ["--append foo --jump bar"]},
            {"foo": set(["bar"])},
            async=True,
            callback=cb,
        )
        self.step_actor(self.ipt)
        cb.assert_called_once_with(None)

    def test_delete_required_chain_stub(self):
        """
        Tests that deleting a required chain stubs it out instead.
        """
        # Exit the graceful restart period, during which we do not stub out
        # chains.
        self.ipt.cleanup(async=True)
        # Install a couple of chains.  foo depends on bar.
        self.ipt.rewrite_chains(
            {"foo": ["--append foo --jump bar"],
             "bar": ["--append bar --jump ACCEPT"]},
            {"foo": set(["bar"]),
             "bar": set()},
            async=True,
        )
        self.step_actor(self.ipt)
        # Both chains should be programmed as normal.
        self.assertEqual(self.stub.chains_contents,
            {"foo": ["--append foo --jump bar"],
             'bar': ["--append bar --jump ACCEPT"] })

        # Deleting bar should stub it out instead.
        self.ipt.delete_chains(["bar"], async=True)
        self.step_actor(self.ipt)
        self.assertEqual(self.stub.chains_contents,
            {"foo": ["--append foo --jump bar"],
             'bar': drop_rules("bar") })

    def test_cleanup_with_dependencies(self):
        # Set up the dataplane with some chains that the IptablesUpdater
        # doesn't know about and some that it will know about.
        self.stub.apply_iptables_restore("""
        *filter
        :INPUT DROP [10:505]
        :FORWARD DROP [0:0]
        :OUTPUT ACCEPT [40:1600]
        # These non-felix chains should be ignored
        :ignore-me -
        :ignore-me-too -
        # These are left-over felix chains.  Some depend on each other.  They
        # can only be cleaned up in the correct order.
        :felix-foo - [0:0]
        :felix-bar -
        :felix-foo -
        :felix-baz -
        :felix-biff -
        --append felix-foo --src 10.0.0.1/32 --jump felix-bar
        # baz depends on biff; cleanup needs to detect that.
        --append felix-baz --src 10.0.0.2/32 --jump felix-biff
        --append felix-biff --src 10.0.0.3/32 --jump DROP
        --append ignore-me --jump ignore-me-too
        --append ignore-me-too --jump DROP
        """.splitlines())

        # IptablesUpdater hears about some chains before the cleanup.  These
        # partially overlap with the ones that are already there.
        self.ipt._load_chain_names_from_iptables(async=True)
        self.ipt.rewrite_chains(
            {"felix-foo": ["--append felix-foo --jump felix-bar",
                           "--append felix-foo --jump felix-baz",
                           "--append felix-foo --jump felix-boff"],
             "felix-bar": ["--append felix-bar --jump ACCEPT"]},
            # felix-foo depends on:
            # * a new chain that's also being programmed
            # * a pre-existing chain that is present at start of day
            # * a new chain that isn't present at all.
            {"felix-foo": set(["felix-bar", "felix-baz", "felix-boff"]),
             "felix-bar": set()},
            async=True,
        )
        self.step_actor(self.ipt)

        # Dataplane should now have all the new chains in place, including
        # a stub for felix-boff.  However, the old chains should not have been
        # cleaned up.
        self.stub.assert_chain_contents({
            "INPUT": [],
            "FORWARD": [],
            "OUTPUT": [],
            "ignore-me": ["--append ignore-me --jump ignore-me-too"],
            "ignore-me-too": ["--append ignore-me-too --jump DROP"],
            "felix-foo": ["--append felix-foo --jump felix-bar",
                          "--append felix-foo --jump felix-baz",
                          "--append felix-foo --jump felix-boff"],
            "felix-bar": ["--append felix-bar --jump ACCEPT"],
            "felix-baz": ["--append felix-baz --src 10.0.0.2/32 "
                          "--jump felix-biff"],
            "felix-boff": drop_rules("felix-boff"),
            "felix-biff": ["--append felix-biff --src 10.0.0.3/32 --jump DROP"],
        })

        # Issue the cleanup.
        self.ipt.cleanup(async=True)
        self.step_actor(self.ipt)

        # Should now have stubbed-out chains for all the ones that are not
        # programmed.
        self.stub.assert_chain_contents({
            # Non felix chains ignored:
            "INPUT": [],
            "FORWARD": [],
            "OUTPUT": [],
            "ignore-me": ["--append ignore-me --jump ignore-me-too"],
            "ignore-me-too": ["--append ignore-me-too --jump DROP"],
            # Explicitly-programmed chains programmed.
            "felix-foo": ["--append felix-foo --jump felix-bar",
                          "--append felix-foo --jump felix-baz",
                          "--append felix-foo --jump felix-boff"],
            "felix-bar": ["--append felix-bar --jump ACCEPT"],
            # All required but unknown chains stubbed.
            "felix-baz": drop_rules("felix-baz"),
            "felix-boff": drop_rules("felix-boff"),
            # felix-biff deleted, even though it was referenced by felix-baz
            # before.
        })

    def test_delete_during_grace_period(self):
        """
        Test explicit deletion of a referenced chain during the grace period.

        The chain _should_ be stubbed out.
        """
        # Simulate a felix restart where the chains we're about to manipulate
        # already exist.
        self.stub.apply_iptables_restore("""
        *filter
        # These are left-over felix chains.  Some depend on each other.  They
        # can only be cleaned up in the correct order.
        :felix-foo - [0:0]
        :felix-bar -
        :felix-baz -
        --append felix-foo --src 10.0.0.1/32 --jump felix-bar
        --append felix-bar --src 10.0.0.2/32 --jump DROP
        --append felix-baz --src 10.0.0.3/32 --jump DROP
        """.splitlines())
        self.ipt._load_chain_names_from_iptables(async=True)

        # IptablesUpdater hears about all the chains before the cleanup.
        # Chains have dependencies.
        self.ipt.rewrite_chains(
            {"felix-foo": ["--append felix-foo --jump felix-bar"],
             "felix-bar": ["--append felix-bar --jump ACCEPT"],
             "felix-baz": ["--append felix-baz --jump ACCEPT"]},
            {"felix-foo": set(["felix-bar"]),
             "felix-bar": set(),
             "felix-baz": set()},
            async=True,
        )
        self.step_actor(self.ipt)

        # Dataplane should now have all the new chains in place.
        self.stub.assert_chain_contents({
            "felix-foo": ["--append felix-foo --jump felix-bar"],
            "felix-bar": ["--append felix-bar --jump ACCEPT"],
            "felix-baz": ["--append felix-baz --jump ACCEPT"],
        })

        # Then delete bar and baz.  The former should be stubbed because it
        # is required by chain foo.  The latter should be deleted.
        self.ipt.delete_chains(["felix-bar", "felix-baz"], async=True)
        self.step_actor(self.ipt)
        self.stub.assert_chain_contents({
            "felix-foo": ["--append felix-foo --jump felix-bar"],
            "felix-bar": drop_rules("felix-bar"),
        })

    def test_cleanup_bad_read_back(self):
        # IptablesUpdater hears about some chains before the cleanup.
        self.ipt.rewrite_chains(
            {"felix-foo": ["--append felix-foo --jump felix-boff"]},
            {"felix-foo": set(["felix-boff"])},
            async=True,
        )
        self.step_actor(self.ipt)
        self.stub.assert_chain_contents({
            "felix-foo": ["--append felix-foo --jump felix-boff"],
            "felix-boff": drop_rules("felix-boff"),
        })

        # Some other process then breaks our chains.
        self.stub.chains_contents = {}
        self.stub.iptables_save_output = [
            None,  # Start of cleanup.
            # End of cleanup.  Out of sync:
            "*filter\n"
            ":INPUT DROP [68:4885]\n"
            ":FORWARD DROP [0:0]\n"
            ":OUTPUT ACCEPT [20:888]\n"
            ":DOCKER - [0:0]\n"
            "-A INPUT -i lxcbr0 -p tcp -m tcp --dport 53 -j ACCEPT\n"
            "-A FORWARD -o lxcbr0 -j ACCEPT\n"
            "COMMIT\n"
        ]
        _log.info("Forcing iptables-save to always return %s",
                  self.stub.iptables_save_output)

        # Issue the cleanup.
        with patch.object(fiptables._log, "error") as m_error:
            self.ipt.cleanup(async=True)
            self.step_actor(self.ipt)
            m_error.assert_called_once_with(
                ANY,
                set([]),
                set([]),
                set(["felix-foo", "felix-boff"])
            )
            self.stub.assert_chain_contents({
                "felix-foo": ["--append felix-foo --jump felix-boff"],
                "felix-boff": drop_rules("felix-boff"),
            })

    def test_ensure_rule_inserted(self):
        fragment = "FOO --jump DROP"
        with patch.object(self.ipt, "_execute_iptables") as m_exec:
            m_exec.side_effect = iter([FailedSystemCall("Message", [], 1, "",
                                                        "line 2 failed"),
                                       None,
                                       None])
            self.ipt.ensure_rule_inserted(fragment, async=True)
            self.step_actor(self.ipt)
            self.assertEqual(
                m_exec.mock_calls,
                [
                    call(["*filter",
                          "--delete FOO --jump DROP",
                          "--insert FOO --jump DROP",
                          "COMMIT"],
                         fail_log_level=logging.DEBUG),
                    call(["*filter",
                          "--insert FOO --jump DROP",
                          "COMMIT"]),
                ])

            self.assertTrue(fragment in self.ipt._inserted_rule_fragments)

    def test_insert_remove_tracking(self):
        fragment = "FOO --jump DROP"
        with patch.object(self.ipt, "_execute_iptables") as m_exec:
            m_exec.side_effect = [
                # Insert.
                None,
                # Remove: requires an exception to terminate loop.
                None,
                FailedSystemCall("Message", [], 1, "", "line 2 failed"),
                # Insert.
                None,
            ]
            self.ipt.ensure_rule_inserted(fragment, async=True)
            self.step_actor(self.ipt)
            self.assertTrue(fragment in self.ipt._inserted_rule_fragments)
            self.assertTrue(fragment not in self.ipt._removed_rule_fragments)

            self.ipt.ensure_rule_removed(fragment, async=True)
            self.step_actor(self.ipt)
            self.assertTrue(fragment not in self.ipt._inserted_rule_fragments)
            self.assertTrue(fragment in self.ipt._removed_rule_fragments)

            self.ipt.ensure_rule_inserted(fragment, async=True)
            self.step_actor(self.ipt)
            self.assertTrue(fragment in self.ipt._inserted_rule_fragments)
            self.assertTrue(fragment not in self.ipt._removed_rule_fragments)

    def test_ensure_rule_removed(self):
        fragment = "FOO --jump DROP"
        with patch.object(self.ipt, "_execute_iptables") as m_exec:
            m_exec.side_effect = iter([None,
                                       FailedSystemCall("Message", [], 1, "",
                                                        "line 2 failed")])
            self.ipt.ensure_rule_removed(fragment, async=True)
            self.step_actor(self.ipt)
            exp_call = call([
                '*filter',
                '--delete FOO --jump DROP',
                'COMMIT',
            ], fail_log_level=logging.DEBUG)
            self.assertEqual(m_exec.mock_calls, [exp_call] * 2)

    def test_ensure_rule_removed_not_present(self):
        with patch.object(self.ipt, "_execute_iptables") as m_exec:
            m_exec.side_effect = iter([FailedSystemCall("Message", [], 1, "",
                                                        "line 2 failed")])
            self.ipt.ensure_rule_removed("FOO --jump DROP", async=True)
            self.step_actor(self.ipt)
            exp_call = call([
                '*filter',
                '--delete FOO --jump DROP',
                'COMMIT',
            ], fail_log_level=logging.DEBUG)
            self.assertEqual(m_exec.mock_calls, [exp_call])

    def test_ensure_rule_removed_missing_dep(self):
        with patch.object(self.ipt, "_execute_iptables") as m_exec:
            m_exec.side_effect = iter([
                FailedSystemCall("Message", [], 1, "",
                                 "at line: 2\n"
                                 "ipset doesn't exist")])
            self.ipt.ensure_rule_removed("FOO --jump DROP", async=True)
            self.step_actor(self.ipt)
            exp_call = call([
                '*filter',
                '--delete FOO --jump DROP',
                'COMMIT',
            ], fail_log_level=logging.DEBUG)
            self.assertEqual(m_exec.mock_calls, [exp_call])

    def test_ensure_rule_removed_error(self):
        with patch.object(self.ipt, "_execute_iptables") as m_exec:
            m_exec.side_effect = iter([FailedSystemCall("Message", [], 1, "",
                                                        "the foo is barred")])
            f = self.ipt.ensure_rule_removed("FOO --jump DROP", async=True)
            self.step_actor(self.ipt)
            self.assertRaises(FailedSystemCall, f.get)
            exp_call = call([
                '*filter',
                '--delete FOO --jump DROP',
                'COMMIT',
            ], fail_log_level=logging.DEBUG)
            self.assertEqual(m_exec.mock_calls, [exp_call])

    def test_refresh_iptables(self):
        self.ipt.ensure_rule_inserted("INPUT -j ACCEPT", async=True)
        self.ipt.ensure_rule_inserted("INPUT -j DROP", async=True)
        self.ipt.ensure_rule_removed("INPUT -j DROP", async=True)
        self.step_actor(self.ipt)

        self.ipt.refresh_iptables(async=True)
        with patch.object(self.ipt, "_insert_rule") as m_insert_rule:
            with patch.object(self.ipt, "_remove_rule") as m_remove_rule:
                self.step_actor(self.ipt)
                m_insert_rule.assert_called_once_with("INPUT -j ACCEPT",
                                                      log_level=logging.DEBUG)
                m_remove_rule.assert_called_once_with("INPUT -j DROP",
                                                      log_level=logging.DEBUG)
Ejemplo n.º 2
0
class TestIptablesUpdater(BaseTestCase):

    def setUp(self):
        super(TestIptablesUpdater, self).setUp()
        self.stub = IptablesStub("filter")
        self.ipt = IptablesUpdater("filter", 4)
        self.ipt._execute_iptables = self.stub.apply_iptables_restore
        self.check_output_patch = patch("gevent.subprocess.check_output",
                                        autospec=True)
        self.m_check_output = self.check_output_patch.start()
        self.m_check_output.side_effect = self.fake_check_output

    def fake_check_output(self, cmd, *args, **kwargs):
        if cmd == ["iptables-save", "--table", "filter"]:
            return self.stub.generate_iptables_save()
        elif cmd == ['iptables', '--wait', '--list', '--table', 'filter']:
            return self.stub.generate_iptables_list()
        else:
            raise AssertionError("Unexpected call %r" % cmd)

    def tearDown(self):
        self.check_output_patch.stop()
        super(TestIptablesUpdater, self).tearDown()

    def test_rewrite_chains_stub(self):
        """
        Tests that referencing a chain causes it to get stubbed out.
        """
        self.ipt.rewrite_chains(
            {"foo": ["--append foo --jump bar"]},
            {"foo": set(["bar"])},
            async=True,
        )
        self.step_actor(self.ipt)
        self.assertEqual(self.stub.chains_contents,
            {"foo": ["--append foo --jump bar"],
             'bar': [MISSING_CHAIN_DROP % "bar"] })

    def test_delete_required_chain_stub(self):
        """
        Tests that deleting a required chain stubs it out instead.
        """
        # Exit the graceful restart period, during which we do not stub out
        # chains.
        self.ipt.cleanup(async=True)
        # Install a couple of chains.  foo depends on bar.
        self.ipt.rewrite_chains(
            {"foo": ["--append foo --jump bar"],
             "bar": ["--append bar --jump ACCEPT"]},
            {"foo": set(["bar"]),
             "bar": set()},
            async=True,
        )
        self.step_actor(self.ipt)
        # Both chains should be programmed as normal.
        self.assertEqual(self.stub.chains_contents,
            {"foo": ["--append foo --jump bar"],
             'bar': ["--append bar --jump ACCEPT"] })

        # Deleting bar should stub it out instead.
        self.ipt.delete_chains(["bar"], async=True)
        self.step_actor(self.ipt)
        self.assertEqual(self.stub.chains_contents,
            {"foo": ["--append foo --jump bar"],
             'bar': [MISSING_CHAIN_DROP % "bar"] })

    def test_cleanup_with_dependencies(self):
        # Set up the dataplane with some chains that the IptablesUpdater
        # doesn't know about and some that it will know about.
        self.stub.apply_iptables_restore("""
        *filter
        :INPUT DROP [10:505]
        :FORWARD DROP [0:0]
        :OUTPUT ACCEPT [40:1600]
        # These non-felix chains should be ignored
        :ignore-me -
        :ignore-me-too -
        # These are left-over felix chains.  Some depend on each other.  They
        # can only be cleaned up in the correct order.
        :felix-foo - [0:0]
        :felix-bar -
        :felix-foo -
        :felix-baz -
        :felix-biff -
        --append felix-foo --src 10.0.0.1/32 --jump felix-bar
        # baz depends on biff; cleanup needs to detect that.
        --append felix-baz --src 10.0.0.2/32 --jump felix-biff
        --append felix-biff --src 10.0.0.3/32 --jump DROP
        --append ignore-me --jump ignore-me-too
        --append ignore-me-too --jump DROP
        """.splitlines())

        # IptablesUpdater hears about some chains before the cleanup.  These
        # partially overlap with the ones that are already there.
        self.ipt.rewrite_chains(
            {"felix-foo": ["--append felix-foo --jump felix-bar",
                           "--append felix-foo --jump felix-baz",
                           "--append felix-foo --jump felix-boff"],
             "felix-bar": ["--append felix-bar --jump ACCEPT"]},
            # felix-foo depends on:
            # * a new chain that's also being programmed
            # * a pre-existing chain that is present at start of day
            # * a new chain that isn't present at all.
            {"felix-foo": set(["felix-bar", "felix-baz", "felix-boff"]),
             "felix-bar": set()},
            async=True,
        )
        self.step_actor(self.ipt)

        # Dataplane should now have all the new chains in place, including
        # a stub for felix-boff.  However, the old chains should not have been
        # cleaned up.
        self.stub.assert_chain_contents({
            "INPUT": [],
            "FORWARD": [],
            "OUTPUT": [],
            "ignore-me": ["--append ignore-me --jump ignore-me-too"],
            "ignore-me-too": ["--append ignore-me-too --jump DROP"],
            "felix-foo": ["--append felix-foo --jump felix-bar",
                          "--append felix-foo --jump felix-baz",
                          "--append felix-foo --jump felix-boff"],
            "felix-bar": ["--append felix-bar --jump ACCEPT"],
            "felix-baz": ["--append felix-baz --src 10.0.0.2/32 "
                          "--jump felix-biff"],
            "felix-boff": [MISSING_CHAIN_DROP % "felix-boff"],
            "felix-biff": ["--append felix-biff --src 10.0.0.3/32 --jump DROP"],
        })

        # Issue the cleanup.
        self.ipt.cleanup(async=True)
        self.step_actor(self.ipt)

        # Should now have stubbed-out chains for all the ones that are not
        # programmed.
        self.stub.assert_chain_contents({
            # Non felix chains ignored:
            "INPUT": [],
            "FORWARD": [],
            "OUTPUT": [],
            "ignore-me": ["--append ignore-me --jump ignore-me-too"],
            "ignore-me-too": ["--append ignore-me-too --jump DROP"],
            # Explicitly-programmed chains programmed.
            "felix-foo": ["--append felix-foo --jump felix-bar",
                          "--append felix-foo --jump felix-baz",
                          "--append felix-foo --jump felix-boff"],
            "felix-bar": ["--append felix-bar --jump ACCEPT"],
            # All required but unknown chains stubbed.
            "felix-baz": [MISSING_CHAIN_DROP % "felix-baz"],
            "felix-boff": [MISSING_CHAIN_DROP % "felix-boff"],
            # felix-biff deleted, even though it was referenced by felix-baz
            # before.
        })
Ejemplo n.º 3
0
class TestIptablesUpdater(BaseTestCase):
    def setUp(self):
        super(TestIptablesUpdater, self).setUp()
        self.stub = IptablesStub("filter")
        self.m_config = Mock()
        self.m_config.REFRESH_INTERVAL = 0  # disable refresh thread
        self.ipt = IptablesUpdater("filter", self.m_config, 4)
        self.ipt._execute_iptables = self.stub.apply_iptables_restore
        self.check_output_patch = patch("gevent.subprocess.check_output",
                                        autospec=True)
        self.m_check_output = self.check_output_patch.start()
        self.m_check_output.side_effect = self.fake_check_output

    def fake_check_output(self, cmd, *args, **kwargs):
        _log.info("Stubbing out call to %s", cmd)
        if cmd == ["iptables-save", "--table", "filter"]:
            return self.stub.generate_iptables_save()
        elif cmd == ['iptables', '--wait', '--list', '--table', 'filter']:
            return self.stub.generate_iptables_list()
        else:
            raise AssertionError("Unexpected call %r" % cmd)

    def tearDown(self):
        self.check_output_patch.stop()
        super(TestIptablesUpdater, self).tearDown()

    def test_rewrite_chains_stub(self):
        """
        Tests that referencing a chain causes it to get stubbed out.
        """
        self.ipt.rewrite_chains(
            {"foo": ["--append foo --jump bar"]},
            {"foo": set(["bar"])},
            async=True,
        )
        self.step_actor(self.ipt)
        self.assertEqual(
            self.stub.chains_contents, {
                "foo": ["--append foo --jump bar"],
                'bar': [MISSING_CHAIN_DROP % "bar"]
            })

    def test_rewrite_chains_cover(self):
        """
        Hits remaining code paths in rewrite chains.
        """
        cb = Mock()
        self.ipt.rewrite_chains(
            {"foo": ["--append foo --jump bar"]},
            {"foo": set(["bar"])},
            suppress_upd_log=True,
            async=True,
            callback=cb,
        )
        self.step_actor(self.ipt)
        cb.assert_called_once_with(None)

    def test_delete_required_chain_stub(self):
        """
        Tests that deleting a required chain stubs it out instead.
        """
        # Exit the graceful restart period, during which we do not stub out
        # chains.
        self.ipt.cleanup(async=True)
        # Install a couple of chains.  foo depends on bar.
        self.ipt.rewrite_chains(
            {
                "foo": ["--append foo --jump bar"],
                "bar": ["--append bar --jump ACCEPT"]
            },
            {
                "foo": set(["bar"]),
                "bar": set()
            },
            async=True,
        )
        self.step_actor(self.ipt)
        # Both chains should be programmed as normal.
        self.assertEqual(
            self.stub.chains_contents, {
                "foo": ["--append foo --jump bar"],
                'bar': ["--append bar --jump ACCEPT"]
            })

        # Deleting bar should stub it out instead.
        self.ipt.delete_chains(["bar"], async=True)
        self.step_actor(self.ipt)
        self.assertEqual(
            self.stub.chains_contents, {
                "foo": ["--append foo --jump bar"],
                'bar': [MISSING_CHAIN_DROP % "bar"]
            })

    def test_cleanup_with_dependencies(self):
        # Set up the dataplane with some chains that the IptablesUpdater
        # doesn't know about and some that it will know about.
        self.stub.apply_iptables_restore("""
        *filter
        :INPUT DROP [10:505]
        :FORWARD DROP [0:0]
        :OUTPUT ACCEPT [40:1600]
        # These non-felix chains should be ignored
        :ignore-me -
        :ignore-me-too -
        # These are left-over felix chains.  Some depend on each other.  They
        # can only be cleaned up in the correct order.
        :felix-foo - [0:0]
        :felix-bar -
        :felix-foo -
        :felix-baz -
        :felix-biff -
        --append felix-foo --src 10.0.0.1/32 --jump felix-bar
        # baz depends on biff; cleanup needs to detect that.
        --append felix-baz --src 10.0.0.2/32 --jump felix-biff
        --append felix-biff --src 10.0.0.3/32 --jump DROP
        --append ignore-me --jump ignore-me-too
        --append ignore-me-too --jump DROP
        """.splitlines())

        # IptablesUpdater hears about some chains before the cleanup.  These
        # partially overlap with the ones that are already there.
        self.ipt.rewrite_chains(
            {
                "felix-foo": [
                    "--append felix-foo --jump felix-bar",
                    "--append felix-foo --jump felix-baz",
                    "--append felix-foo --jump felix-boff"
                ],
                "felix-bar": ["--append felix-bar --jump ACCEPT"]
            },
            # felix-foo depends on:
            # * a new chain that's also being programmed
            # * a pre-existing chain that is present at start of day
            # * a new chain that isn't present at all.
            {
                "felix-foo": set(["felix-bar", "felix-baz", "felix-boff"]),
                "felix-bar": set()
            },
            async=True,
        )
        self.step_actor(self.ipt)

        # Dataplane should now have all the new chains in place, including
        # a stub for felix-boff.  However, the old chains should not have been
        # cleaned up.
        self.stub.assert_chain_contents({
            "INPUT": [],
            "FORWARD": [],
            "OUTPUT": [],
            "ignore-me": ["--append ignore-me --jump ignore-me-too"],
            "ignore-me-too": ["--append ignore-me-too --jump DROP"],
            "felix-foo": [
                "--append felix-foo --jump felix-bar",
                "--append felix-foo --jump felix-baz",
                "--append felix-foo --jump felix-boff"
            ],
            "felix-bar": ["--append felix-bar --jump ACCEPT"],
            "felix-baz":
            ["--append felix-baz --src 10.0.0.2/32 "
             "--jump felix-biff"],
            "felix-boff": [MISSING_CHAIN_DROP % "felix-boff"],
            "felix-biff":
            ["--append felix-biff --src 10.0.0.3/32 --jump DROP"],
        })

        # Issue the cleanup.
        self.ipt.cleanup(async=True)
        self.step_actor(self.ipt)

        # Should now have stubbed-out chains for all the ones that are not
        # programmed.
        self.stub.assert_chain_contents({
            # Non felix chains ignored:
            "INPUT": [],
            "FORWARD": [],
            "OUTPUT": [],
            "ignore-me": ["--append ignore-me --jump ignore-me-too"],
            "ignore-me-too": ["--append ignore-me-too --jump DROP"],
            # Explicitly-programmed chains programmed.
            "felix-foo": [
                "--append felix-foo --jump felix-bar",
                "--append felix-foo --jump felix-baz",
                "--append felix-foo --jump felix-boff"
            ],
            "felix-bar": ["--append felix-bar --jump ACCEPT"],
            # All required but unknown chains stubbed.
            "felix-baz": [MISSING_CHAIN_DROP % "felix-baz"],
            "felix-boff": [MISSING_CHAIN_DROP % "felix-boff"],
            # felix-biff deleted, even though it was referenced by felix-baz
            # before.
        })

    def test_cleanup_bad_read_back(self):
        # IptablesUpdater hears about some chains before the cleanup.
        self.ipt.rewrite_chains(
            {"felix-foo": ["--append felix-foo --jump felix-boff"]},
            {"felix-foo": set(["felix-boff"])},
            async=True,
        )
        self.step_actor(self.ipt)
        self.stub.assert_chain_contents({
            "felix-foo": ["--append felix-foo --jump felix-boff"],
            "felix-boff": [MISSING_CHAIN_DROP % "felix-boff"],
        })

        # Some other process then breaks our chains.
        self.stub.chains_contents = {}
        self.stub.iptables_save_output = [
            None,  # Start of cleanup.
            # End of cleanup.  Out of sync:
            "*filter\n"
            ":INPUT DROP [68:4885]\n"
            ":FORWARD DROP [0:0]\n"
            ":OUTPUT ACCEPT [20:888]\n"
            ":DOCKER - [0:0]\n"
            "-A INPUT -i lxcbr0 -p tcp -m tcp --dport 53 -j ACCEPT\n"
            "-A FORWARD -o lxcbr0 -j ACCEPT\n"
            "COMMIT\n"
        ]
        _log.info("Forcing iptables-save to always return %s",
                  self.stub.iptables_save_output)

        # Issue the cleanup.
        with patch.object(fiptables._log, "error") as m_error:
            self.ipt.cleanup(async=True)
            self.step_actor(self.ipt)
            m_error.assert_called_once_with(ANY, set([]), set([]),
                                            set(["felix-foo", "felix-boff"]))
            self.stub.assert_chain_contents({
                "felix-foo": ["--append felix-foo --jump felix-boff"],
                "felix-boff": [MISSING_CHAIN_DROP % "felix-boff"],
            })

    def test_ensure_rule_inserted(self):
        fragment = "FOO --jump DROP"
        with patch.object(self.ipt, "_execute_iptables") as m_exec:
            m_exec.side_effect = iter([
                FailedSystemCall("Message", [], 1, "", "line 2 failed"), None,
                None
            ])
            self.ipt.ensure_rule_inserted(fragment, async=True)
            self.step_actor(self.ipt)
            self.assertEqual(m_exec.mock_calls, [
                call([
                    "*filter", "--delete FOO --jump DROP",
                    "--insert FOO --jump DROP", "COMMIT"
                ],
                     fail_log_level=logging.DEBUG),
                call(["*filter", "--insert FOO --jump DROP", "COMMIT"]),
            ])

            self.assertTrue(fragment in self.ipt._inserted_rule_fragments)

    def test_insert_remove_tracking(self):
        fragment = "FOO --jump DROP"
        with patch.object(self.ipt, "_execute_iptables") as m_exec:
            m_exec.side_effect = [
                # Insert.
                None,
                # Remove: requires an exception to terminate loop.
                None,
                FailedSystemCall("Message", [], 1, "", "line 2 failed"),
                # Insert.
                None,
            ]
            self.ipt.ensure_rule_inserted(fragment, async=True)
            self.step_actor(self.ipt)
            self.assertTrue(fragment in self.ipt._inserted_rule_fragments)
            self.assertTrue(fragment not in self.ipt._removed_rule_fragments)

            self.ipt.ensure_rule_removed(fragment, async=True)
            self.step_actor(self.ipt)
            self.assertTrue(fragment not in self.ipt._inserted_rule_fragments)
            self.assertTrue(fragment in self.ipt._removed_rule_fragments)

            self.ipt.ensure_rule_inserted(fragment, async=True)
            self.step_actor(self.ipt)
            self.assertTrue(fragment in self.ipt._inserted_rule_fragments)
            self.assertTrue(fragment not in self.ipt._removed_rule_fragments)

    def test_ensure_rule_removed(self):
        fragment = "FOO --jump DROP"
        with patch.object(self.ipt, "_execute_iptables") as m_exec:
            m_exec.side_effect = iter([
                None,
                FailedSystemCall("Message", [], 1, "", "line 2 failed")
            ])
            self.ipt.ensure_rule_removed(fragment, async=True)
            self.step_actor(self.ipt)
            exp_call = call([
                '*filter',
                '--delete FOO --jump DROP',
                'COMMIT',
            ],
                            fail_log_level=logging.DEBUG)
            self.assertEqual(m_exec.mock_calls, [exp_call] * 2)

    def test_ensure_rule_removed_not_present(self):
        with patch.object(self.ipt, "_execute_iptables") as m_exec:
            m_exec.side_effect = iter(
                [FailedSystemCall("Message", [], 1, "", "line 2 failed")])
            self.ipt.ensure_rule_removed("FOO --jump DROP", async=True)
            self.step_actor(self.ipt)
            exp_call = call([
                '*filter',
                '--delete FOO --jump DROP',
                'COMMIT',
            ],
                            fail_log_level=logging.DEBUG)
            self.assertEqual(m_exec.mock_calls, [exp_call])

    def test_ensure_rule_removed_missing_dep(self):
        with patch.object(self.ipt, "_execute_iptables") as m_exec:
            m_exec.side_effect = iter([
                FailedSystemCall("Message", [], 1, "", "at line: 2\n"
                                 "ipset doesn't exist")
            ])
            self.ipt.ensure_rule_removed("FOO --jump DROP", async=True)
            self.step_actor(self.ipt)
            exp_call = call([
                '*filter',
                '--delete FOO --jump DROP',
                'COMMIT',
            ],
                            fail_log_level=logging.DEBUG)
            self.assertEqual(m_exec.mock_calls, [exp_call])

    def test_ensure_rule_removed_error(self):
        with patch.object(self.ipt, "_execute_iptables") as m_exec:
            m_exec.side_effect = iter(
                [FailedSystemCall("Message", [], 1, "", "the foo is barred")])
            f = self.ipt.ensure_rule_removed("FOO --jump DROP", async=True)
            self.step_actor(self.ipt)
            self.assertRaises(FailedSystemCall, f.get)
            exp_call = call([
                '*filter',
                '--delete FOO --jump DROP',
                'COMMIT',
            ],
                            fail_log_level=logging.DEBUG)
            self.assertEqual(m_exec.mock_calls, [exp_call])

    def test_refresh_iptables(self):
        self.ipt.ensure_rule_inserted("INPUT -j ACCEPT", async=True)
        self.ipt.ensure_rule_inserted("INPUT -j DROP", async=True)
        self.ipt.ensure_rule_removed("INPUT -j DROP", async=True)
        self.step_actor(self.ipt)

        self.ipt.refresh_iptables(async=True)
        with patch.object(self.ipt, "_insert_rule") as m_insert_rule:
            with patch.object(self.ipt, "_remove_rule") as m_remove_rule:
                self.step_actor(self.ipt)
                m_insert_rule.assert_called_once_with("INPUT -j ACCEPT",
                                                      log_level=logging.DEBUG)
                m_remove_rule.assert_called_once_with("INPUT -j DROP",
                                                      log_level=logging.DEBUG)