#!/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 subprocess
import time
import re

def break_hostkey(m, address):
    line = "{0} ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJqfgO2FPiix1n2sCJCXbaffwog1Vvi3zRdmcAxG//5T".format(address)
    m.execute("echo '{0}'> /var/lib/cockpit/known_hosts".format(line))

def fix_hostkey(m, key=None):
    if not key:
        key = '';

    m.execute("echo '{0}' > /var/lib/cockpit/known_hosts".format(key))

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, 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 .*/Port {0}/' /etc/ssh/sshd_config".format(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")
    machine.ssh_port = port

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

    raise error

@skipImage("No dashboard on Atomic", "continuous-atomic", "fedora-atomic", "rhel-atomic")
class TestMultiMachineAdd(MachineCase):
    additional_machines = {
        'machine2': { },
        'machine3': { }
    }

    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")

    def tearDown(self):
        if self.runner and self.runner.wasSuccessful():
            self.check_journal_messages(self.machine2)
            self.check_journal_messages(self.machine3)
        MachineCase.tearDown(self)

    def testBasic(self):
        b = self.browser
        m2 = self.machine2
        m3 = self.machine3

        self.login_and_go(None)
        add_machine(b, m2.address)
        add_machine(b, m3.address)

        # 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='%s'] img" % m2.address)
        b.wait_attr("#dashboard-hosts a[data-address='%s'] img" % m2.address, 'src', '../shell/images/server-error.png')

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

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

        b.click("#dashboard-hosts a[data-address='%s']" % m3.address)
        b.switch_to_top()
        b.wait_js_cond('window.location.pathname != "/dashboard"')
        b.enter_page("/system", host=m3.address)
        b.wait_text_not("#system_information_hostname_button", "")
        b.switch_to_top()
        b.go("/dashboard")
        b.enter_page("/dashboard")
        b.wait_attr("#dashboard-hosts a[data-address='%s'] img" % m3.address, '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 program failed: Child process exited with code .*")


@skipImage("No dashboard on Atomic", "continuous-atomic", "fedora-atomic", "rhel-atomic")
class TestMultiMachine(MachineCase):
    additional_machines = {
        'machine2': { }
    }

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

    def tearDown(self):
        if self.runner and self.runner.wasSuccessful():
            self.check_journal_messages(self.machine2)
            if self.machine3:
                self.check_journal_messages(self.machine3)
        MachineCase.tearDown(self)

    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='{}']".format(m2.address))

        # Direct to machine2, new login
        m2.execute("echo admin:alt-password | chpasswd")
        b.switch_to_top()
        b.open("{0}={1}".format(root, m2.address))
        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", m2.address)
        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}={1}/system"'.format(root, m2.address))

        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.address)
        b.wait_not_present("a[data-address='{}']".format(m2.address))
        b.logout()

        # Bad host key
        m.execute("echo '{} ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDgPMmTosSQ4NxMtq+aL2NKLC+W4I9/jbD1e74cnOKTW' > /var/lib/cockpit/known_hosts".format(m2.address))
        b.open("{0}={1}".format(root, m2.address))
        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 '' > /var/lib/cockpit/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}={1}".format(root, m2.address))
        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 /var/lib/cockpit/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")

        # Connect to machine2
        b.set_val("#server-field", m2.address)
        b.set_val("#login-password-input", "alt-password")
        b.click('#login-button')
        b.wait_in_text("#server-name", m2.address)
        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}={1}/system"'.format(root, m2.address))
        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.address)
        b.wait_not_present("a[data-address='{}']".format(m2.address))

        # 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
        m2 = self.machine2

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

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

        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, m2.address)
        b.enter_page("/system", host=m2.address)
        b.wait_text("#system_information_hostname_button", "machine2")
        b.switch_to_top()
        b.wait_js_cond('window.location.pathname == "/cockpit-new/@%s/system"' % m2.address)

        # Test subnav
        b.wait_visible('a[data-target="#tools-panel"]')
        b.click('a[data-target="#tools-panel"]')
        b.wait_present('a[href="/@{}/users"]'.format(m2.address))
        b.wait_visible('a[href="/@{}/users"]'.format(m2.address))
        b.click('a[href="/@{}/users"]'.format(m2.address))
        b.enter_page("/users", host=m2.address)
        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/@%s/users"' % m2.address)
        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, m2.address)

        b.leave_page()
        b.go("/@%s/system/terminal" % m2.address)
        b.enter_page("/system/terminal", host=m2.address)
        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 = "/@%s/playground/test"% m2.address
        dashboard_path = "/dashboard"

        # Add a machine
        self.login_and_go(None)
        add_machine(b, m2.address)

        b.go(dashboard_path)
        b.enter_page("/dashboard")
        # Check the server image
        b.wait_present("#dashboard-hosts a[data-address='%s']" % m2.address)
        b.wait_attr("#dashboard-hosts a[data-address='%s'] img" % m2.address, '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", m2.address)
        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_attr("#dashboard-hosts a[data-address='%s'] img" % m2.address, '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_attr("#dashboard-hosts a[data-address='%s'] img" % m2.address, '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", m2.address, 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_attr("#dashboard-hosts a[data-address='%s'] img" % m2.address, '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 program failed: Child process exited with code .*",
                                    "/playground/test.html: failed to retrieve resource: authentication-failed")

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

        frame = "cockpit1:%s/playground/test" % m2.address
        m2_path = "/@%s/playground/test" % m2.address

        # Add a machine
        self.login_and_go(None)
        add_machine(b, m2.address)

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

        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", m2.address)
        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", m2.address)

        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 = "/@%s" % m2.address

        self.login_and_go(None)

        # Troubleshoot while adding
        b.go(machine_path)

        # Bad hostkey
        break_hostkey(m1, m2.address)
        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", m2.address)
        b.wait_in_text("#troubleshoot-dialog pre", "ssh-keygen")
        b.click("#troubleshoot-dialog .btn-default")
        b.wait_popdown('troubleshoot-dialog')
        fix_hostkey(m1)

        # 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, m2.address)
        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', m2.address)

        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")
        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"), "")
        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@{0}".format(m2.address))
        b.logout()
        b.wait_visible("#login")

        m1.execute("! selinuxenabled || semanage port -a -t ssh_port_t -p tcp 2222")
        change_ssh_port(m2, 2222)
        m2.disconnect()
        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.set_val("#login-custom-password", "alt-password")
        b.click('#troubleshoot-dialog .btn-primary')
        b.wait_popdown('troubleshoot-dialog')

        b.wait_not_visible(".curtains-ct")
        b.enter_page('/system', "admin@{0}:2222".format(m2.address))

        self.allow_hostkey_messages()
        self.allow_journal_messages('.*: couldn\'t connect: Connection refused',
                                    '.* failed to retrieve resource: invalid-hostkey',
                                    '.* host key for server has changed to: .*',
                                    '.* spawning remote 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()
