# Copyright 2012 Locaweb.
# 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.

import os
import sys
from unittest import mock

import fixtures
from neutron_lib import constants
from neutron_lib import exceptions
from neutron_lib.exceptions import l3 as l3_exc
from oslo_config import cfg
import testtools

from neutron._i18n import _
from neutron.agent.linux import iptables_comments as ic
from neutron.agent.linux import iptables_manager
from neutron.agent.linux import utils as linux_utils
from neutron.tests import base
from neutron.tests import tools


IPTABLES_ARG = {'bn': iptables_manager.binary_name,
                'snat_out_comment': ic.SNAT_OUT,
                'filter_rules': '',
                'mark': constants.ROUTER_MARK_MASK}

NAT_TEMPLATE = ('# Generated by iptables_manager\n'
                '*nat\n'
                ':OUTPUT - [0:0]\n'
                ':POSTROUTING - [0:0]\n'
                ':PREROUTING - [0:0]\n'
                ':neutron-postrouting-bottom - [0:0]\n'
                ':%(bn)s-OUTPUT - [0:0]\n'
                ':%(bn)s-POSTROUTING - [0:0]\n'
                ':%(bn)s-PREROUTING - [0:0]\n'
                ':%(bn)s-float-snat - [0:0]\n'
                ':%(bn)s-snat - [0:0]\n'
                '-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
                '-I POSTROUTING 1 -j %(bn)s-POSTROUTING\n'
                '-I POSTROUTING 2 -j neutron-postrouting-bottom\n'
                '-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
                '-I neutron-postrouting-bottom 1 -j %(bn)s-snat\n'
                '-I %(bn)s-snat 1 -j '
                '%(bn)s-float-snat\n'
                'COMMIT\n'
                '# Completed by iptables_manager\n')

NAT_DUMP = NAT_TEMPLATE % IPTABLES_ARG

FILTER_TEMPLATE = ('# Generated by iptables_manager\n'
                   '*filter\n'
                   ':FORWARD - [0:0]\n'
                   ':INPUT - [0:0]\n'
                   ':OUTPUT - [0:0]\n'
                   ':neutron-filter-top - [0:0]\n'
                   ':%(bn)s-FORWARD - [0:0]\n'
                   ':%(bn)s-INPUT - [0:0]\n'
                   ':%(bn)s-OUTPUT - [0:0]\n'
                   ':%(bn)s-local - [0:0]\n'
                   '-I FORWARD 1 -j neutron-filter-top\n'
                   '-I FORWARD 2 -j %(bn)s-FORWARD\n'
                   '-I INPUT 1 -j %(bn)s-INPUT\n'
                   '-I OUTPUT 1 -j neutron-filter-top\n'
                   '-I OUTPUT 2 -j %(bn)s-OUTPUT\n'
                   '-I neutron-filter-top 1 -j %(bn)s-local\n'
                   'COMMIT\n'
                   '# Completed by iptables_manager\n')

FILTER_DUMP = FILTER_TEMPLATE % IPTABLES_ARG

FILTER_WITH_RULES_TEMPLATE = (
    '# Generated by iptables_manager\n'
    '*filter\n'
    ':FORWARD - [0:0]\n'
    ':INPUT - [0:0]\n'
    ':OUTPUT - [0:0]\n'
    ':neutron-filter-top - [0:0]\n'
    ':%(bn)s-FORWARD - [0:0]\n'
    ':%(bn)s-INPUT - [0:0]\n'
    ':%(bn)s-OUTPUT - [0:0]\n'
    ':%(bn)s-filter - [0:0]\n'
    ':%(bn)s-local - [0:0]\n'
    '-I FORWARD 1 -j neutron-filter-top\n'
    '-I FORWARD 2 -j %(bn)s-FORWARD\n'
    '-I INPUT 1 -j %(bn)s-INPUT\n'
    '-I OUTPUT 1 -j neutron-filter-top\n'
    '-I OUTPUT 2 -j %(bn)s-OUTPUT\n'
    '-I neutron-filter-top 1 -j %(bn)s-local\n'
    '%(filter_rules)s'
    'COMMIT\n'
    '# Completed by iptables_manager\n')

COMMENTED_NAT_DUMP = (
    '# Generated by iptables_manager\n'
    '*nat\n'
    ':OUTPUT - [0:0]\n'
    ':POSTROUTING - [0:0]\n'
    ':PREROUTING - [0:0]\n'
    ':neutron-postrouting-bottom - [0:0]\n'
    ':%(bn)s-OUTPUT - [0:0]\n'
    ':%(bn)s-POSTROUTING - [0:0]\n'
    ':%(bn)s-PREROUTING - [0:0]\n'
    ':%(bn)s-float-snat - [0:0]\n'
    ':%(bn)s-snat - [0:0]\n'
    '-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
    '-I POSTROUTING 1 -j %(bn)s-POSTROUTING\n'
    '-I POSTROUTING 2 -j neutron-postrouting-bottom\n'
    '-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
    '-I neutron-postrouting-bottom 1 '
    '-m comment --comment "%(snat_out_comment)s" -j %(bn)s-snat\n'
    '-I %(bn)s-snat 1 -j '
    '%(bn)s-float-snat\n'
    'COMMIT\n'
    '# Completed by iptables_manager\n' % IPTABLES_ARG)

TRAFFIC_COUNTERS_DUMP = (
    'Chain OUTPUT (policy ACCEPT 400 packets, 65901 bytes)\n'
    '    pkts      bytes target     prot opt in     out     source'
    '               destination         \n'
    '     400   65901 chain1     all  --  *      *       0.0.0.0/0'
    '            0.0.0.0/0           \n'
    '     400   65901 chain2     all  --  *      *       0.0.0.0/0'
    '            0.0.0.0/0           \n')

FILTER_RESTORE_DUMP = ('# Generated by iptables_manager\n'
                       '*filter\n'
                       ':FORWARD - [0:0]\n'
                       ':INPUT - [0:0]\n'
                       ':OUTPUT - [0:0]\n'
                       ':neutron-filter-top - [0:0]\n'
                       ':%(bn)s-FORWARD - [0:0]\n'
                       ':%(bn)s-INPUT - [0:0]\n'
                       ':%(bn)s-OUTPUT - [0:0]\n'
                       ':%(bn)s-test-filter - [0:0]\n'
                       ':%(bn)s-local - [0:0]\n'
                       '-A FORWARD -j neutron-filter-top\n'
                       '-A FORWARD -j %(bn)s-FORWARD\n'
                       '-A INPUT -j %(bn)s-INPUT\n'
                       '-A OUTPUT -j neutron-filter-top\n'
                       '-A OUTPUT -j %(bn)s-OUTPUT\n'
                       '-A neutron-filter-top -j %(bn)s-local\n'
                       '%(filter_rules)s'
                       'COMMIT\n'
                       '# Completed by iptables_manager\n')

NAT_RESTORE_TEMPLATE = ('# Generated by iptables_manager\n'
                        '*nat\n'
                        ':OUTPUT - [0:0]\n'
                        ':POSTROUTING - [0:0]\n'
                        ':PREROUTING - [0:0]\n'
                        ':neutron-postrouting-bottom - [0:0]\n'
                        ':%(bn)s-OUTPUT - [0:0]\n'
                        ':%(bn)s-POSTROUTING - [0:0]\n'
                        ':%(bn)s-PREROUTING - [0:0]\n'
                        ':%(bn)s-float-snat - [0:0]\n'
                        ':%(bn)s-snat - [0:0]\n'
                        '-A OUTPUT -j %(bn)s-OUTPUT\n'
                        '-A POSTROUTING -j %(bn)s-POSTROUTING\n'
                        '-A POSTROUTING -j neutron-postrouting-bottom\n'
                        '-A PREROUTING -j %(bn)s-PREROUTING\n'
                        '-A neutron-postrouting-bottom -j %(bn)s-snat\n'
                        '-A %(bn)s-snat -j '
                        '%(bn)s-float-snat\n'
                        'COMMIT\n'
                        '# Completed by iptables_manager\n')

NAT_RESTORE_DUMP = NAT_RESTORE_TEMPLATE % IPTABLES_ARG


class IptablesTestCase(base.BaseTestCase):

    def test_get_binary_name_in_unittest(self):
        # Corresponds to sys.argv content when running python -m unittest class
        with mock.patch('sys.argv', ['python -m unittest', 'class']):
            binary_name = iptables_manager.get_binary_name()
            self.assertEqual('python_-m_unitte', binary_name)


class IptablesCommentsTestCase(base.BaseTestCase):

    def setUp(self):
        super(IptablesCommentsTestCase, self).setUp()
        cfg.CONF.set_override('comment_iptables_rules', True, 'AGENT')
        self.iptables = iptables_manager.IptablesManager()
        self.execute = mock.patch.object(linux_utils, 'execute').start()

    def test_comments_short_enough(self):
        for attr in dir(ic):
            if not attr.startswith('__') and len(getattr(ic, attr)) > 255:
                self.fail("Iptables comment %s is longer than 255 characters."
                          % attr)

    def test_reordering_of_jump_rule_comments(self):
        # jump at the start
        self.assertEqual(
            '-m comment --comment "aloha" -j sg-chain',
            iptables_manager.comment_rule('-j sg-chain', 'aloha'))
        # jump in the middle
        self.assertEqual(
            '-s source -m comment --comment "aloha" -j sg-chain',
            iptables_manager.comment_rule('-s source -j sg-chain', 'aloha'))
        # no jump rule
        self.assertEqual(
            '-s source -m comment --comment "aloha"',
            iptables_manager.comment_rule('-s source', 'aloha'))

    def test_add_filter_rule(self):
        iptables_args = {}
        iptables_args.update(IPTABLES_ARG)
        filter_rules = ('-I %(bn)s-INPUT 1 -s 0/0 -d 192.168.0.2 -j '
                        '%(bn)s-filter\n-I %(bn)s-filter 1 -j DROP\n'
                        % iptables_args)
        iptables_args['filter_rules'] = filter_rules
        filter_dump_mod = FILTER_WITH_RULES_TEMPLATE % iptables_args

        raw_dump = _generate_raw_dump(IPTABLES_ARG)
        mangle_dump = _generate_mangle_dump(IPTABLES_ARG)

        expected_calls_and_values = [
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(filter_dump_mod + mangle_dump +
                                      COMMENTED_NAT_DUMP + raw_dump),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(FILTER_DUMP + mangle_dump +
                                      COMMENTED_NAT_DUMP + raw_dump),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
        ]
        tools.setup_mock_calls(self.execute, expected_calls_and_values)

        self.iptables.ipv4['filter'].add_chain('filter')
        self.iptables.ipv4['filter'].add_rule('filter', '-j DROP')
        self.iptables.ipv4['filter'].add_rule('INPUT',
                                              '-s 0/0 -d 192.168.0.2 -j'
                                              ' %(bn)s-filter' % IPTABLES_ARG)
        self.iptables.apply()

        self.iptables.ipv4['filter'].remove_rule('filter', '-j DROP')
        self.iptables.ipv4['filter'].remove_rule('INPUT',
                                                 '-s 0/0 -d 192.168.0.2 -j'
                                                 ' %(bn)s-filter'
                                                 % IPTABLES_ARG)
        self.iptables.ipv4['filter'].remove_chain('filter')

        self.iptables.apply()

        tools.verify_mock_calls(self.execute, expected_calls_and_values)


def _generate_mangle_dump(iptables_args):
    return ('# Generated by iptables_manager\n'
            '*mangle\n'
            ':FORWARD - [0:0]\n'
            ':INPUT - [0:0]\n'
            ':OUTPUT - [0:0]\n'
            ':POSTROUTING - [0:0]\n'
            ':PREROUTING - [0:0]\n'
            ':%(bn)s-FORWARD - [0:0]\n'
            ':%(bn)s-INPUT - [0:0]\n'
            ':%(bn)s-OUTPUT - [0:0]\n'
            ':%(bn)s-POSTROUTING - [0:0]\n'
            ':%(bn)s-PREROUTING - [0:0]\n'
            ':%(bn)s-mark - [0:0]\n'
            '-I FORWARD 1 -j %(bn)s-FORWARD\n'
            '-I INPUT 1 -j %(bn)s-INPUT\n'
            '-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
            '-I POSTROUTING 1 -j %(bn)s-POSTROUTING\n'
            '-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
            '-I %(bn)s-PREROUTING 1 -j %(bn)s-mark\n'
            'COMMIT\n'
            '# Completed by iptables_manager\n' % iptables_args)


def _generate_mangle_dump_v6(iptables_args):
    return ('# Generated by iptables_manager\n'
            '*mangle\n'
            ':FORWARD - [0:0]\n'
            ':INPUT - [0:0]\n'
            ':OUTPUT - [0:0]\n'
            ':POSTROUTING - [0:0]\n'
            ':PREROUTING - [0:0]\n'
            ':%(bn)s-FORWARD - [0:0]\n'
            ':%(bn)s-INPUT - [0:0]\n'
            ':%(bn)s-OUTPUT - [0:0]\n'
            ':%(bn)s-POSTROUTING - [0:0]\n'
            ':%(bn)s-PREROUTING - [0:0]\n'
            '-I FORWARD 1 -j %(bn)s-FORWARD\n'
            '-I INPUT 1 -j %(bn)s-INPUT\n'
            '-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
            '-I POSTROUTING 1 -j %(bn)s-POSTROUTING\n'
            '-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
            'COMMIT\n'
            '# Completed by iptables_manager\n' % iptables_args)


def _generate_nat_dump_v6(iptables_args):
    return ('# Generated by iptables_manager\n'
            '*nat\n'
            ':PREROUTING - [0:0]\n'
            ':%(bn)s-PREROUTING - [0:0]\n'
            '-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
            'COMMIT\n'
            '# Completed by iptables_manager\n' % iptables_args)


def _generate_raw_dump(iptables_args):
    return ('# Generated by iptables_manager\n'
            '*raw\n'
            ':OUTPUT - [0:0]\n'
            ':PREROUTING - [0:0]\n'
            ':%(bn)s-OUTPUT - [0:0]\n'
            ':%(bn)s-PREROUTING - [0:0]\n'
            '-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
            '-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
            'COMMIT\n'
            '# Completed by iptables_manager\n' % iptables_args)


def _generate_mangle_restore_dump(iptables_args):
    return ('# Generated by iptables_manager\n'
            '*mangle\n'
            ':FORWARD - [0:0]\n'
            ':INPUT - [0:0]\n'
            ':OUTPUT - [0:0]\n'
            ':POSTROUTING - [0:0]\n'
            ':PREROUTING - [0:0]\n'
            ':%(bn)s-FORWARD - [0:0]\n'
            ':%(bn)s-INPUT - [0:0]\n'
            ':%(bn)s-OUTPUT - [0:0]\n'
            ':%(bn)s-POSTROUTING - [0:0]\n'
            ':%(bn)s-PREROUTING - [0:0]\n'
            ':%(bn)s-mark - [0:0]\n'
            '-A FORWARD -j %(bn)s-FORWARD\n'
            '-A INPUT -j %(bn)s-INPUT\n'
            '-A OUTPUT -j %(bn)s-OUTPUT\n'
            '-A POSTROUTING -j %(bn)s-POSTROUTING\n'
            '-A PREROUTING -j %(bn)s-PREROUTING\n'
            '-A %(bn)s-PREROUTING -j %(bn)s-mark\n'
            'COMMIT\n'
            '# Completed by iptables_manager\n' % iptables_args)


def _generate_raw_restore_dump(iptables_args):
    return ('# Generated by iptables_manager\n'
            '*raw\n'
            ':OUTPUT - [0:0]\n'
            ':PREROUTING - [0:0]\n'
            ':%(bn)s-OUTPUT - [0:0]\n'
            ':%(bn)s-PREROUTING - [0:0]\n'
            '-A OUTPUT -j %(bn)s-OUTPUT\n'
            '-A PREROUTING -j %(bn)s-PREROUTING\n'
            'COMMIT\n'
            '# Completed by iptables_manager\n' % iptables_args)


MANGLE_DUMP = _generate_mangle_dump(IPTABLES_ARG)
MANGLE_DUMP_V6 = _generate_mangle_dump_v6(IPTABLES_ARG)
NAT_DUMP_V6 = _generate_nat_dump_v6(IPTABLES_ARG)
RAW_DUMP = _generate_raw_dump(IPTABLES_ARG)
MANGLE_RESTORE_DUMP = _generate_mangle_restore_dump(IPTABLES_ARG)
RAW_RESTORE_DUMP = _generate_raw_restore_dump(IPTABLES_ARG)


class IptablesFixture(fixtures.Fixture):
    def _setUp(self):
        # We MUST save and restore use_table_lock because it is a class
        # attribute and could change state in some tests, which can cause
        # the other iptables_manager test cases to randomly fail due to
        # race conditions.
        self.use_table_lock = iptables_manager.IptablesManager.use_table_lock
        iptables_manager.IptablesManager.use_table_lock = False
        self.addCleanup(self._reset)

    def _reset(self):
        iptables_manager.IptablesManager.use_table_lock = self.use_table_lock


class IptablesManagerBaseTestCase(base.BaseTestCase):
    def setUp(self):
        super(IptablesManagerBaseTestCase, self).setUp()
        cfg.CONF.set_override('comment_iptables_rules', False, 'AGENT')
        cfg.CONF.set_override('report_interval', 30, 'AGENT')
        self.execute = mock.patch.object(linux_utils, "execute").start()
        self.useFixture(IptablesFixture())

    def _extend_with_ip6tables_filter_end(self, expected_calls, filter_dump):
        expected_calls.extend([
            (mock.call(['ip6tables-save'], run_as_root=True,
                       privsep_exec=True),
             ''),
            (mock.call(['ip6tables-restore', '-n'],
                       process_input=filter_dump, run_as_root=True,
                       privsep_exec=True, log_fail_as_error=False),
             None)])

    def _extend_with_ip6tables_filter(self, expected_calls, filter_dump):
        expected_calls.insert(2, (
            mock.call(['ip6tables-save'],
                      run_as_root=True, privsep_exec=True),
            ''))
        expected_calls.insert(3, (
            mock.call(['ip6tables-restore', '-n'],
                      process_input=filter_dump, run_as_root=True,
                      privsep_exec=True, log_fail_as_error=False),
            None))
        self._extend_with_ip6tables_filter_end(expected_calls, filter_dump)


class IptablesManagerStateFulTestCase(IptablesManagerBaseTestCase):
    use_ipv6 = False

    def setUp(self):
        super(IptablesManagerStateFulTestCase, self).setUp()
        self.iptables = iptables_manager.IptablesManager(
            use_ipv6=self.use_ipv6)

    def test_binary_name(self):
        expected = os.path.basename(sys.argv[0])[:16]
        self.assertEqual(expected, iptables_manager.binary_name)

    def test_get_chain_name(self):
        name = '0123456789' * 5
        # 28 chars is the maximum length of iptables chain name.
        self.assertEqual(iptables_manager.get_chain_name(name, wrap=False),
                         name[:28])
        # 11 chars is the maximum length of chain name of iptable_manager
        # if binary_name is prepended.
        self.assertEqual(iptables_manager.get_chain_name(name, wrap=True),
                         name[:11])

    def test_defer_apply_with_exception(self):
        self.iptables._apply = mock.Mock(side_effect=Exception)
        with testtools.ExpectedException(l3_exc.IpTablesApplyException):
            with self.iptables.defer_apply():
                pass

    def test_add_and_remove_chain(self):
        filter_dump_mod = FILTER_WITH_RULES_TEMPLATE % IPTABLES_ARG

        expected_calls_and_values = [
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(filter_dump_mod + MANGLE_DUMP +
                                      NAT_DUMP + RAW_DUMP),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(FILTER_DUMP + MANGLE_DUMP + NAT_DUMP +
                                      RAW_DUMP),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
        ]
        if self.use_ipv6:
            self._extend_with_ip6tables_filter(
                expected_calls_and_values,
                FILTER_DUMP + MANGLE_DUMP_V6 + NAT_DUMP_V6 + RAW_DUMP)

        tools.setup_mock_calls(self.execute, expected_calls_and_values)

        self.iptables.ipv4['filter'].add_chain('filter')
        self.iptables.apply()

        self.iptables.ipv4['filter'].remove_chain('filter')
        self.iptables.apply()

        tools.verify_mock_calls(self.execute, expected_calls_and_values)

    def test_add_filter_rule(self):
        iptables_args = {}
        iptables_args.update(IPTABLES_ARG)
        filter_rules = ('-I %(bn)s-INPUT 1 -s 0/0 -d 192.168.0.2 -j '
                        '%(bn)s-filter\n-I %(bn)s-filter 1 -j DROP\n'
                        % iptables_args)
        iptables_args['filter_rules'] = filter_rules
        filter_dump_mod = FILTER_WITH_RULES_TEMPLATE % iptables_args

        raw_dump = RAW_DUMP % IPTABLES_ARG

        expected_calls_and_values = [
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(filter_dump_mod + MANGLE_DUMP +
                                      NAT_DUMP + RAW_DUMP),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(FILTER_DUMP + MANGLE_DUMP + NAT_DUMP +
                                      RAW_DUMP),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
        ]
        if self.use_ipv6:
            self._extend_with_ip6tables_filter(
                expected_calls_and_values,
                FILTER_DUMP + MANGLE_DUMP_V6 + NAT_DUMP_V6 + raw_dump)

        tools.setup_mock_calls(self.execute, expected_calls_and_values)

        self.iptables.ipv4['filter'].add_chain('filter')
        self.iptables.ipv4['filter'].add_rule('filter', '-j DROP')
        self.iptables.ipv4['filter'].add_rule('INPUT',
                                              '-s 0/0 -d 192.168.0.2 -j'
                                              ' %(bn)s-filter' % IPTABLES_ARG)
        self.iptables.apply()

        self.iptables.ipv4['filter'].remove_rule('filter', '-j DROP')
        self.iptables.ipv4['filter'].remove_rule('INPUT',
                                                 '-s 0/0 -d 192.168.0.2 -j'
                                                 ' %(bn)s-filter'
                                                 % IPTABLES_ARG)
        self.iptables.ipv4['filter'].remove_chain('filter')

        self.iptables.apply()

        tools.verify_mock_calls(self.execute, expected_calls_and_values)

    def test_rule_with_wrap_target(self):
        name = '0123456789' * 5
        wrap = "%s-%s" % (iptables_manager.binary_name,
                          iptables_manager.get_chain_name(name))

        iptables_args = {'bn': iptables_manager.binary_name,
                         'wrap': wrap}

        filter_dump_mod = ('# Generated by iptables_manager\n'
                           '*filter\n'
                           ':FORWARD - [0:0]\n'
                           ':INPUT - [0:0]\n'
                           ':OUTPUT - [0:0]\n'
                           ':neutron-filter-top - [0:0]\n'
                           ':%(wrap)s - [0:0]\n'
                           ':%(bn)s-FORWARD - [0:0]\n'
                           ':%(bn)s-INPUT - [0:0]\n'
                           ':%(bn)s-OUTPUT - [0:0]\n'
                           ':%(bn)s-local - [0:0]\n'
                           '-I FORWARD 1 -j neutron-filter-top\n'
                           '-I FORWARD 2 -j %(bn)s-FORWARD\n'
                           '-I INPUT 1 -j %(bn)s-INPUT\n'
                           '-I OUTPUT 1 -j neutron-filter-top\n'
                           '-I OUTPUT 2 -j %(bn)s-OUTPUT\n'
                           '-I neutron-filter-top 1 -j %(bn)s-local\n'
                           '-I %(bn)s-INPUT 1 -s 0/0 -d 192.168.0.2 -j '
                           '%(wrap)s\n'
                           'COMMIT\n'
                           '# Completed by iptables_manager\n'
                           % iptables_args)

        raw_dump = RAW_DUMP % IPTABLES_ARG

        expected_calls_and_values = [
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(filter_dump_mod + MANGLE_DUMP +
                                      NAT_DUMP + RAW_DUMP),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(FILTER_DUMP + MANGLE_DUMP +
                                      NAT_DUMP + RAW_DUMP),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
        ]
        if self.use_ipv6:
            self._extend_with_ip6tables_filter(
                expected_calls_and_values,
                FILTER_DUMP + MANGLE_DUMP_V6 + NAT_DUMP_V6 + raw_dump)

        tools.setup_mock_calls(self.execute, expected_calls_and_values)

        self.iptables.ipv4['filter'].add_chain(name)
        self.iptables.ipv4['filter'].add_rule('INPUT',
                                              '-s 0/0 -d 192.168.0.2 -j'
                                              ' $%s' % name)
        self.iptables.apply()

        self.iptables.ipv4['filter'].remove_rule('INPUT',
                                                 '-s 0/0 -d 192.168.0.2 -j'
                                                 ' $%s' % name)
        self.iptables.ipv4['filter'].remove_chain(name)

        self.iptables.apply()

        tools.verify_mock_calls(self.execute, expected_calls_and_values)

    def test_add_mangle_rule(self):
        mangle_dump_mod = (
            '# Generated by iptables_manager\n'
            '*mangle\n'
            ':FORWARD - [0:0]\n'
            ':INPUT - [0:0]\n'
            ':OUTPUT - [0:0]\n'
            ':POSTROUTING - [0:0]\n'
            ':PREROUTING - [0:0]\n'
            ':%(bn)s-FORWARD - [0:0]\n'
            ':%(bn)s-INPUT - [0:0]\n'
            ':%(bn)s-OUTPUT - [0:0]\n'
            ':%(bn)s-POSTROUTING - [0:0]\n'
            ':%(bn)s-PREROUTING - [0:0]\n'
            ':%(bn)s-mangle - [0:0]\n'
            ':%(bn)s-mark - [0:0]\n'
            '-I FORWARD 1 -j %(bn)s-FORWARD\n'
            '-I INPUT 1 -j %(bn)s-INPUT\n'
            '-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
            '-I POSTROUTING 1 -j %(bn)s-POSTROUTING\n'
            '-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
            '-I %(bn)s-PREROUTING 1 -j %(bn)s-mark\n'
            '-I %(bn)s-PREROUTING 2 -j MARK --set-xmark 0x1/%(mark)s\n'
            'COMMIT\n'
            '# Completed by iptables_manager\n' % IPTABLES_ARG)

        expected_calls_and_values = [
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(FILTER_DUMP + mangle_dump_mod +
                                      NAT_DUMP + RAW_DUMP),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(FILTER_DUMP + MANGLE_DUMP +
                                      NAT_DUMP + RAW_DUMP),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
        ]
        if self.use_ipv6:
            self._extend_with_ip6tables_filter(
                expected_calls_and_values,
                FILTER_DUMP + MANGLE_DUMP_V6 + NAT_DUMP_V6 + RAW_DUMP)

        tools.setup_mock_calls(self.execute, expected_calls_and_values)

        self.iptables.ipv4['mangle'].add_chain('mangle')
        self.iptables.ipv4['mangle'].add_rule(
            'PREROUTING',
            '-j MARK --set-xmark 0x1/%s' % constants.ROUTER_MARK_MASK)

        self.iptables.apply()

        self.iptables.ipv4['mangle'].remove_rule(
            'PREROUTING',
            '-j MARK --set-xmark 0x1/%s' % constants.ROUTER_MARK_MASK)
        self.iptables.ipv4['mangle'].remove_chain('mangle')

        self.iptables.apply()

        tools.verify_mock_calls(self.execute, expected_calls_and_values)

    def test_add_nat_rule(self):
        nat_dump = NAT_TEMPLATE % IPTABLES_ARG

        nat_dump_mod = ('# Generated by iptables_manager\n'
                        '*nat\n'
                        ':OUTPUT - [0:0]\n'
                        ':POSTROUTING - [0:0]\n'
                        ':PREROUTING - [0:0]\n'
                        ':neutron-postrouting-bottom - [0:0]\n'
                        ':%(bn)s-OUTPUT - [0:0]\n'
                        ':%(bn)s-POSTROUTING - [0:0]\n'
                        ':%(bn)s-PREROUTING - [0:0]\n'
                        ':%(bn)s-float-snat - [0:0]\n'
                        ':%(bn)s-nat - [0:0]\n'
                        ':%(bn)s-snat - [0:0]\n'
                        '-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
                        '-I POSTROUTING 1 -j %(bn)s-POSTROUTING\n'
                        '-I POSTROUTING 2 -j neutron-postrouting-bottom\n'
                        '-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
                        '-I neutron-postrouting-bottom 1 -j %(bn)s-snat\n'
                        '-I %(bn)s-PREROUTING 1 -d 192.168.0.3 -j '
                        '%(bn)s-nat\n'
                        '-I %(bn)s-nat 1 -p tcp --dport 8080 -j '
                        'REDIRECT --to-port 80\n'
                        '-I %(bn)s-snat 1 -j %(bn)s-float-snat\n'
                        'COMMIT\n'
                        '# Completed by iptables_manager\n' % IPTABLES_ARG)

        raw_dump = RAW_DUMP % IPTABLES_ARG

        expected_calls_and_values = [
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(FILTER_DUMP + MANGLE_DUMP +
                                      nat_dump_mod + RAW_DUMP),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(FILTER_DUMP + MANGLE_DUMP + nat_dump +
                                      RAW_DUMP),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
        ]
        if self.use_ipv6:
            self._extend_with_ip6tables_filter(
                expected_calls_and_values,
                FILTER_DUMP + MANGLE_DUMP_V6 + NAT_DUMP_V6 + raw_dump)

        tools.setup_mock_calls(self.execute, expected_calls_and_values)

        self.iptables.ipv4['nat'].add_chain('nat')
        self.iptables.ipv4['nat'].add_rule('PREROUTING',
                                           '-d 192.168.0.3 -j '
                                           '%(bn)s-nat' % IPTABLES_ARG)
        self.iptables.ipv4['nat'].add_rule('nat',
                                           '-p tcp --dport 8080' +
                                           ' -j REDIRECT --to-port 80')

        self.iptables.apply()

        self.iptables.ipv4['nat'].remove_rule('nat',
                                              '-p tcp --dport 8080 -j'
                                              ' REDIRECT --to-port 80')
        self.iptables.ipv4['nat'].remove_rule('PREROUTING',
                                              '-d 192.168.0.3 -j '
                                              '%(bn)s-nat' % IPTABLES_ARG)
        self.iptables.ipv4['nat'].remove_chain('nat')

        self.iptables.apply()

        tools.verify_mock_calls(self.execute, expected_calls_and_values)

    def test_add_raw_rule(self):
        raw_dump_mod = ('# Generated by iptables_manager\n'
                        '*raw\n'
                        ':OUTPUT - [0:0]\n'
                        ':PREROUTING - [0:0]\n'
                        ':%(bn)s-OUTPUT - [0:0]\n'
                        ':%(bn)s-PREROUTING - [0:0]\n'
                        ':%(bn)s-raw - [0:0]\n'
                        '-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
                        '-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
                        '-I %(bn)s-PREROUTING 1 -j CT --notrack\n'
                        'COMMIT\n'
                        '# Completed by iptables_manager\n'
                        % IPTABLES_ARG)

        expected_calls_and_values = [
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(FILTER_DUMP + MANGLE_DUMP + NAT_DUMP +
                                      raw_dump_mod),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(FILTER_DUMP + MANGLE_DUMP + NAT_DUMP +
                                      RAW_DUMP),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
        ]
        if self.use_ipv6:
            self._extend_with_ip6tables_filter(
                expected_calls_and_values,
                FILTER_DUMP + MANGLE_DUMP_V6 + NAT_DUMP_V6 + RAW_DUMP)

        tools.setup_mock_calls(self.execute, expected_calls_and_values)

        self.iptables.ipv4['raw'].add_chain('raw')
        self.iptables.ipv4['raw'].add_rule('PREROUTING',
                                           '-j CT --notrack')

        self.iptables.apply()

        self.iptables.ipv4['raw'].remove_rule('PREROUTING',
                                              '-j CT --notrack')
        self.iptables.ipv4['raw'].remove_chain('raw')

        self.iptables.apply()

        tools.verify_mock_calls(self.execute, expected_calls_and_values)

    def test_add_rule_to_a_nonexistent_chain(self):
        if self.use_ipv6:
            add_rule = self.iptables.ipv6['filter'].add_rule
        else:
            add_rule = self.iptables.ipv4['filter'].add_rule
        self.assertRaises(LookupError, add_rule, 'nonexistent', '-j DROP')

    def test_remove_nonexistent_chain(self):
        if self.use_ipv6:
            remove_chain = self.iptables.ipv6['filter'].remove_chain
        else:
            remove_chain = self.iptables.ipv4['filter'].remove_chain
        with mock.patch.object(iptables_manager, "LOG") as log:
            remove_chain('nonexistent')
        log.debug.assert_called_once_with(
            'Attempted to remove chain %s which does not exist',
            'nonexistent')

    def test_remove_nonexistent_rule(self):
        if self.use_ipv6:
            remove_rule = self.iptables.ipv6['filter'].remove_rule
        else:
            remove_rule = self.iptables.ipv4['filter'].remove_rule
        with mock.patch.object(iptables_manager, "LOG") as log:
            remove_rule('nonexistent', '-j DROP')
        log.warning.assert_called_once_with(
            'Tried to remove rule that was not there: '
            '%(chain)r %(rule)r %(wrap)r %(top)r',
            {'wrap': True, 'top': False, 'rule': '-j DROP',
             'chain': 'nonexistent'})

    def test_iptables__apply_synchronized_no_namespace(self):
        self.execute.side_effect = RuntimeError
        # no namespace set so should raise
        self.assertRaises(RuntimeError,
                          self.iptables._apply_synchronized)
        self.iptables.namespace = 'test'
        with mock.patch('neutron.agent.linux.ip_lib.network_namespace_exists',
                        return_value=True):
            self.assertRaises(RuntimeError,
                              self.iptables._apply_synchronized)
        with mock.patch('neutron.agent.linux.ip_lib.network_namespace_exists',
                        return_value=False):
            self.assertEqual([], self.iptables._apply_synchronized())

    def test_iptables_failure_with_no_failing_line_number(self):
        with mock.patch.object(iptables_manager, "LOG") as log:
            # generate Runtime errors on iptables-restore calls
            def iptables_restore_failer(*args, **kwargs):
                if 'iptables-restore' in args[0]:
                    self.input_lines = kwargs['process_input'].split('\n')
                    # don't provide a specific failure message so all lines
                    # are logged
                    raise RuntimeError()
                return FILTER_DUMP
            self.execute.side_effect = iptables_restore_failer
            # _apply_synchronized calls iptables-restore so it should raise
            # a RuntimeError
            self.assertRaises(RuntimeError,
                              self.iptables._apply_synchronized)
        # The RuntimeError should have triggered a log of the input to the
        # process that it failed to execute. Verify by comparing the log
        # call to the 'process_input' arg given to the failed iptables-restore
        # call.
        # Failure without a specific line number in the error should cause
        # all lines to be logged with numbers.
        logged = ['%7d. %s' % (n, l)
                  for n, l in enumerate(self.input_lines, 1)]
        log.error.assert_called_once_with(_(
            'IPTablesManager.apply failed to apply the '
            'following set of iptables rules:\n%s'),
            '\n'.join(logged)
        )

    def test_iptables_failure(self):
        with mock.patch.object(iptables_manager, "LOG") as log:
            # generate Runtime errors on iptables-restore calls
            def iptables_restore_failer(*args, **kwargs):
                if 'iptables-restore' in args[0]:
                    self.input_lines = kwargs['process_input'].split('\n')
                    # pretend line 11 failed
                    msg = ("Exit code: 1\nStdout: ''\n"
                           "Stderr: 'iptables-restore: line 11 failed\n'")
                    raise exceptions.ProcessExecutionError(
                        msg, iptables_manager.XTABLES_RESOURCE_PROBLEM_CODE)
                return FILTER_DUMP
            self.execute.side_effect = iptables_restore_failer
            # _apply_synchronized calls iptables-restore so it should raise
            # a RuntimeError
            self.assertRaises(RuntimeError,
                              self.iptables._apply_synchronized)

        # check that we tried with -w when the first attempt failed
        self.execute.assert_has_calls(
            [mock.call(['iptables-restore', '-n'],
                       process_input=mock.ANY, run_as_root=True,
                       privsep_exec=True, log_fail_as_error=False),
             mock.call(['iptables-restore', '-n', '-w', '10',
                        '-W', iptables_manager.XLOCK_WAIT_INTERVAL],
                       process_input=mock.ANY, run_as_root=True,
                       privsep_exec=True)])

        # The RuntimeError should have triggered a log of the input to the
        # process that it failed to execute. Verify by comparing the log
        # call to the 'process_input' arg given to the failed iptables-restore
        # call.
        # Line 11 of the input was marked as failing so lines (11 - context)
        # to (11 + context) should be logged
        ctx = iptables_manager.IPTABLES_ERROR_LINES_OF_CONTEXT
        log_start = max(0, 11 - ctx)
        log_end = 11 + ctx
        logged = ['%7d. %s' % (n, l)
                  for n, l in enumerate(self.input_lines[log_start:log_end],
                                        log_start + 1)]
        log.error.assert_called_once_with(_(
            'IPTablesManager.apply failed to apply the '
            'following set of iptables rules:\n%s'),
            '\n'.join(logged)
        )

    def test_iptables_use_table_lock(self):
        # Under normal operation, if we do call iptables-restore with a -w
        # and it succeeds, the next call will only use -w.
        PE_error = exceptions.ProcessExecutionError(
                       "", iptables_manager.XTABLES_RESOURCE_PROBLEM_CODE)

        num_calls = 3
        expected_calls_and_values = [
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             FILTER_DUMP),
            (mock.call(['iptables-restore', '-n'],
                       process_input=mock.ANY, run_as_root=True,
                       privsep_exec=True, log_fail_as_error=False),
             PE_error),
            (mock.call(['iptables-restore', '-n', '-w', '10',
                        '-W', iptables_manager.XLOCK_WAIT_INTERVAL],
                       process_input=mock.ANY, run_as_root=True,
                       privsep_exec=True),
             None),
        ]
        if self.use_ipv6:
            num_calls += 2
            expected_calls_and_values.append(
                (mock.call(['ip6tables-save'], run_as_root=True,
                           privsep_exec=True),
                 FILTER_DUMP))
            expected_calls_and_values.append(
                (mock.call(['ip6tables-restore', '-n', '-w', '10',
                            '-W', iptables_manager.XLOCK_WAIT_INTERVAL],
                           process_input=mock.ANY, run_as_root=True,
                           privsep_exec=True),
                 None))

        tools.setup_mock_calls(self.execute, expected_calls_and_values)
        self.iptables._apply_synchronized()
        self.assertEqual(num_calls, self.execute.call_count)
        tools.verify_mock_calls(self.execute, expected_calls_and_values)

        self.execute.reset_mock()
        num_calls = 2
        expected_calls_and_values = [
            (mock.call(['iptables-save'], run_as_root=True,
                       privsep_exec=True),
             FILTER_DUMP),
            (mock.call(['iptables-restore', '-n', '-w', '10',
                        '-W', iptables_manager.XLOCK_WAIT_INTERVAL],
                       process_input=mock.ANY, run_as_root=True,
                       privsep_exec=True),
             None),
        ]
        if self.use_ipv6:
            num_calls += 2
            expected_calls_and_values.append(
                (mock.call(['ip6tables-save'], run_as_root=True,
                           privsep_exec=True),
                 FILTER_DUMP))
            expected_calls_and_values.append(
                (mock.call(['ip6tables-restore', '-n', '-w', '10',
                            '-W', iptables_manager.XLOCK_WAIT_INTERVAL],
                           process_input=mock.ANY, run_as_root=True,
                           privsep_exec=True),
                 None))

        tools.setup_mock_calls(self.execute, expected_calls_and_values)
        self.iptables._apply_synchronized()
        self.assertEqual(num_calls, self.execute.call_count)
        tools.verify_mock_calls(self.execute, expected_calls_and_values)

        # Another instance of the class should behave similarly now
        self.execute.reset_mock()
        iptm = iptables_manager.IptablesManager(use_ipv6=self.use_ipv6)
        tools.setup_mock_calls(self.execute, expected_calls_and_values)
        iptm._apply_synchronized()
        self.assertEqual(num_calls, self.execute.call_count)
        tools.verify_mock_calls(self.execute, expected_calls_and_values)

    def test_get_traffic_counters_chain_notexists(self):
        with mock.patch.object(iptables_manager, "LOG") as log:
            acc = self.iptables.get_traffic_counters('chain1')
            self.assertIsNone(acc)
        self.assertEqual(0, self.execute.call_count)
        log.warning.assert_called_once_with(
            'Attempted to get traffic counters of chain %s which '
            'does not exist', 'chain1')

    def test_get_traffic_counters(self):
        exp_packets = 800
        exp_bytes = 131802

        expected_calls_and_values = [
            (mock.call(['iptables', '-t', 'filter', '-L', 'OUTPUT',
                        '-n', '-v', '-x', '-w', '10'],
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             TRAFFIC_COUNTERS_DUMP),
            (mock.call(['iptables', '-t', 'raw', '-L', 'OUTPUT', '-n',
                        '-v', '-x', '-w', '10'],
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             ''),
            (mock.call(['iptables', '-t', 'mangle', '-L', 'OUTPUT', '-n',
                        '-v', '-x', '-w', '10'],
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             ''),
            (mock.call(['iptables', '-t', 'nat', '-L', 'OUTPUT', '-n',
                        '-v', '-x', '-w', '10'],
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             ''),
        ]
        if self.use_ipv6:
            expected_calls_and_values.append(
                (mock.call(['ip6tables', '-t', 'raw', '-L', 'OUTPUT',
                           '-n', '-v', '-x', '-w', '10'], run_as_root=True,
                           privsep_exec=True, log_fail_as_error=False),
                 ''))
            expected_calls_and_values.append(
                (mock.call(['ip6tables', '-t', 'filter', '-L', 'OUTPUT',
                           '-n', '-v', '-x', '-w', '10'],
                           run_as_root=True, privsep_exec=True,
                           log_fail_as_error=False),
                 TRAFFIC_COUNTERS_DUMP))
            expected_calls_and_values.append(
                (mock.call(['ip6tables', '-t', 'mangle', '-L', 'OUTPUT',
                           '-n', '-v', '-x', '-w', '10'], run_as_root=True,
                           privsep_exec=True, log_fail_as_error=False),
                 ''))
            exp_packets *= 2
            exp_bytes *= 2

        tools.setup_mock_calls(self.execute, expected_calls_and_values)

        acc = self.iptables.get_traffic_counters('OUTPUT')
        self.assertEqual(acc['pkts'], exp_packets)
        self.assertEqual(acc['bytes'], exp_bytes)

        tools.verify_mock_calls(self.execute, expected_calls_and_values,
                                any_order=True)

    def test_get_traffic_counters_and_zero(self):
        exp_packets = 800
        exp_bytes = 131802

        expected_calls_and_values = [
            (mock.call(['iptables', '-t', 'filter', '-L', 'OUTPUT',
                        '-n', '-v', '-x', '-w', '10', '-Z'],
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             TRAFFIC_COUNTERS_DUMP),
            (mock.call(['iptables', '-t', 'raw', '-L', 'OUTPUT', '-n',
                        '-v', '-x', '-w', '10', '-Z'],
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             ''),
            (mock.call(['iptables', '-t', 'mangle', '-L', 'OUTPUT', '-n',
                        '-v', '-x', '-w', '10', '-Z'],
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             ''),
            (mock.call(['iptables', '-t', 'nat', '-L', 'OUTPUT', '-n',
                        '-v', '-x', '-w', '10', '-Z'],
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             '')
        ]
        if self.use_ipv6:
            expected_calls_and_values.append(
                (mock.call(['ip6tables', '-t', 'raw', '-L', 'OUTPUT',
                            '-n', '-v', '-x', '-w', '10', '-Z'],
                           run_as_root=True, privsep_exec=True,
                           log_fail_as_error=False),
                 ''))
            expected_calls_and_values.append(
                (mock.call(['ip6tables', '-t', 'filter', '-L', 'OUTPUT',
                            '-n', '-v', '-x', '-w', '10', '-Z'],
                           run_as_root=True, privsep_exec=True,
                           log_fail_as_error=False),
                 TRAFFIC_COUNTERS_DUMP))
            expected_calls_and_values.append(
                (mock.call(['ip6tables', '-t', 'mangle', '-L', 'OUTPUT',
                            '-n', '-v', '-x', '-w', '10', '-Z'],
                           run_as_root=True, privsep_exec=True,
                           log_fail_as_error=False),
                 ''))
            exp_packets *= 2
            exp_bytes *= 2

        tools.setup_mock_calls(self.execute, expected_calls_and_values)

        acc = self.iptables.get_traffic_counters('OUTPUT', zero=True)
        self.assertEqual(acc['pkts'], exp_packets)
        self.assertEqual(acc['bytes'], exp_bytes)

        tools.verify_mock_calls(self.execute, expected_calls_and_values,
                                any_order=True)

    def test_add_blank_rule(self):
        iptables_args = {}
        iptables_args.update(IPTABLES_ARG)
        filter_rules = ('-A %(bn)s-test-filter\n' % iptables_args)
        iptables_args['filter_rules'] = filter_rules
        filter_dump_mod = FILTER_RESTORE_DUMP % iptables_args

        expected_calls_and_values = [
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             (filter_dump_mod + MANGLE_RESTORE_DUMP +
              NAT_RESTORE_DUMP + RAW_RESTORE_DUMP)),
        ]
        if self.use_ipv6:
            expected_calls_and_values.append(
                (mock.call(['ip6tables-save'], run_as_root=True,
                           privsep_exec=True),
                 FILTER_DUMP))
            expected_calls_and_values.append(
                (mock.call(['ip6tables-restore', '-n'],
                           process_input=mock.ANY, run_as_root=True,
                           privsep_exec=True, log_fail_as_error=False),
                 None))

        tools.setup_mock_calls(self.execute, expected_calls_and_values)

        self.iptables.ipv4['filter'].add_chain('test-filter')
        self.iptables.ipv4['filter'].add_rule('test-filter', '')

        self.iptables.apply()

        tools.verify_mock_calls(self.execute, expected_calls_and_values)

    def test_add_rule_exchanged_interface_and_ip(self):
        iptables_args = {}
        iptables_args.update(IPTABLES_ARG)
        filter_rules = ('-A %(bn)s-test-filter -d 192.168.0.2 -i tap-xxx '
                        '-j ACCEPT\n'
                        % iptables_args)
        iptables_args['filter_rules'] = filter_rules
        filter_dump_mod = FILTER_RESTORE_DUMP % iptables_args

        RESTORE_INPUT = ('# Generated by iptables_manager\n'
                         '*filter\n'
                         '-D run.py-test-filter 1\n'
                         '-I run.py-test-filter 1 '
                         '-i tap-xxx -d 192.168.0.2 -j ACCEPT\n'
                         'COMMIT\n'
                         '# Completed by iptables_manager\n')

        expected_calls_and_values = [
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             (filter_dump_mod + MANGLE_RESTORE_DUMP +
              NAT_RESTORE_DUMP + RAW_RESTORE_DUMP)),
            (mock.call(['iptables-restore', '-n'],
                       process_input=RESTORE_INPUT,
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
        ]

        if self.use_ipv6:
            self._extend_with_ip6tables_filter_end(
                expected_calls_and_values,
                FILTER_DUMP + MANGLE_DUMP_V6 + NAT_DUMP_V6 + RAW_DUMP)

        tools.setup_mock_calls(self.execute, expected_calls_and_values)

        self.iptables.ipv4['filter'].add_chain('test-filter')
        self.iptables.ipv4['filter'].add_rule('test-filter',
                                              '-i tap-xxx -d 192.168.0.2 '
                                              '-j ACCEPT')

        self.iptables.apply()

        tools.verify_mock_calls(self.execute, expected_calls_and_values)


class IptablesManagerStateFulTestCaseIPv6(IptablesManagerStateFulTestCase):
    use_ipv6 = True


class IptablesManagerStateFulTestCaseCustomBinaryName(
        IptablesManagerBaseTestCase):
    use_ipv6 = False
    bn = ("xbcdef" * 5)

    def setUp(self):
        super(IptablesManagerStateFulTestCaseCustomBinaryName, self).setUp()
        self.iptables = iptables_manager.IptablesManager(
            binary_name=self.bn,
            use_ipv6=self.use_ipv6)

    def test_add_and_remove_chain_custom_binary_name(self):
        iptables_args = {'bn': self.bn[:16], 'filter_rules': ''}

        filter_dump = FILTER_WITH_RULES_TEMPLATE % iptables_args

        filter_dump_ipv6 = FILTER_TEMPLATE % iptables_args

        filter_dump_mod = filter_dump

        nat_dump = NAT_TEMPLATE % iptables_args

        raw_dump = _generate_raw_dump(iptables_args)
        mangle_dump = _generate_mangle_dump(iptables_args)

        expected_calls_and_values = [
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(filter_dump_mod + mangle_dump +
                                      nat_dump + raw_dump),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(filter_dump + mangle_dump +
                                      nat_dump + raw_dump),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
        ]
        if self.use_ipv6:
            mangle_dump_v6 = _generate_mangle_dump_v6(iptables_args)
            nat_dump_v6 = _generate_nat_dump_v6(iptables_args)
            self._extend_with_ip6tables_filter(
                expected_calls_and_values,
                filter_dump_ipv6 + mangle_dump_v6 + nat_dump_v6 + raw_dump)

        tools.setup_mock_calls(self.execute, expected_calls_and_values)

        self.iptables.ipv4['filter'].add_chain('filter')
        self.iptables.apply()

        self.iptables.ipv4['filter'].empty_chain('filter')
        self.iptables.apply()

        tools.verify_mock_calls(self.execute, expected_calls_and_values)


class IptablesManagerStateFulTestCaseCustomBinaryNameIPv6(
        IptablesManagerStateFulTestCaseCustomBinaryName):
    use_ipv6 = True


class IptablesManagerStateFulTestCaseEmptyCustomBinaryName(
        IptablesManagerBaseTestCase):
    use_ipv6 = False
    bn = ("xbcdef" * 5)[:16]

    def setUp(self):
        super(IptablesManagerStateFulTestCaseEmptyCustomBinaryName,
              self).setUp()
        self.iptables = iptables_manager.IptablesManager(
            binary_name=self.bn,
            use_ipv6=self.use_ipv6)

    def test_empty_chain_custom_binary_name(self):
        iptables_args = {'bn': self.bn}

        filter_dump = FILTER_TEMPLATE % iptables_args

        filter_rules = ('-I %(bn)s-filter 1 -s 0/0 -d 192.168.0.2\n'
                        % iptables_args)
        iptables_args['filter_rules'] = filter_rules
        filter_dump_mod = FILTER_WITH_RULES_TEMPLATE % iptables_args

        nat_dump = NAT_TEMPLATE % iptables_args

        raw_dump = _generate_raw_dump(iptables_args)
        mangle_dump = _generate_mangle_dump(iptables_args)

        expected_calls_and_values = [
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(filter_dump_mod + mangle_dump +
                                      nat_dump + raw_dump),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
            (mock.call(['iptables-save'], run_as_root=True, privsep_exec=True),
             ''),
            (mock.call(['iptables-restore', '-n'],
                       process_input=(filter_dump + mangle_dump +
                                      nat_dump + raw_dump),
                       run_as_root=True, privsep_exec=True,
                       log_fail_as_error=False),
             None),
        ]
        if self.use_ipv6:
            mangle_dump_v6 = _generate_mangle_dump_v6(iptables_args)
            nat_dump_v6 = _generate_nat_dump_v6(iptables_args)
            self._extend_with_ip6tables_filter(
                expected_calls_and_values,
                filter_dump + mangle_dump_v6 + nat_dump_v6 + raw_dump)

        tools.setup_mock_calls(self.execute, expected_calls_and_values)

        self.iptables.ipv4['filter'].add_chain('filter')
        self.iptables.ipv4['filter'].add_rule('filter',
                                              '-s 0/0 -d 192.168.0.2')
        self.iptables.apply()

        self.iptables.ipv4['filter'].remove_chain('filter')
        self.iptables.apply()

        tools.verify_mock_calls(self.execute, expected_calls_and_values)


class IptablesManagerStateFulTestCaseEmptyCustomBinaryNameIPv6(
        IptablesManagerStateFulTestCaseEmptyCustomBinaryName):
    use_ipv6 = True


class IptablesManagerStateLessTestCase(base.BaseTestCase):

    def setUp(self):
        super(IptablesManagerStateLessTestCase, self).setUp()
        cfg.CONF.set_override('comment_iptables_rules', False, 'AGENT')
        self.iptables = (iptables_manager.IptablesManager(state_less=True))

    def test_nat_found(self):
        self.assertIn('nat', self.iptables.ipv4)

    def test_mangle_not_found(self):
        self.assertNotIn('mangle', self.iptables.ipv4)

    def test_initialize_mangle_table(self):
        iptables = iptables_manager.IptablesManager(state_less=True)
        iptables.initialize_mangle_table()
        self.assertIn('mangle', iptables.ipv4)
        self.assertIn('nat', iptables.ipv4)

    def test_initialize_nat_table(self):
        iptables = iptables_manager.IptablesManager(state_less=True)
        iptables.initialize_nat_table()
        self.assertIn('nat', iptables.ipv4)
        self.assertNotIn('mangle', iptables.ipv4)


class IptablesManagerNoNatTestCase(base.BaseTestCase):

    def setUp(self):
        super(IptablesManagerNoNatTestCase, self).setUp()
        cfg.CONF.set_override('comment_iptables_rules', False, 'AGENT')
        self.iptables = (iptables_manager.IptablesManager(nat=False))

    def test_nat_found(self):
        self.assertIn('nat', self.iptables.ipv4)

    def test_mangle_found(self):
        self.assertIn('mangle', self.iptables.ipv4)

    def test_initialize_nat_table(self):
        iptables = iptables_manager.IptablesManager(nat=False)
        iptables.initialize_nat_table()
        self.assertIn('nat', iptables.ipv4)
        self.assertIn('mangle', iptables.ipv4)


class IptablesRandomFullyFixture(fixtures.Fixture):
    def _setUp(self):
        # We MUST save and restore _random_fully because it is a class
        # attribute and could change state in some tests, which can cause
        # the other router test cases to randomly fail due to race conditions.
        self._random_fully = iptables_manager.IptablesManager._random_fully
        iptables_manager.IptablesManager._random_fully = None
        self.addCleanup(self._reset)

    def _reset(self):
        iptables_manager.IptablesManager._random_fully = self._random_fully


class IptablesManagerDisableRandomFullyTestCase(base.BaseTestCase):

    def setUp(self):
        super(IptablesManagerDisableRandomFullyTestCase, self).setUp()
        self.useFixture(IptablesRandomFullyFixture())
        self.execute = mock.patch.object(linux_utils, "execute").start()
        cfg.CONF.set_override('use_random_fully', False, "AGENT")

    def test_verify_disable_random_fully(self):
        expected_calls_and_values = [
            (mock.call(['iptables', '--version'],
                       run_as_root=True, privsep_exec=True),
             "iptables v1.6.2")]
        tools.setup_mock_calls(self.execute, expected_calls_and_values)
        iptables_mgrs = [iptables_manager.IptablesManager() for _ in range(3)]
        # The random_full properties of all
        # IptablesManager instances must return False
        for ipt_mgr in iptables_mgrs:
            self.assertFalse(ipt_mgr.random_fully)
