Skip to content
This repository has been archived by the owner on Apr 28, 2020. It is now read-only.

Add L2 Network RDP connection #218

Merged
merged 2 commits into from
Feb 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sass/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
@import './components/Table/editable-table-actions';
@import './components/Table/editable-table';
@import './components/TemplateSource/template-source';
@import './components/VmConsoles/vm-consoles';
@import './components/VmConsoles/desktop-viewer-selector';
@import './components/VmDetails/vm-details';
@import './components/VmStatus/vm-status';
@import './components/Wizard/create-vm-wizard';
Expand Down
4 changes: 4 additions & 0 deletions sass/components/VmConsoles/desktop-viewer-selector.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.kubevirt-desktop-viewer-selector {
display: inline-block;
margin-top: 3em;
}
3 changes: 0 additions & 3 deletions sass/components/VmConsoles/vm-consoles.scss

This file was deleted.

135 changes: 135 additions & 0 deletions src/components/VmConsoles/DesktopViewerSelector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React from 'react';
import PropTypes from 'prop-types';
import { DesktopViewer } from '@patternfly/react-console';
import { Alert } from 'patternfly-react';
import { get } from 'lodash';

import { FormFactory } from '../Form';
import { settingsValue } from '../../k8s/selectors';
import { DEFAULT_RDP_PORT } from '../../constants';
import { RdpServiceNotConfigured } from './VmConsoles';
import {
SELECT_NETWORK_INTERFACE,
NIC,
GUEST_AGENT_WARNING,
NO_IP_ADDRESS,
RDP_NETWORK_INTERFACE_HELP,
} from './strings';
import { NIC_KEY } from './constants';
import { NETWORK_TYPE_MULTUS, NETWORK_TYPE_POD } from '../Wizard/CreateVmWizard/constants';
import { getNetworks } from '../../k8s/vmBuilder';

const getForm = networks => ({
[NIC_KEY]: {
id: 'nic-dropdown',
title: NIC,
type: 'dropdown',
defaultValue: SELECT_NETWORK_INTERFACE,
choices: networks,
help: RDP_NETWORK_INTERFACE_HELP,
},
});

const getVmRdpNetworks = (vm, vmi) => {
const networks = getNetworks(vm).filter(n => n.multus || n.pod);
return get(vmi, 'status.interfaces', [])
.filter(i => networks.some(n => n.name === i.name))
.map(i => {
let ip = i.ipAddress;
if (i.ipAddress) {
const subnetIndex = i.ipAddress.indexOf('/');
if (subnetIndex > 0) {
ip = i.ipAddress.slice(0, subnetIndex);
}
}
const network = networks.find(n => n.name === i.name);
return {
name: i.name,
type: get(network, 'multus') ? NETWORK_TYPE_MULTUS : NETWORK_TYPE_POD,
ip,
};
});
};

const getDefaultNetwork = networks => {
if (networks.length === 1) {
return networks[0];
}
if (networks.length > 1) {
return (
networks.find(n => n.type === NETWORK_TYPE_MULTUS && n.ipAddress) ||
networks.find(n => n.type === NETWORK_TYPE_MULTUS)
);
}
return null;
};

export class DesktopViewerSelector extends React.Component {
constructor(props) {
super(props);
const networks = getVmRdpNetworks(props.vm, props.vmi);
this.state = {
[NIC_KEY]: {
value: getDefaultNetwork(networks),
},
};
}

onFormChange = (newValue, target) => {
this.setState({
[target]: newValue,
});
};

render() {
const networks = getVmRdpNetworks(this.props.vm, this.props.vmi);
let content;
switch (get(settingsValue(this.state, NIC_KEY), 'type')) {
case NETWORK_TYPE_MULTUS:
if (!this.props.guestAgent) {
content = <Alert type="warning">{GUEST_AGENT_WARNING}</Alert>;
} else if (!settingsValue(this.state, NIC_KEY).ip) {
content = <Alert type="warning">{`${NO_IP_ADDRESS} ${settingsValue(this.state, NIC_KEY).name}`}</Alert>;
} else {
const rdp = {
address: settingsValue(this.state, NIC_KEY).ip,
port: DEFAULT_RDP_PORT,
};
content = <DesktopViewer rdp={rdp} />;
}
break;
case NETWORK_TYPE_POD:
content =
this.props.rdpServiceManual || this.props.vncServiceManual ? (
<DesktopViewer rdp={this.props.rdpServiceManual} vnc={this.props.vncServiceManual} />
) : (
<RdpServiceNotConfigured vm={this.props.vm} />
);
break;
default:
// eslint-disable-next-line no-console
console.warn(`Unknown network interface type ${get(settingsValue(this.state, NIC_KEY), 'type')}`);
}

return (
<div className="kubevirt-desktop-viewer-selector">
<FormFactory fields={getForm(networks)} fieldsValues={this.state} onFormChange={this.onFormChange} />
{content}
</div>
);
}
}

DesktopViewerSelector.defaultProps = {
guestAgent: false,
rdpServiceManual: null,
vncServiceManual: null,
};

DesktopViewerSelector.propTypes = {
vm: PropTypes.object.isRequired,
vmi: PropTypes.object.isRequired,
guestAgent: PropTypes.bool,
rdpServiceManual: PropTypes.object,
vncServiceManual: PropTypes.object,
};
94 changes: 46 additions & 48 deletions src/components/VmConsoles/VmConsoles.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import { get } from 'lodash';
import { AccessConsoles, VncConsole, DesktopViewer } from '@patternfly/react-console';
import { Button, ExpandCollapse } from 'patternfly-react';
import { AccessConsoles, VncConsole } from '@patternfly/react-console';
import { Button } from 'patternfly-react';

import { isVmiRunning, isVmStarting } from '../VmStatus';
import { SerialConsoleConnector } from './SerialConsoleConnector';
import { isWindows } from '../../utils';
import { isWindows, isGuestAgentConnected } from '../../utils';
import { DEFAULT_RDP_PORT, TEMPLATE_VM_NAME_LABEL } from '../../constants';
import { DesktopViewerSelector } from './DesktopViewerSelector';

const { VNC_CONSOLE_TYPE, SERIAL_CONSOLE_TYPE } = AccessConsoles.constants;

Expand Down Expand Up @@ -43,48 +44,37 @@ VmIsStarting.propTypes = {
LoadingComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
};

const RdpServiceNotConfigured = ({ vm }) => (
export const RdpServiceNotConfigured = ({ vm }) => (
<div className="kubevirt-vm-consoles__rdp">
<ExpandCollapse
bordered={false}
align={ExpandCollapse.ALIGN_CENTER}
textCollapsed="No RDP Service found"
textExpanded="No RDP Service found"
>
<div className="kubevirt-vm-consoles__rdp-content">
<span>
This is a Windows virtual machine but no Service for the RDP (Remote Desktop Protocol) can be found.
</span>
<br />
<span>
For better experience accessing Windows console, it is recommended to use the RDP. To do so, create a service:
<ul>
<li>
exposing the{' '}
<b>
{DEFAULT_RDP_PORT}
/tcp
</b>{' '}
port of the virtual machine
</li>
<li>
using selector:{' '}
<b>
{TEMPLATE_VM_NAME_LABEL}: {vm.metadata.name}
</b>
</li>
<li>
Example: virtctl expose virtualmachine {vm.metadata.name} --name {vm.metadata.name}
-rdp --port [UNIQUE_PORT] --target-port {DEFAULT_RDP_PORT} --type NodePort
</li>
</ul>
Make sure, the VM object has <b>spec.template.metadata.labels</b> set to{' '}
<span>This is a Windows virtual machine but no Service for the RDP (Remote Desktop Protocol) can be found.</span>
<br />
<span>
For better experience accessing Windows console, it is recommended to use the RDP. To do so, create a service:
<ul>
<li>
exposing the{' '}
<b>
{DEFAULT_RDP_PORT}
/tcp
</b>{' '}
port of the virtual machine
</li>
<li>
using selector:{' '}
<b>
{TEMPLATE_VM_NAME_LABEL}: {vm.metadata.name}
</b>
</span>
</div>
</ExpandCollapse>
</li>
<li>
Example: virtctl expose virtualmachine {vm.metadata.name} --name {vm.metadata.name}
-rdp --port [UNIQUE_PORT] --target-port {DEFAULT_RDP_PORT} --type NodePort
</li>
</ul>
Make sure, the VM object has <b>spec.template.metadata.labels</b> set to{' '}
<b>
{TEMPLATE_VM_NAME_LABEL}: {vm.metadata.name}
</b>
</span>
</div>
);

Expand All @@ -104,21 +94,29 @@ export const VmConsoles = ({ vm, vmi, onStartVm, vnc, serial, rdp, WSFactory, Lo
);
}

let infoMessage;
const vncManual = get({ vnc }, 'vnc.manual');
const rdpManual = get({ rdp }, 'rdp.manual');
const vncServiceManual = get({ vnc }, 'vnc.manual');
const rdpServiceManual = get({ rdp }, 'rdp.manual');

if (!rdpManual && isWindows(vm)) {
infoMessage = <RdpServiceNotConfigured vm={vm} />;
let desktopViewerSelector;
if (isWindows(vm)) {
desktopViewerSelector = (
<DesktopViewerSelector
vncServiceManual={vncServiceManual}
rdpServiceManual={rdpServiceManual}
type="DesktopViewer"
vm={vm}
vmi={vmi}
guestAgent={isGuestAgentConnected(vmi)}
/>
);
}

return (
<div className="co-m-pane__body">
{infoMessage}
<AccessConsoles preselectedType={VNC_CONSOLE_TYPE} disconnectByChange={false}>
<SerialConsoleConnector type={SERIAL_CONSOLE_TYPE} WSFactory={WSFactory} {...serial} />
<VncConsole {...vnc} />
{(vncManual || rdpManual) && <DesktopViewer vnc={vncManual} rdp={rdpManual} />}
{desktopViewerSelector}
</AccessConsoles>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/VmConsoles/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const NIC_KEY = 'nic';
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { DesktopViewerSelector } from '../DesktopViewerSelector';
import { fullVm } from '../../../tests/mocks/vm/vm.mock';

export default [
{
component: DesktopViewerSelector,
props: {
vm: fullVm,
vmi: {
status: {
interfaces: [{ name: 'eth0', ipAddress: '192.168.1.0' }],
},
},
},
},
];
5 changes: 5 additions & 0 deletions src/components/VmConsoles/strings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const SELECT_NETWORK_INTERFACE = '--- Select Network Interface ---';
export const NIC = 'Network Interface';
export const GUEST_AGENT_WARNING = 'Guest Agent is not installed on Virtual Machine';
export const NO_IP_ADDRESS = 'No IP address is reported for network interface';
export const RDP_NETWORK_INTERFACE_HELP = 'The network interface to be used for accessing the RDP console';
12 changes: 12 additions & 0 deletions src/components/VmConsoles/tests/DesktopViewerSelector.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { shallow } from 'enzyme';

import { DesktopViewerSelector } from '../DesktopViewerSelector';
import { default as desktopViewerSelectorFixture } from '../fixtures/DesktopViewerSelector.fixture';

describe('<DesktopViewerSelector />', () => {
it('renders correctly', () => {
const component = shallow(<DesktopViewerSelector {...desktopViewerSelectorFixture[0].props} />);
expect(component).toMatchSnapshot();
});
});
Loading