diff --git a/src/components/CreateDeviceRow/CreateDeviceRow.js b/src/components/CreateDeviceRow/CreateDeviceRow.js
index c09c6d5c5..2531d6bac 100644
--- a/src/components/CreateDeviceRow/CreateDeviceRow.js
+++ b/src/components/CreateDeviceRow/CreateDeviceRow.js
@@ -17,9 +17,9 @@ export class CreateDeviceRow extends React.Component {
};
onFormChange = (newValue, key, valid) => {
- this.props.onChange(newValue, key);
+ const changeValid = this.props.onChange(newValue, key, valid);
this.setState({
- valid,
+ valid: typeof changeValid === 'boolean' ? changeValid : valid,
});
};
@@ -46,5 +46,5 @@ CreateDeviceRow.propTypes = {
onCancel: PropTypes.func.isRequired,
LoadingComponent: PropTypes.func.isRequired,
deviceFields: PropTypes.object.isRequired,
- columnSizes: PropTypes.object.isRequired,
+ columnSizes: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
};
diff --git a/src/components/CreateDeviceRow/tests/CreateDeviceRow.test.js b/src/components/CreateDeviceRow/tests/CreateDeviceRow.test.js
index c401fbdd0..79778351b 100644
--- a/src/components/CreateDeviceRow/tests/CreateDeviceRow.test.js
+++ b/src/components/CreateDeviceRow/tests/CreateDeviceRow.test.js
@@ -1,9 +1,11 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { shallow, mount } from 'enzyme';
import { CreateDeviceRow } from '..';
import { default as CreateDeviceRowFixture } from '../fixtures/CreateDeviceRow.fixture';
+import { setInput } from '../../../tests/enzyme';
+import { Text } from '../../Form';
const testCreateDeviceRow = () => ;
@@ -12,4 +14,17 @@ describe('', () => {
const component = shallow(testCreateDeviceRow());
expect(component).toMatchSnapshot();
});
+
+ it('uses valid value from onChange', () => {
+ const onChange = value => value.value === 'truthyValue';
+ const component = mount();
+
+ setInput(component.find('#device-name').find(Text), 'truthyValue');
+ component.update();
+ expect(component.state('valid')).toBeTruthy();
+
+ setInput(component.find('#device-name').find(Text), 'otherValue');
+ component.update();
+ expect(component.state('valid')).toBeFalsy();
+ });
});
diff --git a/src/components/CreateNicRow/CreateNicRow.js b/src/components/CreateNicRow/CreateNicRow.js
index 90416f285..c305e0661 100644
--- a/src/components/CreateNicRow/CreateNicRow.js
+++ b/src/components/CreateNicRow/CreateNicRow.js
@@ -4,21 +4,37 @@ import { get } from 'lodash';
import { CreateDeviceRow } from '../CreateDeviceRow';
import { getName, getNetworks, getInterfaces } from '../../selectors';
-import { validateDNS1123SubdomainValue } from '../../utils/validations';
+import { validateDNS1123SubdomainValue, validateNetworkBinding } from '../../utils/validations';
+import { getNetworkBindings } from '../../utils/utils';
import { POD_NETWORK } from '../../constants';
-import { HEADER_NAME, HEADER_MAC, SELECT_NETWORK } from '../Wizard/CreateVmWizard/strings';
+import {
+ HEADER_NAME,
+ HEADER_MAC,
+ SELECT_NETWORK,
+ HEADER_BINDING_METHOD,
+ SELECT_BINDING,
+} from '../Wizard/CreateVmWizard/strings';
import { NETWORK_TYPE_POD, NETWORK_TYPE_MULTUS, NAME_KEY } from '../Wizard/CreateVmWizard/constants';
import { Loading } from '../Loading';
import { settingsValue } from '../../k8s/selectors';
import { DROPDOWN, CUSTOM, LABEL } from '../Form';
-const columnSizes = {
+const mainColumnSize = {
lg: 3,
md: 3,
sm: 3,
xs: 3,
};
+const otherColumnSize = {
+ lg: 2,
+ md: 2,
+ sm: 2,
+ xs: 2,
+};
+
+const columnSizes = [mainColumnSize, mainColumnSize, otherColumnSize, otherColumnSize, otherColumnSize];
+
const getUsedNetworks = vm => {
const interfaces = getInterfaces(vm);
const networks = getNetworks(vm);
@@ -61,6 +77,7 @@ const getNetworkChoices = (vm, networks) => {
};
const getNicColumns = (nic, networks, LoadingComponent) => {
+ const bindingChoices = getNetworkBindings(get(settingsValue(nic, 'network'), 'networkType'));
let network;
if (networks) {
const networkChoices = getNetworkChoices(nic.vm, networks);
@@ -71,6 +88,7 @@ const getNicColumns = (nic, networks, LoadingComponent) => {
choices: networkChoices,
disabled: nic.creating || networkChoices.length === 0,
required: true,
+ title: 'Network',
};
} else {
network = {
@@ -94,6 +112,20 @@ const getNicColumns = (nic, networks, LoadingComponent) => {
type: LABEL,
},
network,
+ binding: {
+ id: 'binding',
+ type: DROPDOWN,
+ defaultValue: SELECT_BINDING,
+ title: HEADER_BINDING_METHOD,
+ required: true,
+ disabled: nic.creating,
+ choices: bindingChoices,
+ validate: settings =>
+ validateNetworkBinding(
+ get(settingsValue(settings, 'network'), 'networkType'),
+ settingsValue(settings, 'binding')
+ ),
+ },
mac: {
id: 'mac-address',
title: HEADER_MAC,
@@ -102,25 +134,44 @@ const getNicColumns = (nic, networks, LoadingComponent) => {
};
};
-const onFormChange = (newValue, key, onChange) => {
- if (key === 'network' && get(newValue, 'value.networkType') === NETWORK_TYPE_POD) {
- // reset mac value
- onChange({ value: '' }, 'mac');
+const onFormChange = (nic, formFields, newValue, key, valid, onChange) => {
+ let changeValid = valid;
+ if (key === 'network') {
+ const newNic = {
+ ...nic,
+ [key]: newValue,
+ };
+ const validation = formFields.binding.validate(newNic);
+ if (validation && validation.message) {
+ changeValid = false;
+ validation.message = `Network ${validation.message}`;
+ }
+ onChange({ value: settingsValue(nic, 'binding'), validation }, 'binding');
+
+ if (get(newValue, 'value.networkType') === NETWORK_TYPE_POD) {
+ // reset mac value
+ onChange({ value: '' }, 'mac');
+ }
}
+
onChange(newValue, key);
+ return changeValid;
};
-export const CreateNicRow = ({ nic, onAccept, onCancel, onChange, networks, LoadingComponent }) => (
- onFormChange(newValue, key, onChange)}
- device={nic}
- LoadingComponent={LoadingComponent}
- columnSizes={columnSizes}
- deviceFields={getNicColumns(nic, networks, LoadingComponent)}
- />
-);
+export const CreateNicRow = ({ nic, onAccept, onCancel, onChange, networks, LoadingComponent }) => {
+ const fields = getNicColumns(nic, networks, LoadingComponent);
+ return (
+ onFormChange(nic, fields, newValue, key, valid, onChange)}
+ device={nic}
+ LoadingComponent={LoadingComponent}
+ columnSizes={columnSizes}
+ deviceFields={fields}
+ />
+ );
+};
CreateNicRow.propTypes = {
nic: PropTypes.object.isRequired,
diff --git a/src/components/CreateNicRow/tests/__snapshots__/CreateNicRow.test.js.snap b/src/components/CreateNicRow/tests/__snapshots__/CreateNicRow.test.js.snap
index e38fc4813..beac8353d 100644
--- a/src/components/CreateNicRow/tests/__snapshots__/CreateNicRow.test.js.snap
+++ b/src/components/CreateNicRow/tests/__snapshots__/CreateNicRow.test.js.snap
@@ -4,16 +4,56 @@ exports[` renders correctly 1`] = `
renders correctly 1`] = `
"disabled": false,
"id": "network-type",
"required": true,
+ "title": "Network",
"type": "dropdown",
},
}
diff --git a/src/components/Form/FormFactory.js b/src/components/Form/FormFactory.js
index 376901854..7a92643f0 100644
--- a/src/components/Form/FormFactory.js
+++ b/src/components/Form/FormFactory.js
@@ -270,7 +270,7 @@ export const ListFormFactory = ({ fields, fieldsValues, onFormChange, actions, c
const form = formGroups.map((formGroup, index) => (
{
const errors = Array(4).fill(null);
@@ -106,6 +110,7 @@ const resolveInitialNetworks = (networks, networkConfigs, namespace, sourceType)
editable: true,
edit: false,
networkType,
+ binding: getInterfaceBinding(templateNetwork.interface),
};
}
return {
@@ -138,7 +143,7 @@ export class NetworksTab extends React.Component {
publishResults = rows => {
let valid = this.props.sourceType === PROVISION_SOURCE_PXE ? rows.some(row => row.isBootable) : true;
const nics = rows.map(
- ({ templateNetwork, rootNetwork, id, isBootable, name, mac, network, errors, networkType }) => {
+ ({ templateNetwork, rootNetwork, id, isBootable, name, mac, network, errors, networkType, binding }) => {
const result = {
id,
isBootable,
@@ -148,6 +153,7 @@ export class NetworksTab extends React.Component {
errors,
networkType,
rootNetwork,
+ binding,
};
if (templateNetwork) {
@@ -206,6 +212,7 @@ export class NetworksTab extends React.Component {
name: `nic${state.nextId - 1}`,
mac: '',
network: '',
+ binding: '',
},
],
}));
@@ -218,7 +225,7 @@ export class NetworksTab extends React.Component {
label: HEADER_NAME,
props: {
style: {
- width: '32%',
+ width: '24%',
},
},
},
@@ -233,7 +240,7 @@ export class NetworksTab extends React.Component {
label: HEADER_MAC,
props: {
style: {
- width: '32%',
+ width: '24%',
},
},
},
@@ -251,7 +258,7 @@ export class NetworksTab extends React.Component {
label: HEADER_NETWORK,
props: {
style: {
- width: '32%',
+ width: '24%',
},
},
},
@@ -267,6 +274,23 @@ export class NetworksTab extends React.Component {
initialValue: SELECT_NETWORK,
}),
},
+ {
+ header: {
+ label: HEADER_BINDING_METHOD,
+ props: {
+ style: {
+ width: '24%',
+ },
+ },
+ },
+ property: 'binding',
+ renderConfig: nic => ({
+ id: 'binding-edit',
+ type: DROPDOWN,
+ choices: getNetworkBindings(nic.networkType),
+ initialValue: SELECT_BINDING,
+ }),
+ },
];
if (!this.props.isCreateRemoveDisabled) {
diff --git a/src/components/Wizard/CreateVmWizard/constants.js b/src/components/Wizard/CreateVmWizard/constants.js
index 0c3941781..db82e2901 100644
--- a/src/components/Wizard/CreateVmWizard/constants.js
+++ b/src/components/Wizard/CreateVmWizard/constants.js
@@ -30,6 +30,9 @@ export const BATCH_CHANGES_KEY = 'internalBatchChanges';
// NetworksTab
export const NETWORK_TYPE_MULTUS = 'multus';
export const NETWORK_TYPE_POD = 'pod';
+export const NETWORK_BINDING_MASQUERADE = 'masquerade';
+export const NETWORK_BINDING_BRIDGE = 'bridge';
+export const NETWORK_BINDING_SRIOV = 'sriov';
// StorageTab
export const STORAGE_TYPE_PVC = 'pvc';
diff --git a/src/components/Wizard/CreateVmWizard/strings.js b/src/components/Wizard/CreateVmWizard/strings.js
index 30651a5c4..87e6fd0d9 100644
--- a/src/components/Wizard/CreateVmWizard/strings.js
+++ b/src/components/Wizard/CreateVmWizard/strings.js
@@ -43,6 +43,7 @@ export const getVmwareOsString = osName => `Select matching for: ${osName}`;
// NetworksTab
export const SELECT_NETWORK = '--- Select Network Definition ---';
+export const SELECT_BINDING = '--- Select binding ---';
export const REMOVE_NIC_BUTTON = 'Remove NIC';
export const CREATE_NIC_BUTTON = 'Create NIC';
export const PXE_INFO = 'Pod network is not PXE bootable';
@@ -53,6 +54,7 @@ export const HEADER_MAC = 'MAC Address';
export const HEADER_NETWORK = 'Network Configuration';
export const ERROR_NETWORK_NOT_FOUND = 'Network config not found';
export const ERROR_NETWORK_NOT_SELECTED = 'Network config must be selected';
+export const HEADER_BINDING_METHOD = 'Binding method';
// StorageTab
export const ERROR_NO_BOOTABLE_DISK = 'A bootable disk could not be found';
diff --git a/src/components/Wizard/CreateVmWizard/tests/__snapshots__/NetworksTab.test.js.snap b/src/components/Wizard/CreateVmWizard/tests/__snapshots__/NetworksTab.test.js.snap
index 2895f8697..7b9bc780c 100644
--- a/src/components/Wizard/CreateVmWizard/tests/__snapshots__/NetworksTab.test.js.snap
+++ b/src/components/Wizard/CreateVmWizard/tests/__snapshots__/NetworksTab.test.js.snap
@@ -21,7 +21,7 @@ exports[` renders correctly 1`] = `
"label": "Name",
"props": Object {
"style": Object {
- "width": "32%",
+ "width": "24%",
},
},
},
@@ -33,7 +33,7 @@ exports[` renders correctly 1`] = `
"label": "MAC Address",
"props": Object {
"style": Object {
- "width": "32%",
+ "width": "24%",
},
},
},
@@ -45,13 +45,25 @@ exports[` renders correctly 1`] = `
"label": "Network Configuration",
"props": Object {
"style": Object {
- "width": "32%",
+ "width": "24%",
},
},
},
"property": "network",
"renderConfig": [Function],
},
+ Object {
+ "header": Object {
+ "label": "Binding method",
+ "props": Object {
+ "style": Object {
+ "width": "24%",
+ },
+ },
+ },
+ "property": "binding",
+ "renderConfig": [Function],
+ },
Object {
"header": Object {
"props": Object {
diff --git a/src/k8s/vmBuilder.js b/src/k8s/vmBuilder.js
index b18b9de32..b08c6c793 100644
--- a/src/k8s/vmBuilder.js
+++ b/src/k8s/vmBuilder.js
@@ -16,6 +16,9 @@ import {
NETWORK_TYPE_POD,
DATA_VOLUME_SOURCE_URL,
DATA_VOLUME_SOURCE_BLANK,
+ NETWORK_BINDING_BRIDGE,
+ NETWORK_BINDING_MASQUERADE,
+ NETWORK_BINDING_SRIOV,
} from '../components/Wizard/CreateVmWizard/constants';
import { getCloudInitVolume } from '../selectors';
@@ -95,6 +98,7 @@ export const addInterface = (vm, defaultInterface, network) => {
...(network.templateNetwork ? network.templateNetwork.interface : defaultInterface),
name: network.name,
};
+
if (network.mac) {
interfaceSpec.macAddress = network.mac;
}
@@ -104,10 +108,33 @@ export const addInterface = (vm, defaultInterface, network) => {
interfaceSpec.bootOrder = interfaceSpec.bootOrder ? interfaceSpec.bootOrder : assignBootOrderIndex(vm);
}
+ addBindingToInterface(interfaceSpec, network.binding);
+
const interfaces = getInterfaces(vm);
interfaces.push(interfaceSpec);
};
+export const addBindingToInterface = (interfaceSpec, binding) => {
+ delete interfaceSpec.bridge;
+ delete interfaceSpec.masquerade;
+ delete interfaceSpec.sriov;
+
+ switch (binding) {
+ case NETWORK_BINDING_BRIDGE:
+ interfaceSpec.bridge = {};
+ break;
+ case NETWORK_BINDING_MASQUERADE:
+ interfaceSpec.masquerade = {};
+ break;
+ case NETWORK_BINDING_SRIOV:
+ interfaceSpec.sriov = {};
+ break;
+ default:
+ interfaceSpec.bridge = {};
+ break;
+ }
+};
+
export const addNetwork = (vm, network) => {
const networkSpec = {
...(network.templateNetwork ? network.templateNetwork.network : {}),
diff --git a/src/selectors/vm/selectors.js b/src/selectors/vm/selectors.js
index 6a2525355..b55949f40 100644
--- a/src/selectors/vm/selectors.js
+++ b/src/selectors/vm/selectors.js
@@ -11,6 +11,11 @@ import {
OS_WINDOWS_PREFIX,
TEMPLATE_OS_NAME_ANNOTATION,
} from '../../constants';
+import {
+ NETWORK_BINDING_BRIDGE,
+ NETWORK_BINDING_SRIOV,
+ NETWORK_BINDING_MASQUERADE,
+} from '../../components/Wizard/CreateVmWizard/constants';
export const getDisks = vm => get(vm, 'spec.template.spec.domain.devices.disks', []);
export const getInterfaces = vm => get(vm, 'spec.template.spec.domain.devices.interfaces', []);
@@ -69,3 +74,16 @@ export const getFlavorDescription = vm => {
export const isVmRunning = vm => get(vm, 'spec.running', false);
export const isVmReady = vm => get(vm, 'status.ready', false);
export const isVmCreated = vm => get(vm, 'status.created', false);
+
+export const getInterfaceBinding = intface => {
+ if (intface.bridge) {
+ return NETWORK_BINDING_BRIDGE;
+ }
+ if (intface.sriov) {
+ return NETWORK_BINDING_SRIOV;
+ }
+ if (intface.masquerade) {
+ return NETWORK_BINDING_MASQUERADE;
+ }
+ return null;
+};
diff --git a/src/utils/patches.js b/src/utils/patches.js
index e6f49baff..15940af74 100644
--- a/src/utils/patches.js
+++ b/src/utils/patches.js
@@ -12,7 +12,7 @@ import {
DEVICE_TYPE_INTERFACE,
} from '../constants';
import { NETWORK_TYPE_POD } from '../components/Wizard/CreateVmWizard/constants';
-import { assignBootOrderIndex, getBootableDevicesInOrder, getDevices } from '../k8s/vmBuilder';
+import { assignBootOrderIndex, getBootableDevicesInOrder, getDevices, addBindingToInterface } from '../k8s/vmBuilder';
export const getPxeBootPatch = vm => {
const patches = [];
@@ -315,13 +315,14 @@ export const getAddNicPatch = (vm, nic) => {
const i = {
name: nic.name,
model: nic.model,
- bridge: {},
bootOrder: assignBootOrderIndex(vm),
};
if (nic.mac) {
i.macAddress = nic.mac;
}
+ addBindingToInterface(i, nic.binding);
+
const network = {
name: nic.name,
};
diff --git a/src/utils/tests/validation.test.js b/src/utils/tests/validation.test.js
index db09378ff..c519e0137 100644
--- a/src/utils/tests/validation.test.js
+++ b/src/utils/tests/validation.test.js
@@ -5,6 +5,7 @@ import {
validateContainer,
getValidationObject,
validateVmwareURL,
+ validateNetworkBinding,
} from '../validations';
import {
DNS1123_START_ERROR,
@@ -17,6 +18,13 @@ import {
END_WHITESPACE_ERROR,
START_WHITESPACE_ERROR,
} from '../strings';
+import {
+ NETWORK_TYPE_POD,
+ NETWORK_BINDING_BRIDGE,
+ NETWORK_BINDING_MASQUERADE,
+ NETWORK_BINDING_SRIOV,
+ NETWORK_TYPE_MULTUS,
+} from '../../components/Wizard/CreateVmWizard/constants';
const validatesEmpty = validateFunction => {
expect(validateFunction('')).toEqual(getValidationObject(EMPTY_ERROR));
@@ -125,3 +133,26 @@ describe('validation.js - validateVmwareURL', () => {
expect(validateVmwareURL('http://hello.com ')).toEqual(getValidationObject(END_WHITESPACE_ERROR));
});
});
+
+describe('validation.js - validateNetworkBinding', () => {
+ it('validate pod network type bindings', () => {
+ expect(validateNetworkBinding(NETWORK_TYPE_POD, NETWORK_BINDING_BRIDGE)).toBeNull();
+ expect(validateNetworkBinding(NETWORK_TYPE_POD, NETWORK_BINDING_MASQUERADE)).toBeNull();
+ expect(validateNetworkBinding(NETWORK_TYPE_POD, NETWORK_BINDING_SRIOV)).toBeNull();
+ });
+ it('validate multus network type bindings', () => {
+ expect(validateNetworkBinding(NETWORK_TYPE_MULTUS, NETWORK_BINDING_BRIDGE)).toBeNull();
+ expect(validateNetworkBinding(NETWORK_TYPE_POD, NETWORK_BINDING_MASQUERADE)).toBeDefined();
+ expect(validateNetworkBinding(NETWORK_TYPE_POD, NETWORK_BINDING_SRIOV)).toBeNull();
+ });
+ it('validate null network type bindings', () => {
+ expect(validateNetworkBinding(null, NETWORK_BINDING_BRIDGE)).toBeNull();
+ expect(validateNetworkBinding(null, NETWORK_BINDING_MASQUERADE)).toBeNull();
+ expect(validateNetworkBinding(null, NETWORK_BINDING_SRIOV)).toBeNull();
+ });
+ it('validate empty binding', () => {
+ expect(validateNetworkBinding(null, null)).toBeNull();
+ expect(validateNetworkBinding(null, null)).toBeNull();
+ expect(validateNetworkBinding(null, null)).toBeNull();
+ });
+});
diff --git a/src/utils/utils.js b/src/utils/utils.js
index 0364ef0d3..3b3b1639c 100644
--- a/src/utils/utils.js
+++ b/src/utils/utils.js
@@ -1,5 +1,13 @@
import { NamespaceModel, ProjectModel } from '../models';
+import {
+ NETWORK_TYPE_POD,
+ NETWORK_TYPE_MULTUS,
+ NETWORK_BINDING_BRIDGE,
+ NETWORK_BINDING_SRIOV,
+ NETWORK_BINDING_MASQUERADE,
+} from '../components/Wizard/CreateVmWizard/constants';
+
export function prefixedId(idPrefix, id) {
return idPrefix && id ? `${idPrefix}-${id}` : null;
}
@@ -92,3 +100,13 @@ export const formatNetTraffic = (bytesPerSecond, preferredUnit, fixed = 2) => {
formatted.unit = `${formatted.unit}ps`;
return formatted;
};
+
+export const getNetworkBindings = networkType => {
+ switch (networkType) {
+ case NETWORK_TYPE_MULTUS:
+ return [NETWORK_BINDING_BRIDGE, NETWORK_BINDING_SRIOV];
+ case NETWORK_TYPE_POD:
+ default:
+ return [NETWORK_BINDING_MASQUERADE, NETWORK_BINDING_BRIDGE, NETWORK_BINDING_SRIOV];
+ }
+};
diff --git a/src/utils/validations.js b/src/utils/validations.js
index 26eac10dd..b27ece757 100644
--- a/src/utils/validations.js
+++ b/src/utils/validations.js
@@ -13,7 +13,7 @@ import {
END_WHITESPACE_ERROR,
} from './strings';
-import { parseUrl } from './utils';
+import { parseUrl, getNetworkBindings } from './utils';
import { VALIDATION_ERROR_TYPE } from '../constants';
@@ -107,3 +107,8 @@ export const validateVmwareURL = value => {
*/
return null;
};
+
+export const validateNetworkBinding = (networkType, networkBinding) =>
+ networkBinding && !getNetworkBindings(networkType).includes(networkBinding)
+ ? getValidationObject(`${networkType} cannot have ${networkBinding} binding`)
+ : null;