#!/usr/bin/python3
#
# Integration tests for bonds
#
# These need to be run in a VM and do change the system
# configuration.
#
# Copyright (C) 2018-2021 Canonical, Ltd.
# Author: Mathieu Trudel-Lapierre <mathieu.trudel-lapierre@canonical.com>
# Author: Lukas Märdian <slyon@ubuntu.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys
import subprocess
import unittest

from base import IntegrationTestsBase, test_backends


class _CommonTests():

    def test_bond_base(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      interfaces: [ethbn]
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)

    def test_bond_primary_member(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    %(ec)s: {}
    %(e2c)s: {}
  bonds:
    mybond:
      interfaces: [%(ec)s, %(e2c)s]
      parameters:
        mode: active-backup
        primary: %(ec)s
      addresses: [ '10.10.10.1/24' ]''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client})
        self.generate_and_settle([self.dev_e_client, self.dev_e2_client, 'mybond'])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up(self.dev_e2_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 10.10.10.1/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            result = f.read().strip()
            self.assertIn(self.dev_e_client, result)
            self.assertIn(self.dev_e2_client, result)
        with open('/sys/class/net/mybond/bonding/primary') as f:
            self.assertEqual(f.read().strip(), '%(ec)s' % {'ec': self.dev_e_client})

    def test_bond_all_members_active(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      interfaces: [ethbn]
      parameters:
        all-members-active: true
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/all_slaves_active') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), '1')

    def test_bond_mode_8023ad(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      parameters:
        mode: 802.3ad
      interfaces: [ethbn]
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/mode') as f:
            self.assertEqual(f.read().strip(), '802.3ad 4')

    def test_bond_mode_8023ad_adselect(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      parameters:
        mode: 802.3ad
        ad-select: bandwidth
      interfaces: [ethbn]
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/ad_select') as f:
            self.assertEqual(f.read().strip(), 'bandwidth 1')

    def test_bond_mode_8023ad_lacp_rate(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      parameters:
        mode: 802.3ad
        lacp-rate: fast
      interfaces: [ethbn]
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/lacp_rate') as f:
            self.assertEqual(f.read().strip(), 'fast 1')

    def test_bond_mode_activebackup_failover_mac(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      parameters:
        mode: active-backup
        fail-over-mac-policy: follow
      interfaces: [ethbn]
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/mode') as f:
            self.assertEqual(f.read().strip(), 'active-backup 1')
        with open('/sys/class/net/mybond/bonding/fail_over_mac') as f:
            self.assertEqual(f.read().strip(), 'follow 2')

    def test_bond_mode_balance_xor(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      parameters:
        mode: balance-xor
      interfaces: [ethbn]
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/mode') as f:
            self.assertEqual(f.read().strip(), 'balance-xor 2')

    def test_bond_mode_balance_rr(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      parameters:
        mode: balance-rr
      interfaces: [ethbn]
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/mode') as f:
            self.assertEqual(f.read().strip(), 'balance-rr 0')

    def test_bond_mode_balance_rr_pps(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      parameters:
        mode: balance-rr
        packets-per-member: 15
      interfaces: [ethbn]
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/mode') as f:
            self.assertEqual(f.read().strip(), 'balance-rr 0')
        with open('/sys/class/net/mybond/bonding/packets_per_slave') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), '15')

    def test_bond_resend_igmp(self):
        self.setup_eth(None, False)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
    ethb2:
      match: {name: %(e2c)s}
  bonds:
    mybond:
      addresses: [192.168.9.9/24]
      interfaces: [ethbn, ethb2]
      parameters:
        mode: balance-rr
        mii-monitor-interval: 50s
        resend-igmp: 100
''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client})
        self.generate_and_settle([self.dev_e_client, self.dev_e2_client, 'mybond'])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up(self.dev_e2_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.9.9/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            result = f.read().strip()
            self.assertIn(self.dev_e_client, result)
            self.assertIn(self.dev_e2_client, result)
        with open('/sys/class/net/mybond/bonding/resend_igmp') as f:
            self.assertEqual(f.read().strip(), '100')


@unittest.skipIf("networkd" not in test_backends,
                 "skipping as networkd backend tests are disabled")
class TestNetworkd(IntegrationTestsBase, _CommonTests):
    backend = 'networkd'

    def test_bond_mac(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match:
        name: %(ec)s
  bonds:
    mybond:
      interfaces: [ethbn]
      macaddress: 00:01:02:03:04:05
      dhcp4: yes''' % {'r': self.backend,
                       'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24', '00:01:02:03:04:05'])

    def test_bond_down_delay(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      interfaces: [ethbn]
      parameters:
        mode: active-backup
        mii-monitor-interval: 5
        down-delay: 10s
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/downdelay') as f:
            self.assertEqual(f.read().strip(), '10000')

    def test_bond_up_delay(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      interfaces: [ethbn]
      parameters:
        mode: active-backup
        mii-monitor-interval: 5
        up-delay: 10000
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/updelay') as f:
            self.assertEqual(f.read().strip(), '10000')

    def test_bond_arp_interval(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      interfaces: [ethbn]
      parameters:
        mode: balance-xor
        arp-ip-targets: [ 192.168.5.1 ]
        arp-interval: 50s
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/arp_interval') as f:
            self.assertEqual(f.read().strip(), '50000')

    def test_bond_arp_targets(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      interfaces: [ethbn]
      parameters:
        mode: balance-xor
        arp-interval: 50000
        arp-ip-targets: [ 192.168.5.1 ]
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/arp_ip_target') as f:
            self.assertEqual(f.read().strip(), '192.168.5.1')

    def test_bond_arp_targets_many_lp1829264(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      interfaces: [ethbn]
      parameters:
        mode: balance-xor
        arp-interval: 50000
        arp-ip-targets: [ 192.168.5.1, 192.168.5.34 ]
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/arp_ip_target') as f:
            result = f.read().strip()
            self.assertIn('192.168.5.1', result)
            self.assertIn('192.168.5.34', result)

    def test_bond_arp_all_targets(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      interfaces: [ethbn]
      parameters:
        mode: balance-xor
        arp-ip-targets: [192.168.5.1]
        arp-interval: 50000
        arp-all-targets: all
        arp-validate: all
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/arp_all_targets') as f:
            self.assertEqual(f.read().strip(), 'all 1')

    def test_bond_arp_validate(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      interfaces: [ethbn]
      parameters:
        mode: balance-xor
        arp-ip-targets: [192.168.5.1]
        arp-interval: 50000
        arp-validate: all
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/arp_validate') as f:
            self.assertEqual(f.read().strip(), 'all 3')


@unittest.skipIf("NetworkManager" not in test_backends,
                 "skipping as NetworkManager backend tests are disabled")
class TestNetworkManager(IntegrationTestsBase, _CommonTests):
    backend = 'NetworkManager'

    @unittest.skip("NetworkManager does not support setting MAC for a bond")
    def test_bond_mac(self):
        pass

    def test_bond_down_delay(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      interfaces: [ethbn]
      parameters:
        mode: active-backup
        mii-monitor-interval: 5
        down-delay: 10000
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/downdelay') as f:
            self.assertEqual(f.read().strip(), '10000')

    def test_bond_up_delay(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      interfaces: [ethbn]
      parameters:
        mode: active-backup
        mii-monitor-interval: 5
        up-delay: 10000
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/updelay') as f:
            self.assertEqual(f.read().strip(), '10000')

    def test_bond_arp_interval(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      interfaces: [ethbn]
      parameters:
        mode: balance-xor
        arp-ip-targets: [ 192.168.5.1 ]
        arp-interval: 50000
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/arp_interval') as f:
            self.assertEqual(f.read().strip(), '50000')

    def test_bond_arp_targets(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      interfaces: [ethbn]
      parameters:
        mode: balance-xor
        arp-interval: 50000
        arp-ip-targets: [ 192.168.5.1 ]
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/arp_ip_target') as f:
            self.assertEqual(f.read().strip(), '192.168.5.1')

    def test_bond_arp_all_targets(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      interfaces: [ethbn]
      parameters:
        mode: balance-xor
        arp-ip-targets: [192.168.5.1]
        arp-interval: 50000
        arp-all-targets: all
        arp-validate: all
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/arp_all_targets') as f:
            self.assertEqual(f.read().strip(), 'all 1')

    def test_bond_mode_balance_tlb_learn_interval(self):
        self.setup_eth(None)
        self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL)
        with open(self.config, 'w') as f:
            f.write('''network:
  renderer: %(r)s
  ethernets:
    ethbn:
      match: {name: %(ec)s}
  bonds:
    mybond:
      parameters:
        mode: balance-tlb
        mii-monitor-interval: 5
        learn-packet-interval: 15
      interfaces: [ethbn]
      dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client})
        self.generate_and_settle([self.dev_e_client, self.state_dhcp4('mybond')])
        self.assert_iface_up(self.dev_e_client, ['master mybond'], ['inet '])  # wokeignore:rule=master
        self.assert_iface_up('mybond', ['inet 192.168.5.[0-9]+/24'])
        with open('/sys/class/net/mybond/bonding/slaves') as f:  # wokeignore:rule=slave
            self.assertEqual(f.read().strip(), self.dev_e_client)
        with open('/sys/class/net/mybond/bonding/mode') as f:
            self.assertEqual(f.read().strip(), 'balance-tlb 5')
        with open('/sys/class/net/mybond/bonding/lp_interval') as f:
            self.assertEqual(f.read().strip(), '15')


unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
