diff --git a/src/components/vm/consoles/vnc.jsx b/src/components/vm/consoles/vnc.jsx index 72c8dc9ca..7c3a781d7 100644 --- a/src/components/vm/consoles/vnc.jsx +++ b/src/components/vm/consoles/vnc.jsx @@ -73,12 +73,16 @@ class Vnc extends React.Component { cockpit.transport.wait(() => { const prefix = (new URL(cockpit.transport.uri("channel/" + cockpit.transport.csrf_token))).pathname; - const query = JSON.stringify({ + const data = { payload: "stream", binary: "raw", address: consoleDetail.address, port: parseInt(consoleDetail.tlsPort || consoleDetail.port, 10), - }); + }; + if (cockpit.transport.host !== "localhost") { + data.host = cockpit.transport.host; + } + const query = JSON.stringify(data); this.setState({ path: `${prefix.slice(1)}?${window.btoa(query)}`, }); diff --git a/test/browser/run-test.sh b/test/browser/run-test.sh index 92326cde6..93d888c6e 100755 --- a/test/browser/run-test.sh +++ b/test/browser/run-test.sh @@ -73,6 +73,8 @@ EXCLUDES="$EXCLUDES TestMachinesNICs.NICAddDialog TestMachinesSettings.testMultipleSettings + + TestMultiMachineVNC.testBasic " case "$PLAN" in diff --git a/test/check-machines-multi-host-consoles b/test/check-machines-multi-host-consoles new file mode 100755 index 000000000..ee24fb80f --- /dev/null +++ b/test/check-machines-multi-host-consoles @@ -0,0 +1,135 @@ +#!/usr/bin/python3 -cimport os, sys; os.execv(os.path.dirname(sys.argv[1]) + "/common/pywrap", sys.argv) + +# This file is part of Cockpit. +# +# Copyright (C) 2021 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 testlib +from machineslib import VirtualMachinesCase + + +def start_machine_troubleshoot(b, new=False, known_host=False, password=None): + b.wait_visible("#machine-troubleshoot") + b.click('#machine-troubleshoot') + b.wait_visible('#hosts_setup_server_dialog') + if new: + b.click('#hosts_setup_server_dialog button:contains(Add)') + if not known_host: + b.wait_in_text('#hosts_setup_server_dialog', "You are connecting to") + b.wait_in_text('#hosts_setup_server_dialog', "for the first time.") + b.click("#hosts_setup_server_dialog button:contains('Trust and add host')") + if password: + b.wait_in_text('#hosts_setup_server_dialog', "Unable to log in") + b.set_input_text('#login-custom-password', password) + b.click('#hosts_setup_server_dialog button:contains(Log in)') + + +def add_machine(b, address, known_host=False, password="foobar"): + b.switch_to_top() + b.go(f"/@{address}") + start_machine_troubleshoot(b, new=True, known_host=known_host, password=password) + b.wait_not_present('#hosts_setup_server_dialog') + b.enter_page("/system", host=address) + + +class TestMultiMachineVNC(VirtualMachinesCase): + """ + Test for showing the wrong VNC console when adding another host via the add + host feature the raw stream channel does not set the host option so would + connect to the console of machine you connect to instead of the newly added + host. + + https://issues.redhat.com/browse/RHEL-3959 + """ + + provision = { # noqa: RUF012 + "machine1": {"address": "10.111.113.1/20", "memory_mb": 660}, + "machine2": {"address": "10.111.113.2/20", "memory_mb": 660}, + } + + def setup_ssh_auth(self): + self.machine.execute("d=/home/admin/.ssh; mkdir -p $d; chown admin:admin $d; chmod 700 $d") + self.machine.execute("test -f /home/admin/.ssh/id_rsa || ssh-keygen -f /home/admin/.ssh/id_rsa -t rsa -N ''") + self.machine.execute("chown admin:admin /home/admin/.ssh/id_rsa*") + pubkey = self.machine.execute("cat /home/admin/.ssh/id_rsa.pub") + + for m in self.machines: + self.machines[m].execute("d=/home/admin/.ssh; mkdir -p $d; chown admin:admin $d; chmod 700 $d") + self.machines[m].write("/home/admin/.ssh/authorized_keys", pubkey) + self.machines[m].execute("chown admin:admin /home/admin/.ssh/authorized_keys") + + def setUp(self): + super().setUp() + self.machine1 = self.machines['machine1'] + self.machine2 = self.machines['machine2'] + + # Disable preloading on all machines + # Preloading on machines debug build can overload the browser and cause slowness and browser crashes, + # and failing to load sofware updates breaks pixel tests in release builds + self.setup_provisioned_hosts(disable_preload=True) + self.setup_ssh_auth() + + def testBasic(self): + b = self.browser + m = self.machine + m2 = self.machine2 + m2_host = "10.111.113.2" + + m.execute(f"ssh-keyscan {m2_host} > /etc/ssh/ssh_known_hosts") + self.startLibvirt(m2) + + # Start a VM with a VNC on machine1 + name = "subVmTest1" + self.createVm(name, graphics="vnc", ptyconsole=True) + + self.login_and_go("/machines") + self.waitPageInit() + + self.waitVmRow(name) + self.goToVmPage(name) + b.wait_in_text(f"#vm-{name}-system-state", "Running") + + add_machine(b, m2_host, known_host=True, password=None) + + b.switch_to_top() + b.click("#hosts-sel button") + + b.click(f"a[href='/@{m2_host}']") + + b.wait_js_cond(f'window.location.pathname == "/@{m2_host}/system"') + b.enter_page("/system", host=m2_host) + b.become_superuser() + + b.go(f"/@{m2_host}/machines") + b.enter_page("/machines", host=m2_host) + + self.createVm(name, graphics="vnc", ptyconsole=True, machine=m2) + self.waitVmRow("subVmTest1") + b.wait_in_text("#vm-subVmTest1-system-state", "Running") # running or paused + self.goToVmPage("subVmTest1") + + # Wait till we have a VNC connection + b.wait_visible(".pf-v5-c-console__vnc canvas") + + # Both machines should have the equal amount of VNC connections + m1_open_connections = m.execute("ss -tupn | grep 127.0.0.1:5900 | grep qemu").splitlines() + m2_open_connections = m2.execute("ss -tupn | grep 127.0.0.1:5900 | grep qemu").splitlines() + self.assertTrue(len(m1_open_connections) == 1) + self.assertTrue(len(m2_open_connections) == 1) + + +if __name__ == '__main__': + testlib.test_main() diff --git a/test/machineslib.py b/test/machineslib.py index 7a0854a38..67e6ce518 100644 --- a/test/machineslib.py +++ b/test/machineslib.py @@ -144,8 +144,11 @@ def startLibvirt(self, m): m.execute("virsh net-start default || true") m.execute(r"until virsh net-info default | grep 'Active:\s*yes'; do sleep 1; done") - def createVm(self, name, graphics='none', ptyconsole=False, running=True, memory=128, connection='system'): - m = self.machine + def createVm(self, name, graphics='none', ptyconsole=False, running=True, memory=128, connection='system', machine=None): + if machine is None: + m = self.machine + else: + m = machine image_file = m.pull("cirros") @@ -190,7 +193,7 @@ def createVm(self, name, graphics='none', ptyconsole=False, running=True, memory command.append( f'[ "$(virsh -c qemu:///{connection} domstate {name})" = {state} ] || \ {{ virsh -c qemu:///{connection} dominfo {name} >&2; cat /var/log/libvirt/qemu/{name}.log >&2; exit 1; }}') - self.run_admin("; ".join(command), connection) + self.run_admin("; ".join(command), connection, machine=machine) # TODO check if kernel is booted # Ideally we would like to check guest agent event for that @@ -228,8 +231,11 @@ def prepareStorageDeviceOnISCSI(self, target_iqn): self.addCleanup(m.execute, "targetcli /iscsi delete %s; iscsiadm -m node -o delete || true" % target_iqn) return orig_iqn - def run_admin(self, cmd, connectionName='system'): - m = self.machine + def run_admin(self, cmd, connectionName='system', machine=None): + if machine is None: + m = self.machine + else: + m = machine if connectionName == 'session': return m.execute(f"su - admin -c 'export XDG_RUNTIME_DIR=/run/user/$(id -u admin); {cmd}'")