Skip to content

Commit

Permalink
Add support for multiple pty consoles
Browse files Browse the repository at this point in the history
  • Loading branch information
KKoukiou committed Nov 10, 2021
1 parent 09624ef commit 8e76eea
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 25 deletions.
32 changes: 17 additions & 15 deletions src/components/vm/consoles/consoles.jsx
Expand Up @@ -58,10 +58,10 @@ class Consoles extends React.Component {
const { vm } = this.props;

if (vm.displays) {
if (vm.displays.vnc) {
if (vm.displays.find(display => display.type == "vnc")) {
return 'VncConsole';
}
if (vm.displays.spice) {
if (vm.displays.find(display => display.type == "spice")) {
return 'DesktopViewer';
}
}
Expand All @@ -78,20 +78,21 @@ class Consoles extends React.Component {
onDesktopConsoleDownload (type) {
const { vm } = this.props;
// fire download of the .vv file
domainDesktopConsole({ name: vm.name, id: vm.id, connectionName: vm.connectionName, consoleDetail: vm.displays[type] });
domainDesktopConsole({ name: vm.name, id: vm.id, connectionName: vm.connectionName, consoleDetail: vm.displays.find(display => display.type == type) });
}

render () {
const { vm, onAddErrorNotification } = this.props;
const spice = vm.displays && vm.displays.find(display => display.type == 'spice');
const serial = vm.displays && vm.displays.filter(display => display.type == 'pty');
const vnc = vm.displays && vm.displays.find(display => display.type == 'vnc');

if (!domainCanConsole || !domainCanConsole(vm.state)) {
return (<VmNotRunning />);
}

const serialConsoleCommand = domainSerialConsoleCommand({ vm });

const onDesktopConsole = () => { // prefer spice over vnc
this.onDesktopConsoleDownload(vm.displays.spice ? 'spice' : 'vnc');
this.onDesktopConsoleDownload(spice ? 'spice' : 'vnc');
};

return (
Expand All @@ -100,22 +101,23 @@ class Consoles extends React.Component {
textSerialConsole={_("Serial console")}
textVncConsole={_("VNC console")}
textDesktopViewerConsole={_("Desktop viewer")}>
{!!serialConsoleCommand &&
<SerialConsole type="SerialConsole"
connectionName={vm.connectionName}
vmName={vm.name}
spawnArgs={serialConsoleCommand} />}
{vm.displays && vm.displays.vnc &&
{serial.map((pty, idx) => <SerialConsole type={serial.length == 1 ? "SerialConsole" : cockpit.format(_("Serial console ($0)"), pty.alias || idx)}
key={"pty-" + idx}
connectionName={vm.connectionName}
vmName={vm.name}
spawnArgs={domainSerialConsoleCommand({ vm, alias: pty.alias })} />)}
{vnc &&
<Vnc type="VncConsole"
vmName={vm.name}
vmId={vm.id}
connectionName={vm.connectionName}
consoleDetail={vm.displays.vnc}
consoleDetail={vnc}
onAddErrorNotification={onAddErrorNotification} />}
{vm.displays && (vm.displays.vnc || vm.displays.spice) &&
{(vnc || spice) &&
<DesktopConsole type="DesktopViewer"
onDesktopConsole={onDesktopConsole}
displays={vm.displays} />}
vnc={vnc}
spice={spice} />}
</AccessConsoles>
);
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/vm/consoles/desktopConsole.jsx
Expand Up @@ -36,10 +36,10 @@ function fmt_to_fragments(fmt) {
return React.createElement.apply(null, [React.Fragment, { }].concat(fmt.split(/(\$[0-9]+)/g).map(replace)));
}

const DesktopConsoleDownload = ({ displays, onDesktopConsole }) => {
const DesktopConsoleDownload = ({ vnc, spice, onDesktopConsole }) => {
return (
<DesktopViewer spice={displays.spice}
vnc={displays.vnc}
<DesktopViewer spice={spice}
vnc={vnc}
onDownload={onDesktopConsole}
textManualConnection={_("Manual connection")}
textNoProtocol={_("No connection available")}
Expand Down
9 changes: 4 additions & 5 deletions src/libvirt-xml-parse.js
Expand Up @@ -274,7 +274,7 @@ export function parseDumpxmlForCpu(cpuElem) {
}

export function parseDumpxmlForConsoles(devicesElem) {
const displays = {};
const displays = [];
const graphicsElems = devicesElem.getElementsByTagName("graphics");
if (graphicsElems) {
for (let i = 0; i < graphicsElems.length; i++) {
Expand All @@ -290,7 +290,7 @@ export function parseDumpxmlForConsoles(devicesElem) {
if (display.type &&
(display.autoport ||
(display.address && (display.port || display.tlsPort)))) {
displays[display.type] = display;
displays.push(display);
logDebug(`parseDumpxmlForConsoles(): graphics device found: ${JSON.stringify(display)}`);
} else {
console.warn(`parseDumpxmlForConsoles(): mandatory properties are missing in dumpxml, found: ${JSON.stringify(display)}`);
Expand All @@ -304,9 +304,8 @@ export function parseDumpxmlForConsoles(devicesElem) {
for (let i = 0; i < consoleElems.length; i++) {
const consoleElem = consoleElems[i];
if (consoleElem.getAttribute('type') === 'pty') {
// Definition of serial console is detected.
// So far no additional details needs to be parsed since the console is accessed via 'virsh console'.
displays.pty = {};
const aliasElem = getSingleOptionalElem(consoleElem, 'alias');
displays.push({ type: 'pty', alias: aliasElem ? aliasElem.getAttribute('name') : undefined });
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/libvirtApi/domain.js
Expand Up @@ -83,7 +83,12 @@ export const domainCanPause = (vmState) => vmState == 'running';
export const domainCanRename = (vmState) => vmState == 'shut off';
export const domainCanResume = (vmState) => vmState == 'paused';
export const domainIsRunning = (vmState) => domainCanReset(vmState);
export const domainSerialConsoleCommand = ({ vm }) => vm.displays.pty ? ['virsh', ...VMS_CONFIG.Virsh.connections[vm.connectionName].params, 'console', vm.name] : false;
export const domainSerialConsoleCommand = ({ vm, alias }) => {
if (vm.displays.find(display => display.type == 'pty'))
return ['virsh', ...VMS_CONFIG.Virsh.connections[vm.connectionName].params, 'console', vm.name, alias || ''];
else
return false;
};

let pythonPath;

Expand Down
3 changes: 2 additions & 1 deletion src/libvirtUtils.js
Expand Up @@ -52,12 +52,13 @@ function prepareParamsFromObjOfObjs(objectData, valueTransformer) {
}

export function prepareDisplaysParam(displays) {
return prepareParamsFromObjOfObjs(displays, display => {
return prepareParamsFromArrOfObjs(displays.filter(display => ["vnc", "spice"].includes(display.type)), display => {
return {
type: display.type,
listen: display.address,
port: display.port,
tlsport: display.tlsPort,
alias: display.alias,
};
});
}
Expand Down
12 changes: 12 additions & 0 deletions test/check-machines-consoles
Expand Up @@ -103,6 +103,7 @@ class TestMachinesConsoles(VirtualMachinesCase):
@no_retry_when_changed
def testSerialConsole(self):
b = self.browser
m = self.machine
name = "vmWithSerialConsole"

self.createVm(name, graphics='vnc', ptyconsole=True)
Expand Down Expand Up @@ -130,10 +131,21 @@ class TestMachinesConsoles(VirtualMachinesCase):
b.click("button:contains(Expand)")
b.assert_pixels("#vm-vmWithSerialConsole-consoles-page", "vm-details-console-serial")

# Add a second serial console
m.execute("virsh destroy vmWithSerialConsole && virt-xml --add-device vmWithSerialConsole --console pty,target_type=virtio && virsh start vmWithSerialConsole")
b.click("#pf-c-console__type-selector")
b.wait_visible("#pf-c-console__type-selector + .pf-c-select__menu")
b.click("li:contains('Serial console (console0)') button")
b.wait(lambda: m.execute("ps aux | grep 'virsh -c qemu:///system console vmWithSerialConsole console0'"))
b.click("#pf-c-console__type-selector")
b.click("li:contains('Serial console (console1)') button")
b.wait(lambda: m.execute("ps aux | grep 'virsh -c qemu:///system console vmWithSerialConsole console1'"))

# disconnecting the serial console closes the pty channel
self.allow_journal_messages("connection unexpectedly closed by peer",
".*Connection reset by peer")
self.allow_browser_errors("Disconnection timed out.")
self.allow_journal_messages(".* couldn't shutdown fd: Transport endpoint is not connected")

def testSwitchConsoleFromSerialToGraphical(self):
b = self.browser
Expand Down

0 comments on commit 8e76eea

Please sign in to comment.