#!/usr/bin/python2
# -*- 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 subprocess
import re
import time

import atomiclib
import parent
from testlib import *

def break_hostkey(m, address, filename=None):
    if not filename:
        filename = "/etc/ssh/ssh_known_hosts"

    m.execute('touch {}'.format(filename))

    line = "{0} ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJqfgO2FPiix1n2sCJCXbaffwog1Vvi3zRdmcAxG//5T".format(address)
    m.execute("echo '{0}'> {1}".format(line, filename))

def fix_hostkey(m, key=None, filename=None):
    if not filename:
        filename = "/etc/ssh/ssh_known_hosts"

    if not key:
        key = '';

    m.execute('touch {}'.format(filename))
    m.execute("echo '{0}' > {1}".format(key, filename))

def break_bridge(m):
    m.execute("ln -snf /bin/false /usr/local/bin/cockpit-bridge")

def fix_bridge(m):
    m.execute("rm /usr/local/bin/cockpit-bridge")

def check_failed_state(b, expected_title):
    b.wait_present("#troubleshoot-dialog .btn-default:not([disabled])")
    b.wait_in_text('#troubleshoot-dialog .btn-default', "Close")
    b.wait_in_text('#troubleshoot-dialog h4', expected_title)
    b.click("#troubleshoot-dialog .btn-default")
    b.wait_popdown('troubleshoot-dialog')

def start_machine_troubleshoot(b, new=False, known_host=False):
    b.wait_visible("#machine-troubleshoot")
    b.click('#machine-troubleshoot')
    b.wait_popup('troubleshoot-dialog')
    if (new):
        b.wait_present("#troubleshoot-dialog .btn-primary:not([disabled])")
        b.wait_text('#troubleshoot-dialog .btn-primary', "Add")
        b.click('#troubleshoot-dialog .btn-primary')

        if not known_host:
            b.wait_in_text('#troubleshoot-dialog', "Fingerprint")
            b.click('#troubleshoot-dialog .btn-primary')

def fail_login(b):
    b.click('#troubleshoot-dialog .btn-primary')
    b.wait_present("#troubleshoot-dialog .btn-primary:not([disabled])")
    b.wait_in_text("#troubleshoot-dialog .dialog-error", "Login failed")

def add_machine(b, address, known_host=False):
    b.switch_to_top()
    b.go("/@%s" % address)
    start_machine_troubleshoot(b, new=True, known_host=known_host)
    b.wait_popdown('troubleshoot-dialog')
    b.enter_page("/system", host=address)

def kill_user_admin(machine):
    machine.execute("loginctl terminate-user admin")

def change_ssh_port(machine, address, port=None, timeout_sec=120):
    try:
        port = int(port)
    except:
        port = 22

    # Keep in mind that not all operating systems have firewalld
    machine.execute("firewall-cmd --permanent --zone=public --add-port={0}/tcp || true".format(port))
    machine.execute("firewall-cmd --reload || true")
    machine.execute("! selinuxenabled || semanage port -a -t ssh_port_t -p tcp {0}".format(port))
    machine.execute("sed -i 's/.*Port .*/#\\0/' /etc/ssh/sshd_config")
    machine.execute("printf 'ListenAddress 127.27.0.15:22\nListenAddress {0}:{1}\n' >> /etc/ssh/sshd_config".format(address, port))

    # We stop the sshd.socket unit and just go with a regular
    # daemon.  This is more portable and reloading/restarting the
    # socket doesn't seem to work well.
    #
    machine.execute("( ! systemctl is-active sshd.socket || systemctl stop sshd.socket) && systemctl restart sshd.service")

    start_time = time.time()
    error = None
    while (time.time() - start_time) < timeout_sec:
        try:
            machine.execute("true", quiet=True)
            return
        except Exception as e:
            error = e
        time.sleep(0.5)
    raise error

class TestMultiMachineAdd(MachineCase):
    provision = {
        "machine1": { "address": "10.111.113.1/20" },
        "machine2": { "address": "10.111.113.2/20" },
        "machine3": { "address": "10.111.113.3/20" },
    }

    def setUp(self):
        MachineCase.setUp(self)
        self.machine2 = self.machines['machine2']
        self.machine2.execute("hostnamectl set-hostname machine2")
        self.machine3 = self.machines['machine3']
        self.machine3.execute("hostnamectl set-hostname machine3")
        atomiclib.overlay_dashboard(self.machine)

    def testBasic(self):
        b = self.browser
        m2 = self.machine2
        m3 = self.machine3
        m3_host = "10.111.113.3:2222"
        change_ssh_port(m3, "10.111.113.3", 2222)

        self.login_and_go(None)
        add_machine(b, "10.111.113.2")
        add_machine(b, m3_host)

        # TODO: The concrete error message when killing the bridge and
        # session is not predictable.  So we just wait for any error
        # to appear without checking the precise text.  We print the
        # error message, to help with issue #606.
        #
        # https://github.com/cockpit-project/cockpit/issues/606

        b.go("/dashboard")
        b.enter_page("/dashboard")
        kill_user_admin(m2)
        b.wait_present("#dashboard-hosts a[data-address='10.111.113.2'] img")
        b.wait_attr("#dashboard-hosts a[data-address='10.111.113.2'] img", 'src', '../shell/images/server-error.png')

        kill_user_admin(m3)
        b.wait_present("#dashboard-hosts a[data-address='10.111.113.3'] img")
        b.wait_attr("#dashboard-hosts a[data-address='10.111.113.3'] img", 'src', '../shell/images/server-error.png')

        # Navigating reconnects
        b.click("#dashboard-hosts a[data-address='10.111.113.2']")
        b.switch_to_top()
        b.wait_js_cond('window.location.pathname != "/dashboard"')
        b.enter_page("/system", host="10.111.113.2")
        b.wait_text_not("#system_information_hostname_button", "")
        b.switch_to_top()
        b.go("/dashboard")
        b.enter_page("/dashboard")
        b.wait_present("#dashboard-hosts a[data-address='10.111.113.2'] img")
        b.wait_attr("#dashboard-hosts a[data-address='10.111.113.2'] img", 'src', '../shell/images/server-small.png')

        b.click("#dashboard-hosts a[data-address='10.111.113.3']")
        b.switch_to_top()
        b.wait_js_cond('window.location.pathname != "/dashboard"')
        b.enter_page("/system", host=m3_host)
        b.wait_text_not("#system_information_hostname_button", "")
        b.switch_to_top()
        b.go("/dashboard")
        b.enter_page("/dashboard")
        b.wait_present("#dashboard-hosts a[data-address='10.111.113.3'] img")
        b.wait_attr("#dashboard-hosts a[data-address='10.111.113.3'] img", 'src', '../shell/images/server-small.png')

        self.allow_restart_journal_messages()
        self.allow_hostkey_messages()
        # Might happen when killing the bridge.
        self.allow_journal_messages("localhost: dropping message while waiting for child to exit",
                                    "Received message for unknown channel: .*",
                                    '.*: Socket error: disconnected',
                                    ".*: error reading from ssh",
                                    ".*: bridge failed: .*",
                                    ".*: bridge program failed: Child process exited with code .*")


class TestMultiMachine(MachineCase):
    provision = {
        "machine1": { "address": "10.111.113.1/20" },
        "machine2": { "address": "10.111.113.2/20" },
        "machine3": { "address": "10.111.113.3/20" },
    }

    def setUp(self):
        MachineCase.setUp(self)
        self.machine2 = self.machines['machine2']
        self.machine2.execute("hostnamectl set-hostname machine2")
        atomiclib.overlay_dashboard(self.machine)

    def checkDirectLogin(self, root='/'):
        b = self.browser
        m2 = self.machine2
        m = self.machine

        name = self.machine.execute("hostname")

        # Change os-release pretty name on m2
        m2.execute("sed -i '/NAME=.*/d' /etc/os-release")
        m2.execute("echo 'NAME=\"A Pretty Name\"' >> /etc/os-release")

        b.switch_to_top()
        b.go("{}/system".format(root));
        b.enter_page("/system")
        b.wait_in_text('#system_information_hostname_button', name.strip())
        b.switch_to_top()
        b.wait_js_cond('window.location.pathname == "{0}system"'.format(root))
        b.click("a[href='/dashboard']")
        b.enter_page("/dashboard")
        b.wait_present("a[data-address='10.111.113.2']")

        # Direct to machine2, new login
        m2.execute("echo admin:alt-password | chpasswd")
        b.switch_to_top()
        b.open("{0}=10.111.113.2".format(root))
        b.wait_visible("#login")
        b.wait_visible("#server-name")
        b.wait_not_visible("#badge")
        b.wait_not_visible("#brand")
        b.wait_in_text("#server-name", "10.111.113.2")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "alt-password")
        b.click('#login-button')
        b.expect_load()
        b.enter_page("/system")
        b.wait_in_text('#system_information_hostname_button', "machine2")
        b.switch_to_top()

        # Branding uses m2 pretty name
        b.wait_in_text ("#index-brand", "A Pretty Name")
        b.wait_js_cond('window.location.pathname == "{0}=10.111.113.2/system"'.format(root))
        if self.machine.image in ["rhel-7-4"]:
            # only fixed in PR #6512 in 143
            m2_dashboard_name = "10.111.113.2"
        else:
            m2_dashboard_name = "machine2"

        # Dashboard is not installed on secondary atomic machines
        if not m2.atomic_image:
            b.click("a[href='/dashboard']")
            b.enter_page("/dashboard")
            b.wait_present("a[data-address='localhost']")
            b.wait_in_text("a[data-address='localhost']", m2_dashboard_name)
            b.wait_not_present("a[data-address='10.111.113.2']")
        else:
            b.wait_not_present("a[href='/dashboard']")
        b.logout()

        # Falls back to legacy /var/lib/cockpit/known_hosts
        m.execute("mkdir -p /var/lib/cockpit; mv /etc/ssh/ssh_known_hosts /var/lib/cockpit/known_hosts")
        b.open("{0}=10.111.113.2".format(root))
        b.wait_visible("#login")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "alt-password")
        b.click('#login-button')
        b.expect_load()
        b.logout()

        # /etc/ssh/ssh_known_hosts trumps legacy /var/lib/cockpit/known_hosts; break key in the latter
        m.execute("cp /var/lib/cockpit/known_hosts /etc/ssh/ssh_known_hosts; sed -i 's/AAA/BBB/' /var/lib/cockpit/known_hosts")
        b.open("{0}=10.111.113.2".format(root))
        b.wait_visible("#login")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "alt-password")
        b.click('#login-button')
        b.expect_load()
        b.logout()
        m.execute("rm /var/lib/cockpit/known_hosts")

        # Bad host key
        m.execute("echo '10.111.113.2 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDgPMmTosSQ4NxMtq+aL2NKLC+W4I9/jbD1e74cnOKTW' > /etc/ssh/ssh_known_hosts")
        b.open("{0}=10.111.113.2".format(root))
        b.wait_visible("#login")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "alt-password")
        b.click('#login-button')

        b.wait_not_visible("#conversation-group")
        b.wait_visible("#password-group")
        b.wait_visible("#user-group")
        b.wait_visible("#option-group")
        b.wait_not_visible("#server-group")
        b.wait_visible("#login-error-message")
        b.wait_in_text("#login-error-message", "Hostkey does not match")

        # Clear host key.
        m.execute("echo '' > /etc/ssh/ssh_known_hosts")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "alt-password")
        b.click('#login-button')

        b.wait_not_visible("#conversation-group")
        b.wait_visible("#password-group")
        b.wait_visible("#user-group")
        b.wait_visible("#option-group")
        b.wait_not_visible("#server-group")
        b.wait_visible("#login-error-message")
        b.wait_in_text("#login-error-message", "Host is unknown")

        m.execute("echo '\n[SSH-Login]\nallowUnknown = true' >> /etc/cockpit/cockpit.conf")
        m.restart_cockpit()
        b.open("{0}=10.111.113.2".format(root))
        b.wait_visible("#login")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "alt-password")
        b.click('#login-button')

        b.wait_visible("#conversation-group")
        b.wait_not_visible("#password-group")
        b.wait_not_visible("#user-group")
        b.wait_not_visible("#option-group")
        b.wait_not_visible("#server-group")
        b.wait_in_text("#conversation-prompt", "MD5 Fingerprint")
        b.wait_in_text("#conversation-message", "Do you want to proceed this time?")
        b.set_val('#conversation-input', 'foobar')
        b.click('#login-button')

        b.wait_not_visible("#conversation-group")
        b.wait_visible("#password-group")
        b.wait_visible("#user-group")
        b.wait_visible("#option-group")
        b.wait_not_visible("#server-group")
        b.wait_visible("#login-error-message")
        b.wait_in_text("#login-error-message", "Hostkey is unknown")

        b.wait_visible("#login")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "alt-password")
        b.click('#login-button')

        b.wait_visible("#conversation-group")
        b.wait_not_visible("#password-group")
        b.wait_not_visible("#user-group")
        b.wait_not_visible("#option-group")
        b.wait_not_visible("#server-group")
        b.wait_in_text("#conversation-prompt", "MD5 Fingerprint")
        b.wait_in_text("#conversation-message", "Do you want to proceed this time?")

        try:
            algo = re.sub(r'MD5 Fingerprint \(ssh-(.*)\):', r'\1', b.text("#conversation-prompt"))
            line = m2.execute("ssh-keygen -l -E md5 -f /etc/ssh/ssh_host_%s_key.pub" % algo, quiet=True)
            fp = line.split(" ")[1].replace('MD5:', '')
            self.assertEqual(b.val('#conversation-input'), fp)
        # ssh-keygen doesn't support -E, just make sure we have a fp
        except subprocess.CalledProcessError:
            self.assertTrue(b.val('#conversation-input'))

        b.click('#login-button')
        b.expect_load()
        b.enter_page("/system")
        b.wait_in_text('#system_information_hostname_button', "machine2")
        b.logout()

        # Check hostkey isn't saved
        self.assertFalse(m.execute("cat /etc/ssh/ssh_known_hosts").strip())

        # Direct to machine2, via form
        b.open("{0}other".format(root))
        b.wait_visible("#login")
        b.wait_visible("#user-group")
        b.wait_visible("#password-group")
        b.wait_not_visible("#server-group")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "bad-password")
        b.click('#login-button')
        b.wait_visible("#login-error-message")

        # Connect to bad machine
        b.set_val("#login-password-input", "bad-password")
        b.click('#option-group span')
        b.wait_visible("#server-group")
        b.set_val("#server-field", "bad")
        b.click('#option-group span')
        b.wait_not_visible("#server-group")
        b.click('#login-button')
        b.wait_visible("#server-group")
        b.wait_visible("#login-error-message")
        b.wait_in_text("#login-error-message", "Unable to connect")

        m.restart_cockpit()
        b.open("{0}=10.111.113.2".format(root))
        b.wait_visible("#login")
        b.set_val("#login-user-input", "admin")

        # Connect to machine2
        b.set_val("#server-field", "10.111.113.2")
        b.set_val("#login-password-input", "alt-password")
        b.click('#login-button')
        b.wait_in_text("#server-name", "10.111.113.2")
        b.wait_visible("#conversation-group")
        b.wait_not_visible("#password-group")
        b.wait_not_visible("#user-group")
        b.wait_not_visible("#option-group")
        b.wait_not_visible("#server-group")
        b.wait_in_text("#conversation-prompt", "MD5 Fingerprint")
        b.wait_in_text("#conversation-message", "Do you want to proceed this time?")
        b.click('#login-button')
        b.expect_load()
        b.enter_page("/system")
        b.wait_in_text('#system_information_hostname_button', "machine2")
        b.switch_to_top()
        b.wait_js_cond('window.location.pathname == "{0}=10.111.113.2/system"'.format(root))

        if not m2.atomic_image:
            b.click("a[href='/dashboard']")
            b.enter_page("/dashboard")
            b.wait_present("a[data-address='localhost']")
            b.wait_in_text("a[data-address='localhost']", m2_dashboard_name)
            b.wait_not_present("a[data-address='10.111.113.2']")
        else:
            b.wait_not_present("a[href='/dashboard']")

        # Might happen when we switch away.
        self.allow_hostkey_messages()
        self.allow_journal_messages(".*: Connection reset by peer",
                                    "connection unexpectedly closed by peer")

    def testDirectLogin(self):
        b = self.browser

        self.login_and_go("/dashboard")
        add_machine(b, "10.111.113.2")
        self.checkDirectLogin('/');

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

        m.execute('mkdir -p /etc/cockpit/ && echo "[WebService]\nUrlRoot = cockpit-new" > /etc/cockpit/cockpit.conf')
        m.start_cockpit()

        # Make sure normal urls don't work.
        output = m.execute('curl -s -o /dev/null -w "%{http_code}" http://localhost:9090/cockpit/socket')
        self.assertIn('404', output)

        output = m.execute('curl -s -o /dev/null -w "%{http_code}" http://localhost:9090/cockpit/socket')
        self.assertIn('404', output)

        b.open("/cockpit-new/system")
        b.wait_visible("#login")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "foobar")
        b.set_checked("#authorized-input", True)
        b.click('#login-button')
        b.expect_load()
        b.enter_page("/system")
        b.switch_to_top()
        b.wait_js_cond('window.location.pathname == "/cockpit-new/system"')

        b.click("a[href='/dashboard']")
        b.enter_page("/dashboard")
        b.switch_to_top()
        b.wait_js_cond('window.location.pathname == "/cockpit-new/dashboard"')
        b.enter_page("/dashboard")
        b.wait_present("a[data-address='localhost']")
        b.click("a[data-address='localhost']")
        b.enter_page("/system")
        b.switch_to_top()

        # Test 2nd machine
        b.click("a[href='/dashboard']")
        b.enter_page("/dashboard")
        add_machine(b, "10.111.113.2")
        b.enter_page("/system", host="10.111.113.2")
        b.wait_text("#system_information_hostname_button", "machine2")
        b.switch_to_top()
        b.wait_js_cond('window.location.pathname == "/cockpit-new/@10.111.113.2/system"')

        # Test subnav
        b.wait_present('a[href="/@10.111.113.2/users"]')
        b.wait_visible('a[href="/@10.111.113.2/users"]')
        b.click('a[href="/@10.111.113.2/users"]')
        b.enter_page("/users", host="10.111.113.2")
        b.wait_present("#accounts-list")
        b.wait_present("#accounts-list div.cockpit-account:first-child")
        b.click("#accounts-list div.cockpit-account:first-child")
        b.wait_text("#account-user-name", "admin")
        b.switch_to_top()
        b.wait_js_cond('window.location.pathname == "/cockpit-new/@10.111.113.2/users"')
        b.wait_js_cond('window.location.hash == "#/admin"')


        self.checkDirectLogin('/cockpit-new/');
        self.allow_hostkey_messages()

    def testExternalPage(self):
        b = self.browser
        m1 = self.machine
        m2 = self.machine2

        # Modify the terminals to be different on the two machines.
        m1.needs_writable_usr()
        m1.execute("gunzip -c /usr/share/cockpit/systemd/terminal.html.gz | sed -e 's|</body>|magic-m1-token</body>|' > /usr/share/cockpit/systemd/terminal.html")
        m2.needs_writable_usr()
        m2.execute("gunzip -c /usr/share/cockpit/systemd/terminal.html.gz | sed -e 's|</body>|magic-m2-token</body>|' > /usr/share/cockpit/systemd/terminal.html")

        self.login_and_go("/dashboard")
        add_machine(b, "10.111.113.2")

        b.leave_page()
        b.go("/@10.111.113.2/system/terminal")
        b.enter_page("/system/terminal", host="10.111.113.2")
        b.wait_present("body")
        b.wait_in_text("body", "magic-m2-token")

        b.leave_page()
        b.go("/@localhost/system/terminal")
        b.enter_page("/system/terminal")
        b.wait_present("body")
        b.wait_in_text("body", "magic-m1-token")

        self.allow_hostkey_messages()

    def testFrameNavigation(self):
        b = self.browser
        m2 = self.machine2

        m2_path = "/@10.111.113.2/playground/test"
        dashboard_path = "/dashboard"

        # Add a machine
        self.login_and_go(None)
        add_machine(b, "10.111.113.2")

        b.go(dashboard_path)
        b.enter_page("/dashboard")
        # Check the server image
        b.wait_present("#dashboard-hosts a[data-address='10.111.113.2']")
        b.wait_present("#dashboard-hosts a[data-address='10.111.113.2'] img")
        b.wait_attr("#dashboard-hosts a[data-address='10.111.113.2'] img", 'src', '../shell/images/server-small.png')
        b.switch_to_top()

        # Go to the path, remove the image
        b.go(m2_path)
        b.enter_page("/playground/test", "10.111.113.2")
        b.wait_present("img[src='hammer.gif']");
        b.wait_visible("img[src='hammer.gif']");
        b.click("img[src='hammer.gif']");
        b.switch_to_top()

        # kill admin, lock account
        m2.execute('passwd -l admin')
        kill_user_admin(m2)

        b.wait_present(".curtains-ct");
        b.wait_text(".curtains-ct h1", "Couldn't connect to the machine")
        self.assertEqual(b.text("#machine-reconnect"), "Reconnect")

        b.go(dashboard_path)
        b.enter_page("/dashboard")
        b.wait_present("#dashboard-hosts a[data-address='10.111.113.2'] img")
        b.wait_attr("#dashboard-hosts a[data-address='10.111.113.2'] img", 'src', '../shell/images/server-error.png')
        b.switch_to_top()

        # navigating there again will fail
        b.go(m2_path)
        b.wait_present(".curtains-ct");
        b.wait_text(".curtains-ct h1", "Couldn't connect to the machine")
        self.assertEqual(b.text("#machine-reconnect"), "Reconnect")
        self.assertTrue(b.is_present("#machine-reconnect:not(.disabled)"))

        # wait for dashboard to load
        b.go(dashboard_path)
        b.enter_page("/dashboard")
        b.wait_present("#dashboard-hosts a[data-address='10.111.113.2'] img")
        b.wait_attr("#dashboard-hosts a[data-address='10.111.113.2'] img", 'src', '../shell/images/server-error.png')
        b.switch_to_top()

        # renable admin
        m2.execute('passwd -u admin')

        # path should reconnect at this point
        b.reload()
        b.go(m2_path)
        b.enter_page("/playground/test", "10.111.113.2", reconnect=True)
        # image is back because it page was reloaded after disconnection
        b.wait_present("img[src='hammer.gif']");
        b.switch_to_top()

        # Dashboard shows it as up
        b.go(dashboard_path)
        b.enter_page("/dashboard")
        b.wait_present("#dashboard-hosts a[data-address='10.111.113.2'] img")
        b.wait_attr("#dashboard-hosts a[data-address='10.111.113.2'] img", 'src', '../shell/images/server-small.png')
        b.switch_to_top()

        # Bad host also bounces
        b.go("/@10.0.0.0/playground/test")
        b.wait_present(".curtains-ct");
        b.wait_text(".curtains-ct h1", "Couldn't connect to the machine")
        self.assertEqual(b.text(".curtains-ct p"), "Cannot connect to an unknown machine")

        self.allow_restart_journal_messages()
        self.allow_hostkey_messages()
        # Might happen when killing the bridge.
        self.allow_journal_messages("localhost: dropping message while waiting for child to exit",
                                    "Received message for unknown channel: .*",
                                    '.*: Socket error: disconnected',
                                    ".*: error reading from ssh",
                                    ".*: bridge failed: .*",
                                    ".*: bridge program failed: Child process exited with code .*",
                                    "/playground/test.html: failed to retrieve resource: authentication-failed")

    def testFrameReload(self):
        b = self.browser

        frame = "cockpit1:10.111.113.2/playground/test"
        m2_path = "/@10.111.113.2/playground/test"

        # Add a machine
        self.login_and_go(None)
        add_machine(b, "10.111.113.2")

        b.switch_to_top()
        b.go(m2_path)
        b.enter_page("/playground/test", "10.111.113.2")

        b.wait_present('#file-content')
        b.wait_text('#file-content', "0")
        b.click("#modify-file")
        b.wait_text('#file-content', "1")


        # load the same page on m1
        b.switch_to_top()
        b.go("/@localhost/playground/test")
        b.enter_page("/playground/test")
        b.wait_present('#file-content')
        b.wait_text('#file-content', "0")

        # go back to m2 and reload frame.
        b.switch_to_top()
        b.go(m2_path)
        b.enter_page("/playground/test", "10.111.113.2")
        b.wait_text('#file-content', "1")
        b.switch_to_top()

        b.eval_js('ph_set_attr("iframe[name=\'%s\']", "data-ready", null)' % frame)
        b.eval_js('ph_set_attr("iframe[name=\'%s\']", "src", "../playground/test.html?i=1#/")' % frame)
        b.wait_present("iframe.container-frame[name='%s'][data-ready]" % frame)

        b.enter_page("/playground/test", "10.111.113.2")

        b.wait_present('#file-content')
        b.wait_text('#file-content', "1")

        self.allow_hostkey_messages()

    def testTroubleshooting(self):
        b = self.browser
        m1 = self.machine
        m2 = self.machine2

        machine_path = "/@10.111.113.2"

        self.login_and_go(None)

        # Troubleshoot while adding
        b.go(machine_path)

        # Bad hostkey
        break_hostkey(m1, "10.111.113.2")
        start_machine_troubleshoot(b, True, True)
        b.wait_present("#troubleshoot-dialog .btn-default:not([disabled])")
        b.wait_in_text('#troubleshoot-dialog .btn-default', "Close")
        b.wait_in_text('#troubleshoot-dialog h4', "Incorrect Host Key")
        b.wait_present("#troubleshoot-dialog pre")
        b.wait_in_text("#troubleshoot-dialog pre", "10.111.113.2")
        b.wait_in_text("#troubleshoot-dialog pre", "ssh-keygen")
        b.wait_in_text("#troubleshoot-dialog pre", "/etc/ssh/ssh_known_hosts")
        b.click("#troubleshoot-dialog .btn-default")
        b.wait_popdown('troubleshoot-dialog')
        fix_hostkey(m1)

        # Host key path is correct
        m1.execute("mkdir -p /home/admin/.ssh/")
        break_hostkey(m1, "10.111.113.2", filename="/home/admin/.ssh/known_hosts")

        start_machine_troubleshoot(b, True, True)
        b.wait_present("#troubleshoot-dialog .btn-default:not([disabled])")
        b.wait_in_text('#troubleshoot-dialog .btn-default', "Close")
        b.wait_in_text('#troubleshoot-dialog h4', "Incorrect Host Key")
        b.wait_present("#troubleshoot-dialog pre")
        b.wait_in_text("#troubleshoot-dialog pre", "10.111.113.2")
        b.wait_in_text("#troubleshoot-dialog pre", "ssh-keygen")
        if self.machine.image not in ["rhel-7-4"]:
            # only fixed in 141 (PR #6720)
            b.wait_in_text("#troubleshoot-dialog pre", "/home/admin/.ssh/known_hosts")
        b.click("#troubleshoot-dialog .btn-default")
        b.wait_popdown('troubleshoot-dialog')
        fix_hostkey(m1, filename="/home/admin/.ssh/known_hosts")

        # Bad cockpit
        break_bridge(m2)
        start_machine_troubleshoot(b, True)
        check_failed_state(b, "Cockpit is not installed")
        fix_bridge(m2)

        # Troubleshoot existing
        # Properly add machine
        fix_hostkey(m1)
        add_machine(b, "10.111.113.2")
        b.logout()
        b.wait_visible("#login")

        # Bad cockpit
        break_bridge(m2)
        self.login_and_go(None)
        b.go(machine_path)
        with b.wait_timeout(240):
            start_machine_troubleshoot(b)

        check_failed_state(b, "Cockpit is not installed")
        b.wait_visible("#machine-reconnect")
        fix_bridge(m2)

        # Clear host key
        fix_hostkey(m1)
        b.click("#machine-reconnect")
        start_machine_troubleshoot(b)
        b.wait_in_text('#troubleshoot-dialog h4', "Unknown Host Key")
        b.wait_present("#troubleshoot-dialog .btn-primary:not([disabled])")
        b.wait_in_text('#troubleshoot-dialog .btn-primary', "Connect")
        b.click('#troubleshoot-dialog .btn-primary')
        b.wait_popdown('troubleshoot-dialog')

        # Reconnect
        b.wait_not_visible(".curtains-ct")
        b.enter_page('/system', "10.111.113.2")

        b.logout()
        b.wait_visible("#login")

        # Break auth
        m2.execute("echo admin:alt-password | chpasswd")
        self.login_and_go(None)
        b.go(machine_path)

        with b.wait_timeout(120):
            b.wait_visible("#machine-reconnect")
        start_machine_troubleshoot(b)

        b.wait_present('#troubleshoot-dialog h4')
        b.wait_in_text('#troubleshoot-dialog h4', "Log in to")
        b.wait_present("#troubleshoot-dialog .btn-primary:not([disabled])")
        b.wait_visible("#login-diff-password")
        b.wait_not_visible("#login-available")
        self.assertEqual(b.text("#login-type button span"), "Type a password")
        b.click("#login-type button");
        b.click("#login-type li[value=stored] a");
        b.wait_in_text("#login-type button span", "Using available credentials");
        b.wait_not_visible("#login-diff-password")
        b.wait_visible("#login-available")
        b.wait_in_text("#login-available", "Login Password")
        if m1.image != "rhel-7-4":
            b.wait_not_in_text("#login-available", "Kerberos Ticket")
            b.wait_not_present("#login-available .empty")

        fail_login(b)

        b.click("#login-type button");
        b.click("#login-type li[value=password] a");
        b.wait_in_text("#login-type button span", "Type a password");
        b.wait_visible("#login-diff-password")
        b.wait_not_visible("#login-available")
        self.assertEqual(b.val("#login-custom-password"), "")
        self.assertEqual(b.val("#login-custom-user"), "")
        self.assertEqual(b.attr("#login-custom-user", "placeholder"), "admin")
        b.set_val("#login-custom-user", "admin")
        b.set_val("#login-custom-password", "bad")
        fail_login(b)
        b.set_val("#login-custom-password", "alt-password")
        b.click('#troubleshoot-dialog .btn-primary')
        b.wait_popdown('troubleshoot-dialog')

        # Reconnect
        b.wait_not_visible(".curtains-ct")
        b.enter_page('/system', "admin@10.111.113.2")
        b.logout()
        b.wait_visible("#login")

        m1.execute("! selinuxenabled || semanage port -a -t ssh_port_t -p tcp 2222")

        # Keep in mind that not all operating systems have firewalld
        m2.execute("firewall-cmd --permanent --zone=public --add-port=2222/tcp || true")
        m2.execute("firewall-cmd --reload || true")
        m2.execute("! selinuxenabled || semanage port -a -t ssh_port_t -p tcp 2222")
        m2.execute("sed -i 's/.*Port .*/#\\0/' /etc/ssh/sshd_config")
        m2.execute("printf 'ListenAddress 127.27.0.15:22\nListenAddress 10.111.113.2:2222\n' >> /etc/ssh/sshd_config")

        # We stop the sshd.socket unit and just go with a regular
        # daemon.  This is more portable and reloading/restarting the
        # socket doesn't seem to work well.
        m2.execute("( ! systemctl is-active sshd.socket || systemctl stop sshd.socket) && systemctl restart sshd.service")
        m2.disconnect()
        m2 = None # No more access to m2

        self.login_and_go(None)
        b.go(machine_path)
        with b.wait_timeout(120):
            b.wait_visible("#machine-reconnect")
        start_machine_troubleshoot(b)
        b.wait_in_text('#troubleshoot-dialog h4', "Could not contact")
        b.wait_present("#edit-machine-port")
        b.set_val("#edit-machine-port", "2222")
        b.click('#troubleshoot-dialog .btn-primary')
        b.wait_in_text('#troubleshoot-dialog h4', "Log in to")
        b.wait_visible("#login-diff-password")
        b.click("#login-type button")
        b.wait_visible("#login-type ul li[value='stored'] a")
        b.click("#login-type ul li[value='stored'] a")
        b.wait_not_visible("#login-diff-password")
        b.set_val("#login-custom-user", "root")
        b.click('#troubleshoot-dialog .btn-primary')
        b.wait_popdown('troubleshoot-dialog')

        b.wait_not_visible(".curtains-ct")
        b.enter_page('/system', "root@10.111.113.2:2222")
        b.logout()

        # Login with no saved password
        b.go(machine_path)
        b.wait_visible("#login")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "foobar")
        b.set_checked("#authorized-input", False)
        b.click('#login-button')
        b.expect_load()

        with b.wait_timeout(120):
            b.wait_visible("#machine-reconnect")
        b.click('#machine-troubleshoot')
        b.wait_popup('troubleshoot-dialog')
        b.wait_present('#troubleshoot-dialog h4')
        b.wait_in_text('#troubleshoot-dialog h4', "Log in to")
        b.wait_present("#login-diff-password")
        b.wait_not_visible("#login-available")
        self.assertEqual(b.text("#login-type button span"), "Type a password")
        b.click("#login-type button");
        b.click("#login-type li[value=stored] a");
        b.wait_in_text("#login-type button span", "Using available credentials");
        b.wait_not_visible("#login-diff-password")
        b.wait_visible("#login-available")

        if m1.image != "rhel-7-4":
            b.wait_not_in_text("#login-available", "Login Password")
            b.wait_not_in_text("#login-available", "Kerberos Ticket")
            b.wait_present("#login-available .empty")
            b.wait_visible("#login-available .empty")

        self.allow_hostkey_messages()
        self.allow_journal_messages('.* couldn\'t connect: .*',
                                    '.*: Connection reset by peer',
                                    '.* failed to retrieve resource: invalid-hostkey',
                                    '.* host key for server has changed to: .*',
                                    '.* spawning remote bridge failed .*',
                                    '.*: bridge failed: .*',
                                    '.*: received truncated .*',
                                    '.*: Socket error: disconnected',
                                    '.*: host key for this server changed key type: .*',
                                    '.*: server offered unsupported authentication methods: .*')

if __name__ == '__main__':
    test_main()
