#!/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 . import parent from testlib import * import subprocess import time import re 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, 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 change_ssh_port(m3, 2222) self.login_and_go(None) add_machine(b, m2.address) m3_con = m3.address + ":2222" add_machine(b, m3_con) # 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_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-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_con) 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='%s'] img" % m3.address) 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 failed: .*", ".*: 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']", "machine2") b.wait_not_present("a[data-address='{}']".format(m2.address)) 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}={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.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}={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.expect_load() b.logout() m.execute("rm /var/lib/cockpit/known_hosts") # Bad host key m.execute("echo '{} ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDgPMmTosSQ4NxMtq+aL2NKLC+W4I9/jbD1e74cnOKTW' > /etc/ssh/ssh_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 '' > /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}={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 /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") # 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']", "machine2") 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_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||magic-m1-token|' > /usr/share/cockpit/systemd/terminal.html") m2.needs_writable_usr() m2.execute("gunzip -c /usr/share/cockpit/systemd/terminal.html.gz | sed -e 's||magic-m2-token|' > /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_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-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_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') 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='%s'] img" % m2.address) 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_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-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 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.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, m2.address, 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", m2.address) b.wait_in_text("#troubleshoot-dialog pre", "ssh-keygen") 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, 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: .*', '.* 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()