#!/usr/bin/python
# -*- coding: utf-8 -*-

# This file is part of Cockpit.
#
# Copyright (C) 2013 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.

import parent
from testlib import *
import time
import os
import unittest

class TestNetworking(MachineCase):
    def setUp(self):
        MachineCase.setUp(self)

    def get_iface(self, m, mac):
        def getit():
            path = m.execute("grep -li '%s' /sys/class/net/*/address" % mac)
            return path.split("/")[-2]
        iface = wait(getit).strip()
        print "%s -> %s" % (mac, iface)
        return iface

    def add_iface(self, mac=None, vlan=0):
        m = self.machine
        mac = m.add_netiface(mac=mac, vlan=vlan)
        # Wait for the interface to show up
        self.get_iface(m, mac)
        # Trigger udev to make sure that it has been renamed to its final name
        m.execute("udevadm trigger && udevadm settle")
        return self.get_iface(m, mac)

    def wait_for_iface(self, iface):
        sel = "#networking-interfaces tr[data-interface='%s']" % iface

        valid_states = [
                          "Inactive",
                          "bps",
                          "Configuring IP"
                       ]

        def is_valid():
            row_text = self.browser.text(sel)
            for state in valid_states:
                if state in row_text:
                    return True
            return False

        try:
            self.browser.wait_present(sel)
            self.browser.wait_visible(sel)
            self.browser.wait(is_valid)
        except:
            print "Interface %s didn't show up." % iface
            print self.browser.eval_js("$('#networking-interfaces').html()")
            print self.machine.execute("grep . /sys/class/net/*/address")
            raise

    def switch_off_rp_filter(self, m):
        #
        # HACK - rp_filter interacts badly with our multi-homed
        #        interfaces.  Thus, we switch it off by setting all
        #        instances of it to zero.  This should catch all
        #        existing interfaces as well as 'all' and 'default'
        #        (for interfaces added in the future).
        #
        # https://bugzilla.redhat.com/show_bug.cgi?id=1211287
        #
        m.execute("for d in /proc/sys/net/ipv4/conf/*; do echo 0 >$d/rp_filter; done")

    def testBasic(self):
        b = self.browser
        m = self.machine

        self.switch_off_rp_filter(m)

        self.login_and_go("/network")

        mac = "52:54:00:9e:00:F2"
        iface = self.add_iface(mac)
        wait(lambda: m.execute('nmcli device | grep %s | grep -v unavailable' % iface))

        # We create two connection settings for the new interface and
        # explicitly activate one.  This tests whether Cockpit picks
        # up the right one, and also protects us against pre-existing
        # connection settings that might otherwise interfere.

        m.execute("nmcli connection add type ethernet ifname %s con-name TEST" % iface)
        m.execute("nmcli connection add type ethernet ifname %s con-name TEST2" % iface)
        m.execute("nmcli connection modify TEST ipv4.method shared")
        m.execute('nmcli connection up TEST')

        self.wait_for_iface(iface)
        b.click("#networking-interfaces tr[data-interface='%s']" % iface)
        b.wait_visible("#network-interface")

        # Make sure we are working on the settings written above.
        #
        b.wait_present("tr:contains('IPv4')")
        b.wait_in_text("tr:contains('IPv4')", "Shared")

        # Configure a manual IP address
        #
        b.click("tr:contains('IPv4') a")
        b.wait_popup("network-ip-settings-dialog")
        b.click("#network-ip-settings-dialog .btn.dropdown-toggle")
        b.click("#network-ip-settings-dialog a:contains('Manual')")
        b.set_val('#network-ip-settings-dialog input[placeholder="Address"]', "1.2.3.4")
        b.set_val('#network-ip-settings-dialog input[placeholder*="Netmask"]', "255.255.0.8")
        b.click("#network-ip-settings-dialog button:contains('Apply')")
        b.wait_text_not("#network-ip-settings-error span", "")
        b.set_val('#network-ip-settings-dialog input[placeholder*="Netmask"]', "255.255.192.0")
        b.click("#network-ip-settings-dialog button:contains('Apply')")
        b.wait_popdown("network-ip-settings-dialog")
        b.wait_in_text("#network-interface .panel:contains('%s')" % iface, "1.2.3.4/18")

        # Disconnect
        #
        b.click(".panel-heading:contains('%s') .btn:contains('Off')" % iface)
        b.wait_in_text("tr:contains('Status')", "Inactive")

        # Switch it back to "auto" from the command line and bring it
        # up again
        #
        m.execute("nmcli connection modify TEST ipv4.method auto")
        m.execute("nmcli connection modify TEST ipv4.addresses ''")
        b.wait_in_text("tr:contains('IPv4')", "Automatic (DHCP)")
        m.execute("nmcli connection up TEST")
        b.wait_in_text("tr:contains('Status')", "10.111.")

        # Switch off automatic DNS
        b.click("tr:contains('IPv4') a")
        b.wait_popup("network-ip-settings-dialog")
        b.wait_text("#network-ip-settings-dialog [data-field='dns'] .btn.active", "On")
        b.click("#network-ip-settings-dialog [data-field='dns'] .btn:contains('Off')")
        # The "DNS Search Domains" setting should follow suit
        b.wait_text("#network-ip-settings-dialog [data-field='dns_search'] .btn.active", "Off")
        b.click("#network-ip-settings-dialog button:contains('Apply')")
        b.wait_popdown("network-ip-settings-dialog")

        wait(lambda: "yes" in m.execute("nmcli -f ipv4.ignore-auto-dns connection show TEST"))

        # Change MTU

        b.click("tr:contains('MTU') a")
        b.wait_popup("network-ethernet-settings-dialog")
        b.set_checked('#network-ethernet-settings-mtu-custom', True)
        b.set_val('#network-ethernet-settings-mtu-input', "1400")
        b.click("#network-ethernet-settings-apply")
        b.wait_popdown("network-ethernet-settings-dialog")
        b.wait_in_text("tr:contains('MTU')", "1400")

        # We're debugging failures here log status to journal for diagnosis
        wait(lambda: "mtu 1400" in m.execute("ip link show %s | logger -s 2>&1" % iface))

        # Go back to main page
        b.click("#network-interface ol.breadcrumb li:first-child a")
        b.wait_visible("#networking-interfaces")

        # Check that an unmanaged NIC doesn't show up
        unmanaged = self.add_iface(mac="52:54:00:9e:00:F8")
        self.wait_for_iface(unmanaged)
        b.wait_present("#networking-interfaces tr[data-interface='" + unmanaged + "']")
        b.logout()

        # Now mark this interface as unmanaged
        if "ubuntu" in m.image or "debian" in m.image:
            m.execute("printf 'iface {0} inet static\n' >> /etc/network/interfaces".format(unmanaged))
        else:
            m.execute("echo -e 'NM_CONTROLLED=no\\nDEVICE=\"{0}\"\\n' >> /etc/sysconfig/network-scripts/ifcfg-{0}".format(unmanaged))
        m.execute("systemctl restart NetworkManager")

        # Log in and make sure it doesn't show up
        self.login_and_go("/network")
        self.wait_for_iface(iface)
        b.wait_not_present("#networking-interfaces tr[data-interface='" + unmanaged + "']")


    def testBond(self):
        b = self.browser
        m = self.machine

        self.switch_off_rp_filter(m)

        self.login_and_go("/network")

        # Add interfaces and wait for them to be active or not (as
        # appropriate for the OS).

        # HACK - The interfaces are connected each to their own
        # outside network to avoid something similar to
        #
        #  https://bugzilla.redhat.com/show_bug.cgi?id=1211287
        #
        # Switching off rp_filter doesn't seem to be enough in the
        # case of bonds.

        iface1 = self.add_iface(vlan=1)
        iface2 = self.add_iface(vlan=2)
        self.wait_for_iface(iface1)
        self.wait_for_iface(iface2)

        # Bond them
        b.click("button:contains('Add Bond')")
        b.wait_popup("network-bond-settings-dialog")
        b.set_val("#network-bond-settings-dialog tr:contains('Name') input", "tbond")
        b.set_checked("input[data-iface='%s']" % iface1, True)
        b.set_checked("input[data-iface='%s']" % iface2, True)
        b.click("#network-bond-settings-dialog button:contains('Apply')")
        b.wait_popdown("network-bond-settings-dialog")
        b.wait_present("#networking-interfaces tr[data-interface='tbond']")

        # Check that the configuration file has the expected sane name
        # on systems that use "network-scripts".
        if "ubuntu" not in m.image and "debian" not in m.image:
            m.execute("! test -d /etc/sysconfig || test -f /etc/sysconfig/network-scripts/ifcfg-tbond")

        # Check that the members are displayed
        b.click("#networking-interfaces tr[data-interface='tbond'] td:first-child")
        b.wait_visible("#network-interface")
        b.wait_present("#network-interface-slaves tr[data-interface='%s']" % iface1)
        b.wait_present("#network-interface-slaves tr[data-interface='%s']" % iface2)

        # Deactivate the bond and make sure it is still there after a
        # reload.
        b.wait_text_not("#network-interface-mac", "")
        b.wait_present(".panel-heading:contains('tbond') .btn.active:contains('On')")
        b.click(".panel-heading:contains('tbond') .btn:contains('Off')")
        b.wait_in_text("tr:contains('Status')", "Inactive")
        self.assertNotIn("disabled",
                         b.attr(".panel-heading:contains('tbond') .btn:contains('On')", "class"))

        b.reload()
        b.enter_page("/network")
        b.wait_present("#network-interface-name")
        b.wait_text("#network-interface-name", "tbond")
        b.wait_text("#network-interface-hw", "Bond")
        b.wait_present("#network-interface-slaves tr[data-interface='%s']" % iface1)
        b.wait_present("#network-interface-slaves tr[data-interface='%s']" % iface2)

        # Delete the bond
        b.click("#network-interface button:contains('Delete')")
        b.wait_visible("#networking")
        b.wait_not_present("#networking-interfaces tr[data-interface='tbond']")

        # Due to above reload
        self.allow_journal_messages(".*Connection reset by peer.*",
                                    "connection unexpectedly closed by peer")

    @skipImage("No 'team' support packaged", "debian-8", "ubuntu-1604")
    def testTeam(self):
        b = self.browser
        m = self.machine

        self.switch_off_rp_filter(m)

        self.login_and_go("/network")

        can_add_teams = not "atomic" in m.image

        b.wait_attr("#networking-add-team", "data-test-stable", "yes")
        if not can_add_teams:
            b.wait_not_visible("#networking-add-team")
            return

        # Add interfaces and wait for them to be active or not (as
        # appropriate for the OS).

        # The interfaces are connected each to their own outside
        # network to avoid any issues related to multi-homing.

        iface1 = self.add_iface(vlan=1)
        iface2 = self.add_iface(vlan=2)
        self.wait_for_iface(iface1)
        self.wait_for_iface(iface2)

        # team them
        b.click("button:contains('Add Team')")
        b.wait_popup("network-team-settings-dialog")
        b.set_val("#network-team-settings-dialog tr:contains('Name') input", "tteam")
        b.set_checked("input[data-iface='%s']" % iface1, True)
        b.set_checked("input[data-iface='%s']" % iface2, True)
        b.click("#network-team-settings-dialog button:contains('Apply')")
        b.wait_popdown("network-team-settings-dialog")
        b.wait_present("#networking-interfaces tr[data-interface='tteam']")

        # Check that the configuration file has the expected sane name
        # on systems that use "network-scripts".
        if "ubuntu" not in m.image and "debian" not in m.image:
            m.execute("! test -d /etc/sysconfig || test -f /etc/sysconfig/network-scripts/ifcfg-tteam")

        # Check that the members are displayed
        b.click("#networking-interfaces tr[data-interface='tteam'] td:first-child")
        b.wait_visible("#network-interface")
        b.wait_present("#network-interface-slaves tr[data-interface='%s']" % iface1)
        b.wait_present("#network-interface-slaves tr[data-interface='%s']" % iface2)

        # Deactivate the team and make sure it is still there after a
        # reload.
        b.wait_text_not("#network-interface-mac", "")
        b.wait_present(".panel-heading:contains('tteam') .btn.active:contains('On')")
        b.click(".panel-heading:contains('tteam') .btn:contains('Off')")
        b.wait_in_text("tr:contains('Status')", "Inactive")
        b.reload()
        b.enter_page("/network")
        b.wait_present("#network-interface-name")
        b.wait_text("#network-interface-name", "tteam")
        b.wait_text("#network-interface-hw", "Team")
        b.wait_present("#network-interface-slaves tr[data-interface='%s']" % iface1)
        b.wait_present("#network-interface-slaves tr[data-interface='%s']" % iface2)

        # Delete the team
        b.click("#network-interface button:contains('Delete')")
        b.wait_visible("#networking")
        b.wait_not_present("#networking-interfaces tr[data-interface='tteam']")

        # Due to above reload
        self.allow_journal_messages(".*Connection reset by peer.*",
                                    "connection unexpectedly closed by peer")

    def testBridge(self):
        b = self.browser
        m = self.machine

        self.switch_off_rp_filter(m)

        self.login_and_go("/network")

        # Add interfaces and wait for them to be active or not (as
        # appropriate for the OS).

        # The second interface is connected to a different outside
        # network than the first to avoid a cycle between the
        # "cockpit1" bridge that all VMs are connected to, and the
        # bridge we are creating here.

        iface1 = self.add_iface()
        iface2 = self.add_iface(vlan=1)
        self.wait_for_iface(iface1)
        self.wait_for_iface(iface2)

        # Bridge them
        b.click("button:contains('Add Bridge')")
        b.wait_popup("network-bridge-settings-dialog")
        b.set_val("#network-bridge-settings-dialog tr:contains('Name') input", "tbridge")
        b.set_checked("input[data-iface='%s']" % iface1, True)
        b.set_checked("input[data-iface='%s']" % iface2, True)
        b.click("#network-bridge-settings-dialog button:contains('Apply')")
        b.wait_popdown("network-bridge-settings-dialog")
        b.wait_present("#networking-interfaces tr[data-interface='tbridge']")

        # Check that the configuration file has the expected sane name
        # on systems that use "network-scripts".
        if "ubuntu" not in m.image and "debian" not in m.image:
            m.execute("! test -d /etc/sysconfig || test -f /etc/sysconfig/network-scripts/ifcfg-tbridge")

        # Delete the bridge
        b.click("#networking-interfaces tr[data-interface='tbridge'] td:first-child")
        b.wait_visible("#network-interface")
        b.click("#network-interface button:contains('Delete')")
        b.wait_visible("#networking")
        b.wait_not_present("#networking-interfaces tr[data-interface='tbridge']")

    def testVlan(self):
        b = self.browser
        m = self.machine

        self.login_and_go("/network")

        iface = self.add_iface()
        self.wait_for_iface(iface)

        # Make a VLAN interface
        b.click("button:contains('Add VLAN')")
        b.wait_popup("network-vlan-settings-dialog")
        b.click("#network-vlan-settings-dialog tr:contains('Parent') li[value='%s'] a" % iface, True)
        b.set_val("#network-vlan-settings-dialog tr:contains('Name') input", "tvlan")
        b.set_val("#network-vlan-settings-dialog tr:contains('VLAN Id') input", "123")
        b.click("#network-vlan-settings-dialog button:contains('Apply')")
        b.wait_popdown("network-vlan-settings-dialog")
        b.wait_present("#networking-interfaces tr[data-interface='tvlan']")

        # Activate it.  It wont get an IP address, but that's okay.
        b.click("#networking-interfaces tr[data-interface='tvlan'] td:first-child")
        b.wait_visible("#network-interface")
        b.wait_in_text("tr:contains('Status')", "Inactive")
        b.click(".panel-heading .btn:contains('On')")
        b.wait_not_in_text("tr:contains('Status')", "Inactive")

        # Check that the actual kernel device has the REORDER_HDR flag
        # set.  NetworkManager stopped doing that for connections
        # created via D-Bus at some point.
        self.assertIn("REORDER_HDR", m.execute("ip -d link show tvlan | grep vlan"))

        # Delete it
        b.click("#network-interface button:contains('Delete')")
        b.wait_visible("#networking")
        b.wait_not_present("#networking-interfaces tr[data-interface='tvlan']")

    @skipImage("No 'tun' support", "centos-7", "debian-8", "rhel-atomic", "ubuntu-1604")
    def testOther(self):
        b = self.browser
        m = self.machine

        iface = "tun0"

        # Create a tun device and let NetworkManager manage it
        m.execute("ip tuntap add mode tun dev %s" % iface)
        # HACK: https://bugzilla.gnome.org/show_bug.cgi?id=774266
        wait(lambda: m.execute("nmcli --wait 0 dev con %s" % iface) == "")

        self.login_and_go("/network")
        self.wait_for_iface(iface)
        b.click("#networking-interfaces tr[data-interface='%s']" % iface)
        b.wait_visible("#network-interface")

        # Configure a manual IP address
        #
        b.click("tr:contains('IPv4') a")
        b.wait_popup("network-ip-settings-dialog")
        b.click("#network-ip-settings-dialog .btn.dropdown-toggle")
        b.click("#network-ip-settings-dialog a:contains('Manual')")
        b.set_val('#network-ip-settings-dialog input[placeholder="Address"]', "1.2.3.4")
        b.set_val('#network-ip-settings-dialog input[placeholder*="Netmask"]', "24")
        b.click("#network-ip-settings-dialog button:contains('Apply')")
        b.wait_popdown("network-ip-settings-dialog")
        b.wait_in_text("tr:contains('Status')", "1.2.3.4/24")

    @skipImage("Can not mark unmanaged by mac address", "debian-8", "debian-unstable", "ubuntu-1604")
    def testUnmanaged(self):
        b = self.browser
        m = self.machine

        mac = "52:54:00:9e:00:F2"
        m.write("/etc/sysconfig/network-scripts/ifcfg-TEST",
"""
NAME="TEST"
HWADDR=52:54:00:9e:00:F2
NM_CONTROLLED=no
""")
        m.execute("nmcli con reload")

        self.login_and_go("/network")
        iface = self.add_iface(mac)
        sel = "#networking-unmanaged-interfaces tr[data-interface='%s']" % iface
        self.browser.wait_present(sel)
        self.browser.wait_visible(sel)

    @skipImage("No network checkpoint support", "centos-7", "continuous-atomic", "debian-8", "debian-unstable", "fedora-24", "fedora-atomic", "rhel-7", "rhel-atomic", "ubuntu-1604")
    def testCheckpoint(self):
        b = self.browser
        m = self.machine

        iface = self.get_iface(m, m.macaddr)

        self.login_and_go("/network")
        self.wait_for_iface(iface)
        b.click("#networking-interfaces tr[data-interface='%s']" % iface)
        b.wait_visible("#network-interface")

        # Disconnect
        b.click(".panel-heading:contains('%s') .btn:contains('Off')" % iface)
        b.wait_visible("#confirm-breaking-change-popup")
        b.click("#confirm-breaking-change-popup button:contains('Keep connection')")

        # Change IP
        b.click("tr:contains('IPv4') a")
        b.wait_popup("network-ip-settings-dialog")
        b.click("#network-ip-settings-dialog .btn.dropdown-toggle")
        b.click("#network-ip-settings-dialog a:contains('Manual')")
        b.set_val('#network-ip-settings-dialog input[placeholder="Address"]', "1.2.3.4")
        b.set_val('#network-ip-settings-dialog input[placeholder*="Netmask"]', "24")
        b.click("#network-ip-settings-dialog button:contains('Apply')")
        b.wait_visible("#confirm-breaking-change-popup")
        b.click("#confirm-breaking-change-popup button:contains('Keep connection')")

    @skipImage("Main interface can't be managed", "debian-8", "debian-unstable", "ubuntu-1604")
    def testBondingMain(self):
        b = self.browser
        m = self.machine

        iface = self.get_iface(m, m.macaddr)

        self.login_and_go("/network")
        self.wait_for_iface(iface)

        # Put the main interface into a bond.  Everything should keep working.
        b.click("button:contains('Add Bond')")
        b.wait_popup("network-bond-settings-dialog")
        b.set_val("#network-bond-settings-dialog tr:contains('Name') input", "tbond")
        b.set_checked("input[data-iface='%s']" % iface, True)
        b.click("#network-bond-settings-dialog button:contains('Apply')")
        b.wait_popdown("network-bond-settings-dialog")
        b.wait_present("#networking-interfaces tr[data-interface='tbond']")

        # Check that it has the main connection enslaved and the right IP address
        b.click("#networking-interfaces tr[data-interface='tbond'] td:first-child")
        b.wait_visible("#network-interface")
        b.wait_present("#network-interface-slaves tr[data-interface='%s']" % iface)
        b.wait_in_text("#network-interface .panel:contains('tbond')", m.address)

        # Delete the bond
        b.click("#network-interface button:contains('Delete')")
        b.wait_visible("#networking")
        b.wait_not_present("#networking-interfaces tr[data-interface='tbond']")
        b.wait_present("#networking-interfaces tr[data-interface='%s']" % iface)
        b.wait_in_text("#networking-interfaces tr[data-interface='%s']" % iface, m.address)

    @skipImage("No network checkpoint support", "centos-7", "continuous-atomic", "debian-8", "debian-unstable", "fedora-24", "fedora-atomic", "rhel-7", "rhel-atomic", "ubuntu-1604")
    @skipImage("Main interface can't be managed", "debian-8", "debian-unstable", "ubuntu-1604")
    def testBondingMainSlowly(self):
        b = self.browser
        m = self.machine

        iface = self.get_iface(m, m.macaddr)

        self.login_and_go("/network")
        self.wait_for_iface(iface)

        # Slow down DHCP enough that it would trigger a rollback.

        dhcp_delay = 15

        m.needs_writable_usr()
        m.execute("mv /usr/sbin/dhclient /usr/sbin/dhclient.real")
        m.write("/usr/sbin/dhclient", "#! /bin/sh\nsleep %s\nexec /usr/sbin/dhclient.real" % dhcp_delay)
        m.execute("chmod a+x /usr/sbin/dhclient")

        # Put the main interface into a bond.  Everything should keep
        # working since checkpoints are not used and thus no rollback
        # is actually triggered.

        b.click("button:contains('Add Bond')")
        b.wait_popup("network-bond-settings-dialog")
        b.set_val("#network-bond-settings-dialog tr:contains('Name') input", "tbond")
        b.set_checked("input[data-iface='%s']" % iface, True)
        b.click("#network-bond-settings-dialog button:contains('Apply')")
        b.wait_popdown("network-bond-settings-dialog")
        b.wait_present("#networking-interfaces tr[data-interface='tbond']")

        # Check that it has the main connection enslaved and the right IP address
        b.click("#networking-interfaces tr[data-interface='tbond'] td:first-child")
        b.wait_visible("#network-interface")
        b.wait_present("#network-interface-slaves tr[data-interface='%s']" % iface)
        b.wait_in_text("#network-interface .panel:contains('tbond')", m.address)

    def testNoConnectionSettings(self):
        b = self.browser
        m = self.machine

        self.login_and_go("/network")

        iface = self.add_iface()
        self.wait_for_iface(iface)

        b.click("#networking-interfaces tr[data-interface='%s']" % iface)
        b.wait_visible("#network-interface")

        # Edit and apply the ghost settings
        b.click("tr:contains('IPv4') a")
        b.wait_popup("network-ip-settings-dialog")
        b.click("#network-ip-settings-dialog .btn.dropdown-toggle")
        b.click("#network-ip-settings-dialog a:contains('Manual')")
        b.set_val('#network-ip-settings-dialog input[placeholder="Address"]', "1.2.3.4")
        b.set_val('#network-ip-settings-dialog input[placeholder*="Netmask"]', "24")
        b.click("#network-ip-settings-dialog button:contains('Apply')")
        b.wait_popdown("network-ip-settings-dialog")
        b.wait_in_text("tr:contains('IPv4')", "Address 1.2.3.4/24")

        # Check that we now have connection settings
        con_id = m.execute("nmcli -m tabular -t -f GENERAL.CONNECTION device show %s" % iface).strip()
        self.assertEqual(m.execute('nmcli -m tabular -t -f ipv4.method con show "%s"' % con_id).strip(),
                         "manual")

        # The interface will be activated. Deactivate it.
        b.wait_in_text("tr:contains('Status')", "1.2.3.4/24")
        b.click(".panel-heading:contains('%s') .btn:contains('Off')" % iface)
        b.wait_in_text("tr:contains('Status')", "Inactive")

        # Delete the connection settings again and wait for the ghost
        # settings to be re-created.
        m.execute('nmcli con del "%s"' % con_id)
        b.wait_in_text("tr:contains('IPv4')", "Automatic")

        # Activate with ghost settings
        b.click(".panel-heading:contains('%s') .btn:contains('On')" % iface)
        b.wait_in_text("tr:contains('Status')", "10.111.")

        # Check again that we now have connection settings
        con_id = m.execute("nmcli -m tabular -t -f GENERAL.CONNECTION device show %s" % iface).strip()
        self.assertEqual(m.execute('nmcli -m tabular -t -f ipv4.method con show "%s"' % con_id).strip(),
                         "auto")

    @skipImage("No network checkpoint support", "centos-7", "continuous-atomic", "debian-8", "debian-unstable", "fedora-24", "fedora-atomic", "rhel-7", "rhel-atomic", "ubuntu-1604")
    def testCheckpointSlowRollback(self):
        b = self.browser
        m = self.machine

        # Slow down DHCP requests.  This would normally cause the
        # global health check to fail during rollback and prevent
        # showing the #confirm-breaking-change-popup.  We expect the
        # global health check failure to be ignored.

        # We need at least 60 seconds of network disconnection to let
        # the health check fail reliably.  A rollback is started 15
        # seconds after disconnection, so we need to delay the DHCP
        # request by at least 45 seconds.  However, NetworkManager has
        # a default DHCP timeout of 45 seconds, so we need to increase
        # that.

        dhcp_delay = 60
        dhcp_timeout = 120

        # HACK - the ipv4.dhcp-timeout doesn't survive a rollback, so
        # we get the default of 45 seconds and have to use a shorter
        # delay.  This seems to work, but it's too tight for comfort...
        #
        # https://bugzilla.redhat.com/show_bug.cgi?id=1400540

        dhcp_delay = 40

        m.needs_writable_usr()
        m.execute("mv /usr/sbin/dhclient /usr/sbin/dhclient.real")
        m.write("/usr/sbin/dhclient", "#! /bin/sh\nsleep %s\nexec /usr/sbin/dhclient.real" % dhcp_delay)
        m.execute("chmod a+x /usr/sbin/dhclient")

        iface = self.get_iface(m, m.macaddr)

        # Set the DHCP timeout
        con_id = m.execute("nmcli -m tabular -t -f GENERAL.CONNECTION device show %s" % iface).strip()
        m.execute('nmcli con mod "%s" ipv4.dhcp-timeout %s' % (con_id, dhcp_timeout))

        self.login_and_go("/network")
        self.wait_for_iface(iface)
        b.click("#networking-interfaces tr[data-interface='%s']" % iface)
        b.wait_visible("#network-interface")

        # Disconnect
        b.click(".panel-heading:contains('%s') .btn:contains('Off')" % iface)
        with b.wait_timeout(120):
            b.wait_visible("#confirm-breaking-change-popup")
            b.click("#confirm-breaking-change-popup button:contains('Keep connection')")

    def testOtherSettings(self):
        b = self.browser
        m = self.machine

        iface = self.add_iface()
        wait(lambda: m.execute('nmcli device | grep %s | grep -v unavailable' % iface))
        m.execute("nmcli con add type ethernet ifname %s con-name TEST" % iface)
        m.execute("nmcli con mod TEST ipv4.dhcp-hostname foo")

        self.login_and_go("/network")
        self.wait_for_iface(iface)

        b.click("#networking-interfaces tr[data-interface='%s'] td:first-child" % iface)
        b.wait_visible("#network-interface")

        b.click("tr:contains('IPv4') a")
        b.wait_popup("network-ip-settings-dialog")
        b.click("#network-ip-settings-dialog button:contains('Apply')")
        b.wait_popdown("network-ip-settings-dialog")

        self.assertEqual(m.execute("nmcli -m tabular -t -f ipv4.dhcp-hostname con show TEST").strip(),
                         "foo")

    def testNonDefaultBondSettings(self):
        b = self.browser
        m = self.machine

        iface1 = self.add_iface()
        wait(lambda: m.execute('nmcli device | grep %s | grep -v unavailable' % iface1))

        iface2 = self.add_iface()
        wait(lambda: m.execute('nmcli device | grep %s | grep -v unavailable' % iface2))

        m.execute("nmcli con add type ethernet ifname %s con-name TEST1" % iface1)
        m.execute("nmcli con add type ethernet ifname %s con-name TEST2" % iface2)
        m.execute("nmcli con mod TEST1 ipv4.method shared")

        self.login_and_go("/network")
        self.wait_for_iface(iface1)
        self.wait_for_iface(iface2)

        m.execute("nmcli con up TEST1")
        m.execute("nmcli dev dis %s" % iface2)

        b.wait_in_text("tr[data-interface='%s'] td:nth-child(2)" % iface1, "10.42.")
        b.wait_text("tr[data-interface='%s'] td:nth-child(3)" % iface2, "Inactive")

        b.click("button:contains('Add Bond')")
        b.wait_popup("network-bond-settings-dialog")
        b.set_val("#network-bond-settings-dialog tr:contains('Name') input", "tbond")
        b.set_checked("input[data-iface='%s']" % iface1, True)
        b.set_checked("input[data-iface='%s']" % iface2, True)
        b.click("#network-bond-settings-dialog button:contains('Apply')")
        b.wait_popdown("network-bond-settings-dialog")
        b.wait_present("#networking-interfaces tr[data-interface='tbond']")

        b.click("#networking-interfaces tr[data-interface='tbond'] td:first-child")
        b.wait_visible("#network-interface")
        b.wait_in_text("tr:contains('IPv4')", "Shared")

    def testBondRename(self):
        b = self.browser
        m = self.machine

        self.login_and_go("/network")

        # Wait until the page is really ready by waiting for the main interface
        # to show up in some row.
        iface = self.get_iface(m, m.macaddr)
        b.wait_present("tr[data-interface='%s']" % iface)

        # Make a simple bond without any members.  This is enough to
        # test the renaming.

        b.click("button:contains('Add Bond')")
        b.wait_popup("network-bond-settings-dialog")
        b.set_val("#network-bond-settings-dialog tr:contains('Name') input", "tbond")
        b.click("#network-bond-settings-dialog button:contains('Apply')")
        b.wait_popdown("network-bond-settings-dialog")
        b.wait_present("#networking-interfaces tr[data-interface='tbond']")

        # Rename while it is active

        b.click("#networking-interfaces tr[data-interface='tbond'] td:first-child")
        b.wait_visible("#network-interface")
        b.click(".panel-heading .btn:contains('On')")
        b.wait_in_text("tr:contains('Status')", "Configuring")

        b.click("tr:contains('Bond') a")
        b.wait_popup("network-bond-settings-dialog")
        b.set_val("#network-bond-settings-dialog tr:contains('Name') input", "tbond3000")
        b.click("#network-bond-settings-dialog button:contains('Apply')")
        b.wait_popdown("network-bond-settings-dialog")
        b.wait_text("#network-interface-name", "tbond3000")

if __name__ == '__main__':
    test_main()
