diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/create-vm-wizard-footer.tsx b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/create-vm-wizard-footer.tsx index f7341039c93..687d84fd794 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/create-vm-wizard-footer.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/create-vm-wizard-footer.tsx @@ -53,6 +53,7 @@ const CreateVMWizardFooterComponent: React.FC { const jumpToStepID = (isValid && diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/create-vm-wizard.tsx b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/create-vm-wizard.tsx index 24f15599862..3392b65c601 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/create-vm-wizard.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/create-vm-wizard.tsx @@ -33,12 +33,15 @@ import { } from '../../utils/immutable'; import { getTemplateOperatingSystems } from '../../selectors/vm-template/advanced'; import { ResultsWrapper } from '../../k8s/enhancedK8sMethods/types'; +import { NetworkWrapper } from '../../k8s/wrapper/vm/network-wrapper'; +import { NetworkInterfaceWrapper } from '../../k8s/wrapper/vm/network-interface-wrapper'; import { ChangedCommonData, CommonData, CreateVMWizardComponentProps, DetectCommonDataChanges, VMSettingsField, + VMWizardNetwork, VMWizardProps, VMWizardTab, } from './types'; @@ -47,14 +50,55 @@ import { vmWizardActions } from './redux/actions'; import { ActionType } from './redux/types'; import { iGetCommonData, iGetCreateVMWizardTabs } from './selectors/immutable/selectors'; import { isStepLocked, isStepPending, isStepValid } from './selectors/immutable/wizard-selectors'; -import { VMSettingsTab } from './tabs/vm-settings-tab/vm-settings-tab'; import { ResourceLoadErrors } from './resource-load-errors'; import { CreateVMWizardFooter } from './create-vm-wizard-footer'; +import { VMSettingsTab } from './tabs/vm-settings-tab/vm-settings-tab'; +import { NetworkingTab } from './tabs/networking-tab/networking-tab'; import { ReviewTab } from './tabs/review-tab/review-tab'; import { ResultTab } from './tabs/result-tab/result-tab'; import './create-vm-wizard.scss'; +// TODO remove after moving create functions from kubevirt-web-ui-components +/** * + * kubevirt-web-ui-components InterOP + */ +const kubevirtInterOP = ({ activeNamespace, vmSettings, networks, storages, templates }) => { + const clonedVMsettings = _.cloneDeep(vmSettings); + const clonedNetworks = _.cloneDeep(networks); + const clonedStorages = _.cloneDeep(storages); + + clonedVMsettings.namespace = { value: activeNamespace }; + const operatingSystems = getTemplateOperatingSystems(templates); + const osField = clonedVMsettings[VMSettingsField.OPERATING_SYSTEM]; + const osID = osField.value; + osField.value = operatingSystems.find(({ id }) => id === osID); + + const interOPNetworks = clonedNetworks.map(({ networkInterface, network }) => { + const networkInterfaceWrapper = NetworkInterfaceWrapper.initialize(networkInterface); + const networkWrapper = NetworkWrapper.initialize(network); + + return { + name: networkInterfaceWrapper.getName(), + mac: networkInterfaceWrapper.getMACAddress(), + binding: networkInterfaceWrapper.getTypeValue(), + isBootable: networkInterfaceWrapper.isFirstBootableDevice(), + network: networkWrapper.getReadableName(), + networkType: networkWrapper.getTypeValue(), + templateNetwork: { + network, + interface: networkInterface, + }, + }; + }); + + return { + interOPVMSettings: clonedVMsettings, + interOPNetworks, + interOPStorages: clonedStorages, + }; +}; + export class CreateVMWizardComponent extends React.Component { private isClosed = false; @@ -93,6 +137,12 @@ export class CreateVMWizardComponent extends React.Component( + iGetIn(this.props.stepData, [VMWizardTab.NETWORKING, 'value']), + ); + const storages = immutableListToShallowJS( + iGetIn(this.props.stepData, [VMWizardTab.STORAGE, 'value']), + ); const templates = immutableListToShallowJS( concatImmutableLists( iGetLoadedData(this.props[VMWizardProps.commonTemplates]), @@ -100,25 +150,20 @@ export class CreateVMWizardComponent extends React.Component id === osID); - /** - * END kubevirt-web-ui-components InterOP - */ + const { interOPVMSettings, interOPNetworks, interOPStorages } = kubevirtInterOP({ + vmSettings, + networks, + storages, + templates, + activeNamespace: this.props.activeNamespace, + }); create( enhancedK8sMethods, templates, - vmSettings, - iGetIn(this.props.stepData, [VMWizardTab.NETWORKS, 'value']).toJS(), - iGetIn(this.props.stepData, [VMWizardTab.STORAGE, 'value']).toJS(), + interOPVMSettings, + interOPNetworks, + interOPStorages, immutableListToShallowJS(iGetLoadedData(this.props[VMWizardProps.persistentVolumeClaims])), units, ) @@ -148,9 +193,15 @@ export class CreateVMWizardComponent extends React.Component ), }, - // { - // id: VMWizardTab.NETWORKS, - // }, + { + id: VMWizardTab.NETWORKING, + component: ( + <> + + + + ), + }, // { // id: VMWizardTab.STORAGE, // }, @@ -276,7 +327,7 @@ export const CreateVMWizardPageComponent: React.FC (dispatch, getState) => { - const prevState = getState(); // must be called before dispatch +const withUpdateAndValidateState = ( + id: string, + resolveAction, + changedCommonData?: Set, +) => (dispatch, getState) => { + const prevState = getState(); // must be called before dispatch in resolveAction - dispatch( - vmWizardInternalActions[InternalActionType.Create](id, { - tabs: ALL_VM_WIZARD_TABS.reduce((initial, tabKey) => { - initial[tabKey] = getTabInitialState(tabKey, commonData); - return initial; - }, {}), - commonData, - }), - ); + resolveAction(dispatch, getState); + + updateAndValidateState({ + id, + dispatch, + changedCommonData: changedCommonData || new Set(), + getState, + prevState, + }); +}; - updateAndValidateState({ +export const vmWizardActions: VMWizardActions = { + [ActionType.Create]: (id, commonData: CommonData) => + withUpdateAndValidateState( id, - changedCommonData: new Set(DetectCommonDataChanges), - dispatch, - getState, - prevState, - }); - }, + (dispatch) => + dispatch( + vmWizardInternalActions[InternalActionType.Create](id, { + tabs: ALL_VM_WIZARD_TABS.reduce((initial, tabKey) => { + initial[tabKey] = getTabInitialState(tabKey, commonData); + return initial; + }, {}), + commonData, + }), + ), + new Set(DetectCommonDataChanges), + ), [ActionType.Dispose]: (id) => (dispatch, getState) => { const prevState = getState(); // must be called before dispatch cleanup({ @@ -48,34 +62,32 @@ export const vmWizardActions: VMWizardActions = { dispatch(vmWizardInternalActions[InternalActionType.Dispose](id)); }, - [ActionType.SetVmSettingsFieldValue]: (id, key: VMSettingsField, value: string) => ( - dispatch, - getState, - ) => { - const prevState = getState(); // must be called before dispatch - dispatch(vmWizardInternalActions[InternalActionType.SetVmSettingsFieldValue](id, key, value)); - - updateAndValidateState({ + [ActionType.SetVmSettingsFieldValue]: (id, key: VMSettingsField, value: string) => + withUpdateAndValidateState(id, (dispatch) => + dispatch(vmWizardInternalActions[InternalActionType.SetVmSettingsFieldValue](id, key, value)), + ), + [ActionType.UpdateCommonData]: (id, commonData: CommonData, changedProps: ChangedCommonData) => + withUpdateAndValidateState( id, - dispatch, - changedCommonData: new Set(), - getState, - prevState, - }); - }, - [ActionType.UpdateCommonData]: (id, commonData: CommonData, changedProps: ChangedCommonData) => ( - dispatch, - getState, - ) => { - const prevState = getState(); // must be called before dispatch - - dispatch(vmWizardInternalActions[InternalActionType.UpdateCommonData](id, commonData)); - - updateAndValidateState({ id, dispatch, changedCommonData: changedProps, getState, prevState }); - }, - [ActionType.SetNetworks]: (id, value: any, isValid: boolean, isLocked: boolean) => (dispatch) => { - dispatch(vmWizardInternalActions[InternalActionType.SetNetworks](id, value, isValid, isLocked)); + (dispatch) => + dispatch(vmWizardInternalActions[InternalActionType.UpdateCommonData](id, commonData)), + changedProps, + ), + [ActionType.SetTabLocked]: (id, tab: VMWizardTab, isLocked: boolean) => (dispatch) => { + dispatch(vmWizardInternalActions[InternalActionType.SetTabLocked](id, tab, isLocked)); }, + [ActionType.UpdateNIC]: (id, network: VMWizardNetwork) => + withUpdateAndValidateState(id, (dispatch) => + dispatch(vmWizardInternalActions[InternalActionType.UpdateNIC](id, network)), + ), + [ActionType.RemoveNIC]: (id, networkID: string) => + withUpdateAndValidateState(id, (dispatch) => + dispatch(vmWizardInternalActions[InternalActionType.RemoveNIC](id, networkID)), + ), + [ActionType.SetNetworks]: (id, networks: VMWizardNetwork[]) => + withUpdateAndValidateState(id, (dispatch) => + dispatch(vmWizardInternalActions[InternalActionType.SetNetworks](id, networks)), + ), [ActionType.SetStorages]: (id, value: any, isValid: boolean, isLocked: boolean) => (dispatch) => { dispatch(vmWizardInternalActions[InternalActionType.SetStorages](id, value, isValid, isLocked)); }, diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/initial-state.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/initial-state.ts index a85bb786017..2d4017648a8 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/initial-state.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/initial-state.ts @@ -7,7 +7,7 @@ import { getReviewInitialState } from './review-tab-initial-state'; const initialStateGetterResolver = { [VMWizardTab.VM_SETTINGS]: getVmSettingsInitialState, - [VMWizardTab.NETWORKS]: getNetworksInitialState, + [VMWizardTab.NETWORKING]: getNetworksInitialState, [VMWizardTab.STORAGE]: getStorageInitialState, [VMWizardTab.REVIEW]: getReviewInitialState, [VMWizardTab.RESULT]: getResultInitialState, diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/networks-tab-initial-state.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/networks-tab-initial-state.ts index 0e2ea0e4638..fec79c89713 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/networks-tab-initial-state.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/networks-tab-initial-state.ts @@ -1,15 +1,21 @@ -import { NetworkInterfaceType, NetworkType, POD_NETWORK } from '../../../../constants/vm'; +import { VMWizardNetwork, VMWizardNetworkType } from '../../types'; +import { NetworkInterfaceWrapper } from '../../../../k8s/wrapper/vm/network-interface-wrapper'; +import { NetworkInterfaceModel, NetworkType } from '../../../../constants/vm/network'; +import { NetworkWrapper } from '../../../../k8s/wrapper/vm/network-wrapper'; +import { getSequenceName } from '../../../../utils/strings'; -export const podNetwork = { - rootNetwork: {}, - id: 0, - name: 'nic0', - mac: '', - network: POD_NETWORK, - editable: true, - edit: false, - networkType: NetworkType.POD, - binding: NetworkInterfaceType.MASQUERADE, +export const podNetwork: VMWizardNetwork = { + id: '0', + type: VMWizardNetworkType.UI_DEFAULT_POD_NETWORK, + networkInterface: NetworkInterfaceWrapper.initializeFromSimpleData({ + name: getSequenceName('nic'), + model: NetworkInterfaceModel.VIRTIO, + interfaceType: NetworkType.POD.getDefaultInterfaceType(), + }).asResource(), + network: NetworkWrapper.initializeFromSimpleData({ + name: getSequenceName('nic'), + type: NetworkType.POD, + }).asResource(), }; export const getNetworksInitialState = () => ({ diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/storage-tab-initial-state.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/storage-tab-initial-state.ts index 143fe5fa496..f085e722531 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/storage-tab-initial-state.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/storage-tab-initial-state.ts @@ -1,6 +1,5 @@ // left intentionally empty import { ProvisionSource } from '../../../../types/vm'; -import { StorageType } from '../../../../constants/vm/storage'; const rootDisk = { rootStorage: {}, @@ -9,11 +8,11 @@ const rootDisk = { }; export const rootContainerDisk = { ...rootDisk, - storageType: StorageType.CONTAINER, + // storageType: StorageType.CONTAINER, TODO!! }; export const rootDataVolumeDisk = { ...rootDisk, - storageType: StorageType.DATAVOLUME, + // storageType: StorageType.DATAVOLUME, TODO!! size: 10, }; export const getInitialDisk = (provisionSource: ProvisionSource) => { diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/vm-settings-tab-initial-state.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/vm-settings-tab-initial-state.ts index ccd78d58327..6cbe859336b 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/vm-settings-tab-initial-state.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/initial-state/vm-settings-tab-initial-state.ts @@ -9,7 +9,7 @@ export const getInitialVmSettings = (common: CommonData) => { } = common; const provisionSources = [ - // ProvisionSource.PXE, // TODO: uncomment when storage tab is implemented + ProvisionSource.PXE, ProvisionSource.URL, ProvisionSource.CONTAINER, // ProvisionSource.CLONED_DISK, // TODO: uncomment when storage tab is implemented diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/internal-actions.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/internal-actions.ts index ad32d2ed158..47fdecde4ac 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/internal-actions.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/internal-actions.ts @@ -1,4 +1,4 @@ -import { VMSettingsField, VMWizardTab } from '../types'; +import { VMSettingsField, VMWizardNetwork, VMWizardTab } from '../types'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { ActionBatch, InternalActionType, WizardInternalActionDispatcher } from './types'; @@ -47,6 +47,14 @@ export const vmWizardInternalActions: VMWizardInternalActions = { }, type: InternalActionType.SetTabValidity, }), + [InternalActionType.SetTabLocked]: (id, tab: VMWizardTab, isLocked: boolean) => ({ + payload: { + id, + tab, + isLocked, + }, + type: InternalActionType.SetTabLocked, + }), [InternalActionType.SetVmSettingsFieldValue]: (id, key: VMSettingsField, value: string) => ({ payload: { id, @@ -85,12 +93,24 @@ export const vmWizardInternalActions: VMWizardInternalActions = { }, type: InternalActionType.UpdateVmSettings, }), - [InternalActionType.SetNetworks]: (id, value, isValid: boolean, isLocked: boolean) => ({ + [InternalActionType.UpdateNIC]: (id, network: VMWizardNetwork) => ({ payload: { id, - value, - isValid, - isLocked, + network, + }, + type: InternalActionType.UpdateNIC, + }), + [InternalActionType.RemoveNIC]: (id, networkID: string) => ({ + payload: { + id, + networkID, + }, + type: InternalActionType.RemoveNIC, + }), + [InternalActionType.SetNetworks]: (id, networks: VMWizardNetwork[]) => ({ + payload: { + id, + value: networks, }, type: InternalActionType.SetNetworks, }), diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/reducers.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/reducers.ts index c1148426ced..123045429c0 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/reducers.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/reducers.ts @@ -1,5 +1,7 @@ +import * as _ from 'lodash'; import { Map as ImmutableMap, fromJS } from 'immutable'; import { VMWizardTab } from '../types'; +import { iGet } from '../../../utils/immutable'; import { InternalActionType, WizardInternalAction } from './types'; // Merge deep in without updating the keys with undefined values @@ -34,6 +36,25 @@ const setObjectValues = (state, path, obj) => { : state; }; +const updateIDItemInList = (state, path, item?) => { + const itemID = iGet(item, 'id'); + return state.updateIn(path, (items) => { + const networkIndex = item ? items.findIndex((t) => iGet(t, 'id') === itemID) : -1; + if (networkIndex === -1) { + const maxID = items.map((t) => iGet(t, 'id')).max() || 0; + return items.push(item.set('id', _.toString(_.toSafeInteger(maxID) + 1))); + } + return items.set(networkIndex, item); + }); +}; + +const removeIDItemFromList = (state, path, itemID?) => { + return state.updateIn(path, (items) => { + const networkIndex = itemID == null ? -1 : items.findIndex((t) => iGet(t, 'id') === itemID); + return networkIndex === -1 ? items : items.delete(networkIndex); + }); +}; + export default (state, action: WizardInternalAction) => { if (!state) { return ImmutableMap(); @@ -46,8 +67,20 @@ export default (state, action: WizardInternalAction) => { return state.set(dialogId, fromJS(payload.value)); case InternalActionType.Dispose: return state.delete(dialogId); + case InternalActionType.UpdateNIC: + return updateIDItemInList( + state, + [dialogId, 'tabs', VMWizardTab.NETWORKING, 'value'], + fromJS(payload.network), + ); + case InternalActionType.RemoveNIC: + return removeIDItemFromList( + state, + [dialogId, 'tabs', VMWizardTab.NETWORKING, 'value'], + payload.networkID, + ); case InternalActionType.SetNetworks: - return setTabKeys(state, VMWizardTab.NETWORKS, action); + return setTabKeys(state, VMWizardTab.NETWORKING, action); case InternalActionType.SetStorages: return setTabKeys(state, VMWizardTab.STORAGE, action); case InternalActionType.SetResults: @@ -67,6 +100,8 @@ export default (state, action: WizardInternalAction) => { [dialogId, 'tabs', payload.tab, 'hasAllRequiredFilled'], payload.hasAllRequiredFilled, ); + case InternalActionType.SetTabLocked: + return state.setIn([dialogId, 'tabs', payload.tab, 'isLocked'], payload.isLocked); case InternalActionType.SetVmSettingsFieldValue: return state.setIn( [dialogId, 'tabs', VMWizardTab.VM_SETTINGS, 'value', payload.key, 'value'], diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/stateUpdate/vmSettings/prefill-vm-template-state-update.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/stateUpdate/vmSettings/prefill-vm-template-state-update.ts index 929b2ad3ef4..61d0e92472f 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/stateUpdate/vmSettings/prefill-vm-template-state-update.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/stateUpdate/vmSettings/prefill-vm-template-state-update.ts @@ -1,14 +1,19 @@ -import * as _ from 'lodash'; +import { createBasicLookup } from '@console/shared/src'; import { InternalActionType, UpdateOptions } from '../../types'; import { iGetVmSettingValue } from '../../../selectors/immutable/vm-settings'; -import { VMSettingsField, VMWizardProps } from '../../../types'; +import { + VMSettingsField, + VMWizardNetwork, + VMWizardNetworkType, + VMWizardProps, +} from '../../../types'; import { iGetLoadedCommonData, iGetName } from '../../../selectors/immutable/selectors'; import { concatImmutableLists, immutableListToShallowJS } from '../../../../../utils/immutable'; import { iGetNetworks as getDialogNetworks } from '../../../selectors/immutable/networks'; import { iGetStorages } from '../../../selectors/immutable/storage'; import { podNetwork } from '../../initial-state/networks-tab-initial-state'; import { vmWizardInternalActions } from '../../internal-actions'; -import { CUSTOM_FLAVOR } from '../../../../../constants/vm'; +import { CUSTOM_FLAVOR, NetworkInterfaceModel } from '../../../../../constants/vm'; import { DEFAULT_CPU, getCloudInitUserData, @@ -25,12 +30,14 @@ import { getTemplateOperatingSystems, getTemplateWorkloadProfiles, } from '../../../../../selectors/vm-template/advanced'; -import { ProvisionSource } from '../../../../../types/vm'; +import { ProvisionSource, V1Network } from '../../../../../types/vm'; import { getTemplateProvisionSource, getTemplateStorages, } from '../../../../../selectors/vm-template/combined'; import { getFlavors } from '../../../../../selectors/vm-template/combined-dependent'; +import { getSimpleName } from '../../../../../selectors/utils'; +import { getNextIDResolver } from '../../../../../utils/utils'; // used by user template; currently we do not support PROVISION_SOURCE_IMPORT const provisionSourceDataFieldResolver = { @@ -53,15 +60,17 @@ export const prefillVmTemplateUpdater = ({ id, dispatch, getState }: UpdateOptio const vmSettingsUpdate = {}; // filter out oldTemplates - const networkRowsUpdate = immutableListToShallowJS(getDialogNetworks(state, id)).filter( - (network) => !network.templateNetwork, - ); + let networksUpdate = immutableListToShallowJS( + getDialogNetworks(state, id), + ).filter((network) => network.type !== VMWizardNetworkType.TEMPLATE); + const getNextNetworkID = getNextIDResolver(networksUpdate); + const storageRowsUpdate = immutableListToShallowJS(iGetStorages(state, id)).filter( (storage) => !(storage.templateStorage || storage.rootStorage), ); - if (!networkRowsUpdate.find((row) => row.rootNetwork)) { - networkRowsUpdate.push(podNetwork); + if (!networksUpdate.find((row) => !!row.network.pod)) { + networksUpdate.unshift({ ...podNetwork, id: getNextNetworkID() }); } if (iUserTemplate) { @@ -111,24 +120,27 @@ export const prefillVmTemplateUpdater = ({ id, dispatch, getState }: UpdateOptio } } + const networkLookup = createBasicLookup(getNetworks(vm), getSimpleName); // prefill networks - const templateNetworks = getInterfaces(vm).map((i) => ({ - templateNetwork: { - network: getNetworks(vm).find((n) => n.name === i.name), - interface: i, + const templateNetworks = getInterfaces(vm).map((intface) => ({ + id: getNextNetworkID(), + type: VMWizardNetworkType.TEMPLATE, + network: networkLookup[getSimpleName(intface)], + networkInterface: { + ...intface, + model: intface.model || NetworkInterfaceModel.VIRTIO.getValue(), }, })); - // do not add root interface if there already is one or autoAttachPodInterface is set to false + // remove pod networks if there is already one in template or autoAttachPodInterface is set to false if ( - templateNetworks.some((network) => network.templateNetwork.network.pod) || + templateNetworks.some((network) => !!network.network.pod) || !hasAutoAttachPodInterface(vm, true) ) { - const index = _.findIndex(networkRowsUpdate, (network: any) => network.rootNetwork); - networkRowsUpdate.splice(index, 1); + networksUpdate = networksUpdate.filter((network) => !network.network.pod); } - networkRowsUpdate.push(...templateNetworks); + networksUpdate.push(...templateNetworks); // prefill storage const templateStorages = getTemplateStorages(userTemplate, dataVolumes).map((storage) => ({ @@ -153,6 +165,6 @@ export const prefillVmTemplateUpdater = ({ id, dispatch, getState }: UpdateOptio } dispatch(vmWizardInternalActions[InternalActionType.UpdateVmSettings](id, vmSettingsUpdate)); - dispatch(vmWizardInternalActions[InternalActionType.SetNetworks](id, networkRowsUpdate)); + dispatch(vmWizardInternalActions[InternalActionType.SetNetworks](id, networksUpdate)); dispatch(vmWizardInternalActions[InternalActionType.SetStorages](id, storageRowsUpdate)); }; diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/types.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/types.ts index 174d69000af..63b5e7e4eba 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/types.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/types.ts @@ -3,18 +3,22 @@ import { ChangedCommonDataProp, VMSettingsField, VMSettingsFieldType, + VMWizardNetwork, VMWizardTab, } from '../types'; import { ValidationObject } from '../../../utils/validations/types'; export enum ActionType { - Create = 'KubevirtVMWizardCreate', - Dispose = 'KubevirtVMWizardDispose', - UpdateCommonData = 'KubevirtVMWizardUpdateCommonData', - SetVmSettingsFieldValue = 'KubevirtVMWizardSetVmSettingsFieldValue', - SetNetworks = 'KubevirtVMWizardSetNetworks', - SetStorages = 'KubevirtVMWizardSetStorages', - SetResults = 'KubevirtVMWizardSetResults', + Create = 'KubevirtVMWizardExternalCreate', + Dispose = 'KubevirtVMWiExternalDispose', + UpdateCommonData = 'KubevirtVMWizardExternalUpdateCommonData', + SetVmSettingsFieldValue = 'KubevirtVMWizardExternalSetVmSettingsFieldValue', + SetTabLocked = 'KubevirtVMWizardExternalSetTabLocked', + RemoveNIC = 'KubevirtVMWizardExternalRemoveNIC', + UpdateNIC = 'KubevirtVMWizardExternalUpdateNIC', + SetNetworks = 'KubevirtVMWizardExternalSetNetworks', + SetStorages = 'KubevirtVMWizardExternalSetStorages', + SetResults = 'KubevirtVMWizardExternalSetResults', } // should not be called directly from outside redux code (e.g. stateUpdate) @@ -24,11 +28,14 @@ export enum InternalActionType { Update = 'KubevirtVMWizardUpdateInternal', UpdateCommonData = 'KubevirtVMWizardUpdateCommonData', SetTabValidity = 'KubevirtVMWizardSetTabValidityInternal', + SetTabLocked = 'KubevirtVMWizardSetTabLocked', SetVmSettingsFieldValue = 'KubevirtVMWizardSetVmSettingsFieldValueInternal', SetInVmSettings = 'KubevirtVMWizardSetInVmSettingsInternal', SetInVmSettingsBatch = 'KubevirtVMWizardSetInVmSettingsBatchInternal', UpdateVmSettingsField = 'KubevirtVMWizardUpdateVmSettingsFieldInternal', UpdateVmSettings = 'KubevirtVMWizardUpdateVmSettingsInternal', + RemoveNIC = 'KubevirtVMWizardRemoveNIC', + UpdateNIC = 'KubevirtVMWizardUpdateNIC', SetNetworks = 'KubevirtVMWizardSetNetworks', SetStorages = 'KubevirtVMWizardSetStorages', SetResults = 'KubevirtVMWizardSetResults', @@ -47,6 +54,9 @@ export type WizardInternalAction = { key?: VMSettingsField; tab?: VMWizardTab; batch?: ActionBatch; + network?: VMWizardNetwork; + networks?: VMWizardNetwork[]; + networkID?: string; }; }; diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/utils.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/utils.ts index f6b08d810f1..ea4da25a8f3 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/utils.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/utils.ts @@ -5,8 +5,9 @@ import { setVmSettingsTabValidity, validateVmSettings, } from './validations/vm-settings-tab-validation'; +import { setNetworksTabValidity, validateNetworks } from './validations/networks-tab-validation'; -const UPDATE_TABS = [VMWizardTab.VM_SETTINGS]; +const UPDATE_TABS = [VMWizardTab.VM_SETTINGS, VMWizardTab.NETWORKING]; const updaterResolver = { [VMWizardTab.VM_SETTINGS]: updateVmSettingsState, @@ -14,10 +15,12 @@ const updaterResolver = { const validateTabResolver = { [VMWizardTab.VM_SETTINGS]: validateVmSettings, + [VMWizardTab.NETWORKING]: validateNetworks, }; const isTabValidResolver = { [VMWizardTab.VM_SETTINGS]: setVmSettingsTabValidity, + [VMWizardTab.NETWORKING]: setNetworksTabValidity, }; export const updateAndValidateState = (options: UpdateOptions) => { diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/validations/networks-tab-validation.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/validations/networks-tab-validation.ts new file mode 100644 index 00000000000..00e763962cf --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/validations/networks-tab-validation.ts @@ -0,0 +1,93 @@ +import { VMSettingsField, VMWizardTab } from '../../types'; +import { InternalActionType, UpdateOptions } from '../types'; +import { vmWizardInternalActions } from '../internal-actions'; +import { validateNIC } from '../../../../utils/validations/vm'; +import { iGetIn } from '../../../../utils/immutable'; +import { iGetNetworks } from '../../selectors/immutable/networks'; +import { checkTabValidityChanged } from '../../selectors/immutable/selectors'; +import { iGetVmSettingValue } from '../../selectors/immutable/vm-settings'; +import { ProvisionSource } from '../../../../types/vm'; +import { getNetworksWithWrappers } from '../../selectors/selectors'; + +export const validateNetworks = (options: UpdateOptions) => { + const { id, prevState, dispatch, getState } = options; + const state = getState(); + + const prevINetworks = iGetNetworks(prevState, id); + const iNetworks = iGetNetworks(state, id); + + if ( + prevINetworks && + iNetworks && + prevINetworks.size === iNetworks.size && + !prevINetworks.find( + (prevINetwork, prevINetworkIndex) => prevINetwork !== iNetworks.get(prevINetworkIndex), + ) + ) { + return; + } + + const networks = getNetworksWithWrappers(state, id); + + const validatedNetworks = networks.map( + ({ networkInterfaceWrapper, networkWrapper, ...networkBundle }) => { + const otherNetworkBundles = networks.filter((n) => n.id !== networkBundle.id); // to discard networks with a same name + const usedMultusNetworkNames = new Set( + otherNetworkBundles + .map(({ networkWrapper: nw }) => nw.getMultusNetworkName()) + .filter((n) => n), + ); + const usedInterfacesNames = new Set( + otherNetworkBundles.map(({ networkInterfaceWrapper: niw }) => niw.getName()), + ); + + return { + ...networkBundle, + validation: validateNIC(networkInterfaceWrapper, networkWrapper, { + usedInterfacesNames, + usedMultusNetworkNames, + }), + }; + }, + ); + + dispatch(vmWizardInternalActions[InternalActionType.SetNetworks](id, validatedNetworks)); +}; + +export const setNetworksTabValidity = (options: UpdateOptions) => { + const { id, dispatch, getState } = options; + const state = getState(); + const iNetworks = iGetNetworks(state, id); + + let hasAllRequiredFilled = iNetworks.every((iNetwork) => + iGetIn(iNetwork, ['validation', 'hasAllRequiredFilled']), + ); + + if ( + hasAllRequiredFilled && + iGetVmSettingValue(state, id, VMSettingsField.PROVISION_SOURCE_TYPE) === ProvisionSource.PXE + ) { + hasAllRequiredFilled = !!iNetworks.find( + (networkBundle) => + !iGetIn(networkBundle, ['network', 'pod']) && + iGetIn(networkBundle, ['networkInterface', 'bootOrder']) === 1, + ); + } + + let isValid = hasAllRequiredFilled; + + if (isValid) { + isValid = iNetworks.every((iNetwork) => iGetIn(iNetwork, ['validation', 'isValid'])); + } + + if (checkTabValidityChanged(state, id, VMWizardTab.NETWORKING, isValid, hasAllRequiredFilled)) { + dispatch( + vmWizardInternalActions[InternalActionType.SetTabValidity]( + id, + VMWizardTab.NETWORKING, + isValid, + hasAllRequiredFilled, + ), + ); + } +}; diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/validations/vm-settings-tab-validation.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/validations/vm-settings-tab-validation.ts index 36cdfebf344..de8bd7d326d 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/validations/vm-settings-tab-validation.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/validations/vm-settings-tab-validation.ts @@ -2,10 +2,10 @@ import { isEmpty } from 'lodash'; import { List } from 'immutable'; import { VMSettingsField, VMWizardProps, VMWizardTab } from '../../types'; import { - iGetFieldKey, - iGetVmSettings, hasVmSettingsChanged, + iGetFieldKey, iGetFieldValue, + iGetVmSettings, isFieldRequired, } from '../../selectors/immutable/vm-settings'; import { @@ -27,6 +27,7 @@ import { import { concatImmutableLists, immutableListToShallowJS } from '../../../../utils/immutable'; import { getFieldTitle } from '../../utils/vm-settings-tab-utils'; import { + checkTabValidityChanged, iGetCommonData, iGetLoadedCommonData, iGetName, @@ -163,12 +164,14 @@ export const setVmSettingsTabValidity = (options: UpdateOptions) => { ); } - dispatch( - vmWizardInternalActions[InternalActionType.SetTabValidity]( - id, - VMWizardTab.VM_SETTINGS, - isValid, - hasAllRequiredFilled, - ), - ); + if (checkTabValidityChanged(state, id, VMWizardTab.VM_SETTINGS, isValid, hasAllRequiredFilled)) { + dispatch( + vmWizardInternalActions[InternalActionType.SetTabValidity]( + id, + VMWizardTab.VM_SETTINGS, + isValid, + hasAllRequiredFilled, + ), + ); + } }; diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/resource-load-errors.tsx b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/resource-load-errors.tsx index 3c905d9ea70..bbd568be9d9 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/resource-load-errors.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/resource-load-errors.tsx @@ -22,7 +22,7 @@ const stateToProps = (state, { wizardReduxID }) => ({ errors: [ asError(state, wizardReduxID, VMWizardProps.commonTemplates), asError(state, wizardReduxID, VMWizardProps.userTemplates), - asError(state, wizardReduxID, VMWizardProps.networkConfigs), + asError(state, wizardReduxID, VMWizardProps.networkAttachmentDefinitions), asError(state, wizardReduxID, VMWizardProps.persistentVolumeClaims), asError(state, wizardReduxID, VMWizardProps.dataVolumes), asError(state, wizardReduxID, VMWizardProps.storageClasses), diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/immutable/networks.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/immutable/networks.ts index 6997327d3ed..27fbb1d4dcf 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/immutable/networks.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/immutable/networks.ts @@ -3,4 +3,4 @@ import { VMWizardTab } from '../../types'; import { iGetCreateVMWizardTabs } from './selectors'; export const iGetNetworks = (state, id: string) => - iGetIn(iGetCreateVMWizardTabs(state, id), [VMWizardTab.NETWORKS, 'value']); + iGetIn(iGetCreateVMWizardTabs(state, id), [VMWizardTab.NETWORKING, 'value']); diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/immutable/selectors.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/immutable/selectors.ts index 3c687831a41..c26c6d82607 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/immutable/selectors.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/immutable/selectors.ts @@ -1,13 +1,28 @@ import { K8sResourceKind } from '@console/internal/module/k8s'; import { iGet, iGetIn, iGetLoadedData } from '../../../../utils/immutable'; import { getCreateVMWizards } from '../selectors'; -import { CommonDataProp } from '../../types'; +import { CommonDataProp, VMWizardTab } from '../../types'; +import { hasStepAllRequiredFilled, isStepValid } from './wizard-selectors'; export const iGetCreateVMWizard = (state, reduxID: string) => iGet(getCreateVMWizards(state), reduxID); export const iGetCreateVMWizardTabs = (state, reduxID: string) => iGet(iGetCreateVMWizard(state, reduxID), 'tabs'); +export const checkTabValidityChanged = ( + state, + reduxID: string, + tab: VMWizardTab, + nextIsValid, + nextHasAllRequiredFilled, +) => { + const tabs = iGetCreateVMWizardTabs(state, reduxID); + return ( + isStepValid(tabs, tab) !== nextIsValid || + hasStepAllRequiredFilled(tabs, tab) !== nextHasAllRequiredFilled + ); +}; + export const iGetCommonData = (state, reduxID: string, key: CommonDataProp) => { const wizard = iGetCreateVMWizard(state, reduxID); const data = iGetIn(wizard, ['commonData', 'data', key]); diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/selectors.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/selectors.ts index c962e44bcc3..121b91e2f8c 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/selectors.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/selectors.ts @@ -1,5 +1,23 @@ import { get } from 'lodash'; import { Map } from 'immutable'; +import { iGetIn, immutableListToShallowJS } from '../../../utils/immutable'; +import { VMWizardNetwork, VMWizardNetworkWithWrappers, VMWizardTab } from '../types'; +import { NetworkInterfaceWrapper } from '../../../k8s/wrapper/vm/network-interface-wrapper'; +import { NetworkWrapper } from '../../../k8s/wrapper/vm/network-wrapper'; export const getCreateVMWizards = (state): Map => get(state, ['kubevirt', 'createVmWizards']); + +export const getNetworks = (state, id: string): VMWizardNetwork[] => + immutableListToShallowJS( + iGetIn(getCreateVMWizards(state), [id, 'tabs', VMWizardTab.NETWORKING, 'value']), + ); + +export const getNetworksWithWrappers = (state, id: string): VMWizardNetworkWithWrappers[] => + getNetworks(state, id).map(({ network, networkInterface, ...rest }) => ({ + networkInterfaceWrapper: NetworkInterfaceWrapper.initialize(networkInterface), + networkWrapper: NetworkWrapper.initialize(network), + networkInterface, + network, + ...rest, + })); diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/strings/networking.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/strings/networking.ts new file mode 100644 index 00000000000..d84fbb360fb --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/strings/networking.ts @@ -0,0 +1,4 @@ +export const ADD_NETWORK_INTERFACE = 'Add Network Interface'; +export const PXE_NIC_NOT_FOUND_ERROR = 'A PXE-capable network interface could not be found.'; +export const PXE_INFO = 'Pod network is not PXE bootable'; +export const SELECT_PXE_NIC = '--- Select PXE network interface ---'; diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/strings/strings.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/strings/strings.ts index fccadae4727..61617689e91 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/strings/strings.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/strings/strings.ts @@ -11,7 +11,7 @@ export const getCreateVMLikeEntityLabel = (isTemplate: boolean) => export const TabTitleResolver = { [VMWizardTab.VM_SETTINGS]: 'General', - [VMWizardTab.NETWORKS]: 'Networking', + [VMWizardTab.NETWORKING]: 'Networking', [VMWizardTab.STORAGE]: 'Storage', [VMWizardTab.REVIEW]: 'Review', [VMWizardTab.RESULT]: 'Result', diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/networking-tab.scss b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/networking-tab.scss new file mode 100644 index 00000000000..c5f780378f5 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/networking-tab.scss @@ -0,0 +1,14 @@ +.kubevirt-create-vm-modal__networking-tab-container { + display: flex; + flex-direction: column; + min-height: 100%; +} + +.kubevirt-create-vm-modal__networking-tab-main { + flex: 1; +} + +.kubevirt-create-vm-modal__networking-tab-pxe { + margin-top: 1rem; + margin-bottom: 3rem; +} diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/networking-tab.tsx b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/networking-tab.tsx new file mode 100644 index 00000000000..bb1cca86c78 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/networking-tab.tsx @@ -0,0 +1,167 @@ +import * as React from 'react'; +import { connect } from 'react-redux'; +import { + Bullseye, + Button, + ButtonVariant, + EmptyState, + EmptyStateVariant, + Split, + SplitItem, + Title, +} from '@patternfly/react-core'; +import { PlusCircleIcon } from '@patternfly/react-icons'; +import { iGetCreateVMWizardTabs } from '../../selectors/immutable/selectors'; +import { isStepLocked } from '../../selectors/immutable/wizard-selectors'; +import { + VMSettingsField, + VMWizardNetwork, + VMWizardNetworkWithWrappers, + VMWizardTab, +} from '../../types'; +import { VMNicsTable } from '../../../vm-nics/vm-nics'; +import { nicTableColumnClasses } from '../../../vm-nics/utils'; +import { vmWizardActions } from '../../redux/actions'; +import { ActionType } from '../../redux/types'; +import { ADD_NETWORK_INTERFACE } from '../../strings/networking'; +import { iGetVmSettingValue } from '../../selectors/immutable/vm-settings'; +import { ProvisionSource } from '../../../../types/vm'; +import { getNetworksWithWrappers } from '../../selectors/selectors'; +import { wrapWithProgress } from '../../../../utils/utils'; +import { vmWizardNicModalEnhanced } from './vm-wizard-nic-modal-enhanced'; +import { VMWizardNicRow } from './vm-wizard-nic-row'; +import { VMWizardNetworkBundle } from './types'; +import { PXENetworks } from './pxe-networks'; + +import './networking-tab.scss'; + +const getNicsData = (networks: VMWizardNetworkWithWrappers[]): VMWizardNetworkBundle[] => + networks.map((wizardNetworkData) => { + const { networkInterfaceWrapper, networkWrapper } = wizardNetworkData; + return { + wizardNetworkData, + // for sorting + name: networkInterfaceWrapper.getName(), + model: networkInterfaceWrapper.getReadableModel(), + networkName: networkWrapper.getReadableName(), + interfaceType: networkInterfaceWrapper.getTypeValue(), + macAddress: networkInterfaceWrapper.getMACAddress(), + }; + }); + +const NetworkingTabComponent: React.FC = ({ + isPXENICRequired, + wizardReduxID, + isLocked, + setTabLocked, + removeNIC, + updateNetworks, + networks, +}) => { + const hasNetworks = networks.length > 0; + + const withProgress = wrapWithProgress(setTabLocked); + + const addButtonProps = { + id: 'add-nic', + onClick: () => + withProgress( + vmWizardNicModalEnhanced({ + wizardReduxID, + }).result, + ), + isDisabled: isLocked, + }; + + return ( +
+ + + + Network Interfaces + + + {hasNetworks && ( + + + + )} + + {hasNetworks && ( + <> +
+ +
+ {isPXENICRequired && ( +
+ +
+ )} + + )} + {!hasNetworks && ( + + + + No network interface added + + + + + )} +
+ ); +}; + +type NetworkingTabComponentProps = { + isLocked: boolean; + isPXENICRequired: boolean; + wizardReduxID: string; + networks: VMWizardNetworkWithWrappers[]; + removeNIC: (id: string) => void; + setTabLocked: (isLocked: boolean) => void; + updateNetworks: (networks: VMWizardNetwork[]) => void; +}; + +const stateToProps = (state, { wizardReduxID }) => { + const stepData = iGetCreateVMWizardTabs(state, wizardReduxID); + return { + isLocked: isStepLocked(stepData, VMWizardTab.NETWORKING), + networks: getNetworksWithWrappers(state, wizardReduxID), + isPXENICRequired: + iGetVmSettingValue(state, wizardReduxID, VMSettingsField.PROVISION_SOURCE_TYPE) === + ProvisionSource.PXE, + }; +}; + +const dispatchToProps = (dispatch, { wizardReduxID }) => ({ + setTabLocked: (isLocked) => { + dispatch( + vmWizardActions[ActionType.SetTabLocked](wizardReduxID, VMWizardTab.NETWORKING, isLocked), + ); + }, + removeNIC: (id: string) => { + dispatch(vmWizardActions[ActionType.RemoveNIC](wizardReduxID, id)); + }, + updateNetworks: (networks: VMWizardNetwork[]) => { + dispatch(vmWizardActions[ActionType.SetNetworks](wizardReduxID, networks)); + }, +}); + +export const NetworkingTab = connect( + stateToProps, + dispatchToProps, +)(NetworkingTabComponent); diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/pxe-networks.tsx b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/pxe-networks.tsx new file mode 100644 index 00000000000..2fe3d67dd72 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/pxe-networks.tsx @@ -0,0 +1,98 @@ +import * as React from 'react'; +import { Form, FormSelect, FormSelectOption } from '@patternfly/react-core'; +import { VMWizardNetwork, VMWizardNetworkType, VMWizardNetworkWithWrappers } from '../../types'; +import { NetworkInterfaceWrapper } from '../../../../k8s/wrapper/vm/network-interface-wrapper'; +import { PXE_INFO, PXE_NIC_NOT_FOUND_ERROR, SELECT_PXE_NIC } from '../../strings/networking'; +import { FormRow } from '../../../form/form-row'; +import { ValidationErrorType } from '../../../../utils/validations/types'; +import { FormSelectPlaceholderOption } from '../../../form/form-select-placeholder-option'; +import { ignoreCaseSort } from '../../../../utils/sort'; + +import './networking-tab.scss'; + +const PXE_BOOTSOURCE_ID = 'pxe-bootsource'; + +type PXENetworksProps = { + isDisabled: boolean; + networks: VMWizardNetworkWithWrappers[]; + updateNetworks: (networks: VMWizardNetwork[]) => void; +}; + +export const PXENetworks: React.FC = ({ + isDisabled, + updateNetworks, + networks, +}) => { + const pxeNetworks = networks.filter((n) => !n.networkWrapper.isPodNetwork()); + const hasPXENetworks = pxeNetworks.length > 0; + + const selectedPXE = pxeNetworks.find((network) => + network.networkInterfaceWrapper.isFirstBootableDevice(), + ); + + const onPXENetworkChange = (id: string) => { + const bootOrderIndexes = networks + .map((wizardNetwork) => + wizardNetwork.id === id || wizardNetwork.type !== VMWizardNetworkType.TEMPLATE + ? null + : wizardNetwork.networkInterfaceWrapper.getBootOrder(), + ) + .filter((b) => b != null) + .sort(); + updateNetworks( + // TODO: include disks in the computation and maybe move somewhere else (state update) + networks.map( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ({ networkInterfaceWrapper, networkWrapper: unused, ...wizardNetwork }) => { + if (wizardNetwork.id === id || networkInterfaceWrapper.hasBootOrder()) { + return { + ...wizardNetwork, + networkInterface: NetworkInterfaceWrapper.mergeWrappers( + networkInterfaceWrapper, + NetworkInterfaceWrapper.initializeFromSimpleData({ + bootOrder: + wizardNetwork.id === id + ? 1 + : bootOrderIndexes.indexOf(networkInterfaceWrapper.getBootOrder()) + 2, + }), + ).asResource(), + }; + } + return wizardNetwork; + }, + ), + ); + }; + + return ( +
+ + + + {ignoreCaseSort(pxeNetworks, null, (n) => n.networkWrapper.getReadableName()).map( + (network) => ( + + ), + )} + + +
+ ); +}; diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/types.tsx b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/types.tsx new file mode 100644 index 00000000000..a1c069ea03a --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/types.tsx @@ -0,0 +1,17 @@ +import { NetworkSimpleData } from '../../../vm-nics/types'; +import { VMWizardNetworkWithWrappers } from '../../types'; + +export type VMWizardNetworkBundle = NetworkSimpleData & { + wizardNetworkData: VMWizardNetworkWithWrappers; +}; + +export type VMWizardNicRowActionOpts = { + wizardReduxID: string; + removeNIC?: (id: string) => void; + withProgress?: (promise: Promise) => void; +}; + +export type VMWizardNicRowCustomData = VMWizardNicRowActionOpts & { + columnClasses: string[]; + isDisabled: boolean; +}; diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/vm-wizard-nic-modal-enhanced.tsx b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/vm-wizard-nic-modal-enhanced.tsx new file mode 100644 index 00000000000..735fff25660 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/vm-wizard-nic-modal-enhanced.tsx @@ -0,0 +1,107 @@ +import * as React from 'react'; +import { connect } from 'react-redux'; +import { FirehoseResult } from '@console/internal/components/utils'; +import { createModalLauncher, ModalComponentProps } from '@console/internal/components/factory'; +import { K8sResourceKind } from '@console/internal/module/k8s'; +import { NetworkAttachmentDefinitionModel } from '../../../../models'; +import { NetworkWrapper } from '../../../../k8s/wrapper/vm/network-wrapper'; +import { NetworkInterfaceWrapper } from '../../../../k8s/wrapper/vm/network-interface-wrapper'; +import { iGetCommonData } from '../../selectors/immutable/selectors'; +import { + VMWizardNetwork, + VMWizardNetworkType, + VMWizardNetworkWithWrappers, + VMWizardProps, +} from '../../types'; +import { NICModal } from '../../../modals/nic-modal'; +import { iFirehoseResultToJS } from '../../../../utils/immutable'; +import { NetworkType } from '../../../../constants/vm'; +import { vmWizardActions } from '../../redux/actions'; +import { ActionType } from '../../redux/types'; +import { getNetworksWithWrappers } from '../../selectors/selectors'; + +const VMWizardNICModal: React.FC = (props) => { + const { + id, + type, + addUpdateNIC, + networks, + networkInterfaceWrapper = NetworkInterfaceWrapper.EMPTY, + networkWrapper = NetworkWrapper.EMPTY, + ...restProps + } = props; + + const usedInterfacesNames: Set = new Set( + networks + .map(({ networkInterfaceWrapper: nicWrapper }) => nicWrapper.getName()) + .filter((n) => n && n !== networkInterfaceWrapper.getName()), + ); + + const usedMultusNetworkNames: Set = new Set( + networks + .filter( + ({ networkWrapper: usedNetworkWrapper }) => + usedNetworkWrapper.getType() === NetworkType.MULTUS && + usedNetworkWrapper.getMultusNetworkName() !== networkWrapper.getMultusNetworkName(), + ) + .map(({ networkWrapper: usedNetworkWrapper }) => usedNetworkWrapper.getMultusNetworkName()), + ); + + const allowPodNetwork = + networkWrapper.isPodNetwork() || + !networks.find(({ networkWrapper: usedNetworkWrapper }) => usedNetworkWrapper.isPodNetwork()); + + return ( + { + addUpdateNIC({ + id, + type: type || VMWizardNetworkType.UI_INPUT, + networkInterface: resultNetworkInterfaceWrapper.asResource(), + network: resultNetworkWrapper.asResource(), + }); + return Promise.resolve(); + }} + /> + ); +}; + +type VMWizardNICModalProps = ModalComponentProps & { + id?: string; + type?: VMWizardNetworkType; + networkInterfaceWrapper?: NetworkInterfaceWrapper; + networkWrapper?: NetworkWrapper; + networks?: VMWizardNetworkWithWrappers[]; + nads?: FirehoseResult; + addUpdateNIC: (network: VMWizardNetwork) => void; +}; + +const stateToProps = (state, { wizardReduxID }) => { + const hasNADs = !!state.k8s.getIn(['RESOURCES', 'models', NetworkAttachmentDefinitionModel.kind]); + return { + hasNADs, + networks: getNetworksWithWrappers(state, wizardReduxID), + nads: iFirehoseResultToJS( + iGetCommonData(state, wizardReduxID, VMWizardProps.networkAttachmentDefinitions), + ), + }; +}; + +const dispatchToProps = (dispatch, { wizardReduxID }) => ({ + addUpdateNIC: (network: VMWizardNetwork) => { + dispatch(vmWizardActions[ActionType.UpdateNIC](wizardReduxID, network)); + }, +}); + +const VMWizardNICModalConnected = connect( + stateToProps, + dispatchToProps, +)(VMWizardNICModal); + +export const vmWizardNicModalEnhanced = createModalLauncher(VMWizardNICModalConnected); diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/vm-wizard-nic-row.tsx b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/vm-wizard-nic-row.tsx new file mode 100644 index 00000000000..3ac05c55974 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/networking-tab/vm-wizard-nic-row.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; +import * as _ from 'lodash'; +import { Kebab, KebabOption } from '@console/internal/components/utils'; +import { NicSimpleRow } from '../../../vm-nics/nic-row'; +import { VMWizardNetworkWithWrappers } from '../../types'; +import { VMWizardNetworkBundle, VMWizardNicRowActionOpts, VMWizardNicRowCustomData } from './types'; +import { vmWizardNicModalEnhanced } from './vm-wizard-nic-modal-enhanced'; + +const menuActionEdit = ( + { networkInterfaceWrapper, networkWrapper, id, type }: VMWizardNetworkWithWrappers, + { wizardReduxID, withProgress }: VMWizardNicRowActionOpts, +): KebabOption => ({ + label: 'Edit', + callback: () => + withProgress( + vmWizardNicModalEnhanced({ + wizardReduxID, + id, + type, + networkInterfaceWrapper, + networkWrapper, + }).result, + ), +}); + +const menuActionRemove = ( + { id }: VMWizardNetworkWithWrappers, + { withProgress, removeNIC }: VMWizardNicRowActionOpts, +): KebabOption => ({ + label: 'Delete', + callback: () => + withProgress( + new Promise((resolve) => { + removeNIC(id); + resolve(); + }), + ), +}); + +const getActions = ( + wizardNetworkData: VMWizardNetworkWithWrappers, + opts: VMWizardNicRowActionOpts, +) => { + const actions = [menuActionEdit, menuActionRemove]; + return actions.map((a) => a(wizardNetworkData, opts)); +}; + +export type VMWizardNicRowProps = { + obj: VMWizardNetworkBundle; + customData: VMWizardNicRowCustomData; + index: number; + style: object; +}; + +export const VMWizardNicRow: React.FC = ({ + obj: { name, wizardNetworkData, ...restData }, + customData: { isDisabled, columnClasses, removeNIC, withProgress, wizardReduxID }, + index, + style, +}) => { + return ( + + } + /> + ); +}; diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/result-tab/result-tab-row.scss b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/result-tab/result-tab-row.scss index ef24fa44448..2bd7f37e4a2 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/result-tab/result-tab-row.scss +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/result-tab/result-tab-row.scss @@ -10,8 +10,3 @@ .kubevirt-create-vm-modal__result-tab-row { text-align: left; } - -.kubevirt-create-vm-modal__result-tab-row--error { - background-color: #ffe6e6; - border-color: var(--pf-global--danger-color--200); -} diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/result-tab/result-tab-row.tsx b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/result-tab/result-tab-row.tsx index b5970fb500d..210be530190 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/result-tab/result-tab-row.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/result-tab/result-tab-row.tsx @@ -16,13 +16,7 @@ export const ResultTabRow: React.FC = ({ title, content, isEr 'kubevirt-create-vm-modal___result-tab-row-container--error': isError, })} > -
-        {content}
-      
+
{content}
); }; diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/result-tab/result-tab.tsx b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/result-tab/result-tab.tsx index df6458d6e28..5885c51172b 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/result-tab/result-tab.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/result-tab/result-tab.tsx @@ -10,7 +10,7 @@ import { RequestResultsPart } from './request-results-part'; import './result-tab.scss'; -export const ResultTabComponent: React.FC = ({ +const ResultTabComponent: React.FC = ({ wizardReduxID, isValid, isPending, diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/review-tab/networking-review.scss b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/review-tab/networking-review.scss new file mode 100644 index 00000000000..5ca7c223198 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/review-tab/networking-review.scss @@ -0,0 +1,14 @@ +@import '~patternfly/dist/sass/patternfly/color-variables'; + +.kubevirt-create-vm-modal__review-tab-networking { + dt { + color: $color-pf-black-600; + text-transform: capitalize; + } +} + +.kubevirt-create-vm-modal__review-tab-networking-simple-list { + list-style-type: none; + margin: 0; + padding: 0; +} diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/review-tab/networking-review.tsx b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/review-tab/networking-review.tsx new file mode 100644 index 00000000000..a0113af3b06 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/review-tab/networking-review.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import * as _ from 'lodash'; +import * as classNames from 'classnames'; +import { connect } from 'react-redux'; +import { VMWizardNetworkWithWrappers } from '../../types'; +import { getNetworksWithWrappers } from '../../selectors/selectors'; + +import './networking-review.scss'; + +const NetworkingReviewConnected: React.FC = ({ + networks, + className, +}) => { + return ( +
+
Network Interfaces
+
+
    + {networks.map(({ id, networkInterfaceWrapper, networkWrapper }) => ( +
  • + {_.compact([ + networkInterfaceWrapper.getName(), + networkInterfaceWrapper.getReadableModel(), + networkWrapper.getReadableName(), + networkInterfaceWrapper.getMACAddress(), + ]).join(' - ')} +
  • + ))} +
+
+
+ ); +}; + +type NetworkingTabComponentProps = { + networks: VMWizardNetworkWithWrappers[]; + className: string; +}; + +const stateToProps = (state, { wizardReduxID }) => ({ + networks: getNetworksWithWrappers(state, wizardReduxID), +}); + +export const NetworkingReview = connect(stateToProps)(NetworkingReviewConnected); diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/review-tab/review-tab.scss b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/review-tab/review-tab.scss index d707bd0f008..f1556509317 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/review-tab/review-tab.scss +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/review-tab/review-tab.scss @@ -1,3 +1,7 @@ .kubevirt-create-vm-modal__review-tab-title { margin-bottom: 1em; } + +.kubevirt-create-vm-modal__review-tab-lower-section { + margin-top: 20px; +} diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/review-tab/review-tab.tsx b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/review-tab/review-tab.tsx index 8a2c95985e2..3a93a987f2d 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/review-tab/review-tab.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/review-tab/review-tab.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { Title } from '@patternfly/react-core'; import { VMSettingsTab } from '../vm-settings-tab/vm-settings-tab'; +import { NetworkingReview } from './networking-review'; import './review-tab.scss'; @@ -11,6 +12,10 @@ export const ReviewTab: React.FC = ({ wizardReduxID }) => { Review and confirm your settings + ); }; diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/vm-settings-tab/user-templates.tsx b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/vm-settings-tab/user-templates.tsx index 88ad474aeeb..50d9e0942ba 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/vm-settings-tab/user-templates.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/tabs/vm-settings-tab/user-templates.tsx @@ -13,7 +13,7 @@ import { iGetName } from '../../selectors/immutable/selectors'; export const UserTemplates: React.FC = React.memo( ({ userTemplateField, userTemplates, commonTemplates, dataVolumes, onChange }) => { const data = iGetLoadedData(userTemplates); - const names = + const names: string[] = data && data .toIndexedSeq() diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/types.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/types.ts index 275c94a9c54..85cd108df90 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/types.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/types.ts @@ -1,11 +1,14 @@ import { FirehoseResult } from '@console/internal/components/utils'; import { TemplateKind } from '@console/internal/module/k8s'; import { getStringEnumValues } from '../../utils/types'; -import { VMKind } from '../../types/vm'; +import { V1Network, V1NetworkInterface, VMKind } from '../../types/vm'; +import { NetworkInterfaceWrapper } from '../../k8s/wrapper/vm/network-interface-wrapper'; +import { NetworkWrapper } from '../../k8s/wrapper/vm/network-wrapper'; +import { UINetworkInterfaceValidation } from '../../utils/validations/vm'; export enum VMWizardTab { // order important VM_SETTINGS = 'VM_SETTINGS', - NETWORKS = 'NETWORKS', + NETWORKING = 'NETWORKING', STORAGE = 'STORAGE', REVIEW = 'REVIEW', RESULT = 'RESULT', @@ -18,7 +21,7 @@ export enum VMWizardProps { virtualMachines = 'virtualMachines', userTemplates = 'userTemplates', commonTemplates = 'commonTemplates', - networkConfigs = 'networkConfigs', + networkAttachmentDefinitions = 'networkAttachmentDefinitions', storageClasses = 'storageClasses', persistentVolumeClaims = 'persistentVolumeClaims', dataVolumes = 'dataVolumes', @@ -67,7 +70,7 @@ export type ChangedCommonDataProp = | VMWizardProps.userTemplates | VMWizardProps.persistentVolumeClaims | VMWizardProps.commonTemplates - | VMWizardProps.networkConfigs + | VMWizardProps.networkAttachmentDefinitions | VMWizardProps.storageClasses; export type CommonDataProp = VMWizardProps.isCreateTemplate | ChangedCommonDataProp; @@ -81,7 +84,7 @@ export const DetectCommonDataChanges = new Set([ VMWizardProps.userTemplates, VMWizardProps.commonTemplates, VMWizardProps.persistentVolumeClaims, - VMWizardProps.networkConfigs, + VMWizardProps.networkAttachmentDefinitions, ]); export type CommonData = { @@ -106,3 +109,22 @@ export type CreateVMWizardComponentProps = { onResultsChanged: (results, isValid: boolean, isLocked: boolean, isPending: boolean) => void; lockTab: (tabID: VMWizardTab) => void; }; + +export enum VMWizardNetworkType { + TEMPLATE = 'TEMPLATE', + UI_DEFAULT_POD_NETWORK = 'UI_DEFAULT_POD_NETWORK', + UI_INPUT = 'UI_INPUT', +} + +export type VMWizardNetwork = { + id?: string; + type: VMWizardNetworkType; + network: V1Network; + networkInterface: V1NetworkInterface; + validation?: UINetworkInterfaceValidation; +}; + +export type VMWizardNetworkWithWrappers = VMWizardNetwork & { + networkInterfaceWrapper: NetworkInterfaceWrapper; + networkWrapper: NetworkWrapper; +}; diff --git a/frontend/packages/kubevirt-plugin/src/components/form/form-row.tsx b/frontend/packages/kubevirt-plugin/src/components/form/form-row.tsx index 7ff3f7f5622..9b2080d1a99 100644 --- a/frontend/packages/kubevirt-plugin/src/components/form/form-row.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/form/form-row.tsx @@ -16,6 +16,7 @@ export const FormRow: React.FC = ({ isLoading, validationMessage, validationType, + validation, children, }) => { if (isHidden) { @@ -27,8 +28,8 @@ export const FormRow: React.FC = ({ label={title} isRequired={isRequired} fieldId={fieldId} - isValid={validationType !== ValidationErrorType.Error} - helperTextInvalid={validationMessage} + isValid={((validation && validation.type) || validationType) !== ValidationErrorType.Error} + helperTextInvalid={(validation && validation.message) || validationMessage} > {help && ( @@ -63,5 +64,9 @@ type FormRowProps = { isLoading?: boolean; validationMessage?: string; validationType?: ValidationErrorType; + validation?: { + message?: string; + type?: ValidationErrorType; + }; children?: React.ReactNode; }; diff --git a/frontend/packages/kubevirt-plugin/src/components/form/k8s-resource-select-row.tsx b/frontend/packages/kubevirt-plugin/src/components/form/k8s-resource-select-row.tsx new file mode 100644 index 00000000000..322389ac1d2 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/form/k8s-resource-select-row.tsx @@ -0,0 +1,79 @@ +import { FirehoseResult } from '@console/internal/components/utils'; +import { K8sKind, K8sResourceKind } from '@console/internal/module/k8s'; +import * as React from 'react'; +import { FormSelect, FormSelectOption } from '@patternfly/react-core'; +import { getName } from '@console/shared/src'; +import { ValidationErrorType, ValidationObject } from '../../utils/validations/types'; +import { getLoadedData, getLoadError, isLoaded } from '../../utils'; +import { ignoreCaseSort } from '../../utils/sort'; +import { FormRow } from './form-row'; +import { asFormSelectValue, FormSelectPlaceholderOption } from './form-select-placeholder-option'; + +type K8sResourceSelectProps = { + id: string; + isDisabled: boolean; + isPlaceholderDisabled?: boolean; + hasPlaceholder?: boolean; + data?: FirehoseResult; + name?: string; + onChange: (name: string) => void; + model: K8sKind; + title?: string; + validation?: ValidationObject; + filter?: (obj: K8sResourceKind) => boolean; +}; + +export const K8sResourceSelectRow: React.FC = ({ + id, + isDisabled, + isPlaceholderDisabled, + hasPlaceholder, + data, + onChange, + name, + model, + title, + validation, + filter, +}) => { + const isLoading = !isLoaded(data); + const loadError = getLoadError(data, model); + + let loadedData = getLoadedData(data, []); + + if (filter) { + loadedData = loadedData.filter(filter); + } + + return ( + + + {hasPlaceholder && ( + + )} + {ignoreCaseSort(loadedData, ['metadata', 'name']).map((entity) => { + const selectName = getName(entity); + return ; + })} + + + ); +}; diff --git a/frontend/packages/kubevirt-plugin/src/components/form/size-unit-form-row.scss b/frontend/packages/kubevirt-plugin/src/components/form/size-unit-form-row.scss new file mode 100644 index 00000000000..780e741e45e --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/form/size-unit-form-row.scss @@ -0,0 +1,7 @@ +.kubevirt-size-unit-form-row__size { + max-width: 100% !important; +} + +.kubevirt-size-unit-form-row__unit { + width: 6em; +} diff --git a/frontend/packages/kubevirt-plugin/src/components/form/size-unit-form-row.tsx b/frontend/packages/kubevirt-plugin/src/components/form/size-unit-form-row.tsx new file mode 100644 index 00000000000..227d1ceaa15 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/form/size-unit-form-row.tsx @@ -0,0 +1,72 @@ +import * as React from 'react'; +import { FormSelect, FormSelectOption, Split, SplitItem } from '@patternfly/react-core'; +import { ValidationObject } from '../../utils/validations/types'; +import { prefixedID } from '../../utils'; +import { getStringEnumValues } from '../../utils/types'; +import { FormRow } from './form-row'; +import { Integer } from './integer/integer'; + +import './size-unit-form-row.scss'; + +export enum BinaryUnit { + Mi = 'Mi', + Gi = 'Gi', + Ti = 'Ti', +} + +type SizeUnitFormRowProps = { + size: string; + title?: string; + unit: BinaryUnit; + validation: ValidationObject; + id?: string; + isDisabled?: boolean; + isRequired?: boolean; + onSizeChanged: (size: string) => void; + onUnitChanged: (unit: BinaryUnit) => void; +}; +export const SizeUnitFormRow: React.FC = ({ + title = 'Size', + size, + unit, + validation, + id, + isRequired, + isDisabled, + onSizeChanged, + onUnitChanged, +}) => ( + + + + onSizeChanged(v), [onSizeChanged])} + /> + + + onUnitChanged(u as BinaryUnit), [onUnitChanged])} + value={unit} + id={prefixedID(id, 'unit')} + isDisabled={isDisabled} + > + {getStringEnumValues(BinaryUnit).map((u) => { + return ; + })} + + + + +); diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/delete-device-modal/delete-device-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/delete-device-modal/delete-device-modal.tsx index 6db91868461..a564a5dfdd9 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/delete-device-modal/delete-device-modal.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/delete-device-modal/delete-device-modal.tsx @@ -11,7 +11,7 @@ import { getName } from '@console/shared'; import { VMLikeEntityKind } from '../../../types'; import { getVMLikeModel } from '../../../selectors/vm'; import { getRemoveDiskPatches } from '../../../k8s/patches/vm/vm-disk-patches'; -import { getRemoveNicPatches } from '../../../k8s/patches/vm/vm-nic-patches'; +import { getRemoveNICPatches } from '../../../k8s/patches/vm/vm-nic-patches'; export enum DeviceType { NIC = 'NIC', @@ -42,7 +42,7 @@ export const DeleteDeviceModal = withHandlePromise((props: DeleteDeviceModalProp patches = getRemoveDiskPatches(vmLikeEntity, device); break; case DeviceType.NIC: - patches = getRemoveNicPatches(vmLikeEntity, device); + patches = getRemoveNICPatches(vmLikeEntity, device); break; default: return; diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/disk-modal-enhanced.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/disk-modal-enhanced.tsx new file mode 100644 index 00000000000..caa4170de67 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/disk-modal-enhanced.tsx @@ -0,0 +1,152 @@ +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Firehose, FirehoseResult } from '@console/internal/components/utils'; +import { createModalLauncher, ModalComponentProps } from '@console/internal/components/factory'; +import { k8sPatch } from '@console/internal/module/k8s'; +import { getName, getNamespace } from '@console/shared/src'; +import { + NamespaceModel, + PersistentVolumeClaimModel, + ProjectModel, + StorageClassModel, +} from '@console/internal/models'; +import { getLoadedData } from '../../../utils'; +import { asVM, getVMLikeModel, getDisks, getDataVolumeTemplates } from '../../../selectors/vm'; +import { VMLikeEntityKind } from '../../../types'; +import { getSimpleName } from '../../../selectors/utils'; +import { DiskWrapper } from '../../../k8s/wrapper/vm/disk-wrapper'; +import { VolumeWrapper } from '../../../k8s/wrapper/vm/volume-wrapper'; +import { DataVolumeWrapper } from '../../../k8s/wrapper/vm/data-volume-wrapper'; +import { getUpdateDiskPatches } from '../../../k8s/patches/vm/vm-disk-patches'; +import { DiskModal } from './disk-modal'; + +const DiskModalFirehoseComponent: React.FC = (props) => { + const { disk, volume, dataVolume, vmLikeEntity, vmLikeEntityLoading, ...restProps } = props; + + const vmLikeFinal = getLoadedData(vmLikeEntityLoading, vmLikeEntity); // default old snapshot before loading a new one + const vm = asVM(vmLikeFinal); + + const diskWrapper = disk ? DiskWrapper.initialize(disk) : DiskWrapper.EMPTY; + const volumeWrapper = volume ? VolumeWrapper.initialize(volume) : VolumeWrapper.EMPTY; + const dataVolumeWrapper = dataVolume + ? DataVolumeWrapper.initialize(dataVolume) + : DataVolumeWrapper.EMPTY; + + const usedDiskNames: Set = new Set( + getDisks(vm) + .map(getSimpleName) + .filter((n) => n && n !== diskWrapper.getName()), + ); + + const usedPVCNames: Set = new Set( + getDataVolumeTemplates(vm) + .map((dv) => getName(dv)) + .filter((n) => n && n !== dataVolumeWrapper.getName()), + ); + + const onSubmit = async (resultDisk, resultVolume, resultDataVolume) => + k8sPatch( + getVMLikeModel(vmLikeEntity), + vmLikeEntity, + await getUpdateDiskPatches(vmLikeEntity, { + disk: DiskWrapper.mergeWrappers(diskWrapper, resultDisk).asResource(), + volume: VolumeWrapper.mergeWrappers(volumeWrapper, resultVolume).asResource(), + dataVolume: + resultDataVolume && + DataVolumeWrapper.mergeWrappers(dataVolumeWrapper, resultDataVolume).asResource(), + oldDiskName: diskWrapper.getName(), + oldVolumeName: volumeWrapper.getName(), + oldDataVolumeName: dataVolumeWrapper.getName(), + }), + ); + + return ( + + ); +}; + +type DiskModalFirehoseComponentProps = ModalComponentProps & { + disk?: any; + volume?: any; + dataVolume?: any; + namespace: string; + onNamespaceChanged: (namespace: string) => void; + storageClasses?: FirehoseResult; + persistentVolumeClaims?: FirehoseResult; + vmLikeEntityLoading?: FirehoseResult; + vmLikeEntity: VMLikeEntityKind; +}; + +const DiskModalFirehose: React.FC = (props) => { + const { vmLikeEntity, useProjects, ...restProps } = props; + + const vmName = getName(vmLikeEntity); + const vmNamespace = getNamespace(vmLikeEntity); + + const [namespace, setNamespace] = React.useState(vmNamespace); + + const resources = [ + { + kind: (useProjects ? ProjectModel : NamespaceModel).kind, + isList: true, + prop: 'namespaces', + }, + { + kind: getVMLikeModel(vmLikeEntity).kind, + name: vmName, + namespace: vmNamespace, + prop: 'vmLikeEntityLoading', + }, + { + kind: StorageClassModel.kind, + isList: true, + prop: 'storageClasses', + }, + { + kind: PersistentVolumeClaimModel.kind, + isList: true, + namespace, + prop: 'persistentVolumeClaims', + }, + ]; + + return ( + + setNamespace(n)} + {...restProps} + /> + + ); +}; + +type DiskModalFirehoseProps = ModalComponentProps & { + vmLikeEntity: VMLikeEntityKind; + disk?: any; + volume?: any; + dataVolume?: any; + useProjects: boolean; +}; + +const diskModalStateToProps = ({ k8s }) => { + const useProjects = k8s.hasIn(['RESOURCES', 'models', ProjectModel.kind]); + return { + useProjects, + }; +}; + +const DiskModalConnected = connect(diskModalStateToProps)(DiskModalFirehose); + +export const diskModalEnhanced = createModalLauncher(DiskModalConnected); diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/disk-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/disk-modal.tsx new file mode 100644 index 00000000000..a709618e166 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/disk-modal.tsx @@ -0,0 +1,355 @@ +import * as React from 'react'; +import { Form, FormSelect, FormSelectOption, TextInput } from '@patternfly/react-core'; +import { + FirehoseResult, + HandlePromiseProps, + validate, + withHandlePromise, +} from '@console/internal/components/utils'; +import { + createModalLauncher, + ModalBody, + ModalComponentProps, + ModalTitle, +} from '@console/internal/components/factory'; +import { K8sResourceKind } from '@console/internal/module/k8s'; +import { + NamespaceModel, + PersistentVolumeClaimModel, + StorageClassModel, +} from '@console/internal/models'; +import { getName } from '@console/shared/src'; +import { getLoadedData, prefixedID } from '../../../utils'; +import { validateDisk } from '../../../utils/validations/vm'; +import { isValidationError } from '../../../utils/validations/common'; +import { FormRow } from '../../form/form-row'; +import { + asFormSelectValue, + FormSelectPlaceholderOption, +} from '../../form/form-select-placeholder-option'; +import { getDialogUIError, getSequenceName } from '../../../utils/strings'; +import { ModalFooter } from '../modal/modal-footer'; +import { useShowErrorToggler } from '../../../hooks/use-show-error-toggler'; +import { DiskWrapper } from '../../../k8s/wrapper/vm/disk-wrapper'; +import { DataVolumeWrapper } from '../../../k8s/wrapper/vm/data-volume-wrapper'; +import { VolumeWrapper } from '../../../k8s/wrapper/vm/volume-wrapper'; +import { DiskBus, DiskType } from '../../../constants/vm/storage'; +import { getPvcStorageSize } from '../../../selectors/pvc/selectors'; +import { K8sResourceSelectRow } from '../../form/k8s-resource-select-row'; +import { SizeUnitFormRow, BinaryUnit } from '../../form/size-unit-form-row'; +import { StorageUISource } from './storage-ui-source'; + +export const DiskModal = withHandlePromise((props: DiskModalProps) => { + const { + storageClasses, + usedPVCNames, + persistentVolumeClaims, + vmName, + vmNamespace, + namespace, + namespaces, + onNamespaceChanged, + usedDiskNames, + onSubmit, + inProgress, + errorMessage, + handlePromise, + close, + cancel, + } = props; + const asId = prefixedID.bind(null, 'disk'); + const disk = props.disk || DiskWrapper.EMPTY; + const volume = props.volume || VolumeWrapper.EMPTY; + const dataVolume = props.dataVolume || DataVolumeWrapper.EMPTY; + const isEditing = disk !== DiskWrapper.EMPTY; + + const [source, setSource] = React.useState( + StorageUISource.fromTypes(volume.getType(), dataVolume.getType()) || StorageUISource.BLANK, + ); + + const [url, setURL] = React.useState(dataVolume.getURL); + + const [containerImage, setContainerImage] = React.useState( + volume.getContainerImage() || '', + ); + + const [pvcName, setPVCName] = React.useState(source.getPVCName(volume, dataVolume)); + + const [name, setName] = React.useState( + disk.getName() || getSequenceName('disk', usedDiskNames), + ); + const [bus, setBus] = React.useState( + disk.getDiskBus() || (isEditing ? null : DiskBus.VIRTIO), + ); + const [storageClassName, setStorageClassName] = React.useState( + dataVolume.getStorageClassName(), + ); + + const [size, setSize] = React.useState(`${dataVolume.getSize().value}`); + const [unit, setUnit] = React.useState(dataVolume.getSize().unit || BinaryUnit.Gi); + + const resultDisk = DiskWrapper.initializeFromSimpleData({ + name, + bus, + type: DiskType.DISK, + }); + + const resultDataVolumeName = prefixedID(vmName, name); + const resultVolume = VolumeWrapper.initializeFromSimpleData( + { + name, + type: source.getVolumeType(), + typeData: { + name: resultDataVolumeName, + claimName: pvcName, + image: containerImage, + }, + }, + { sanitizeTypeData: true }, + ); + + let resultDataVolume; + if (source.requiresDatavolume()) { + resultDataVolume = DataVolumeWrapper.initializeFromSimpleData( + { + name: resultDataVolumeName, + storageClassName: storageClassName || undefined, + type: source.getDataVolumeSourceType(), + size, + unit, + typeData: { name: pvcName, namespace, url }, + }, + { sanitizeTypeData: true }, + ); + } + + const { + validations: { + name: nameValidation, + size: sizeValidation, + container: containerValidation, + pvc: pvcValidation, + url: urlValidation, + }, + isValid, + hasAllRequiredFilled, + } = validateDisk(resultDisk, resultVolume, resultDataVolume, { usedDiskNames, usedPVCNames }); + + const [showUIError, setShowUIError] = useShowErrorToggler(false, isValid, isValid); + + const submit = (e) => { + e.preventDefault(); + + if (isValid) { + // eslint-disable-next-line promise/catch-or-return + handlePromise(onSubmit(resultDisk, resultVolume, resultDataVolume)).then(close); + } else { + setShowUIError(true); + } + }; + + const onSourceChanged = (uiSource) => { + setSize(''); + setUnit('Gi'); + setURL(''); + setPVCName(''); + setContainerImage(''); + setStorageClassName(''); + onNamespaceChanged(vmNamespace); + setSource(StorageUISource.fromString(uiSource)); + }; + + const onPVCChanged = (newPVCName) => { + setPVCName(newPVCName); + if (source === StorageUISource.ATTACH_CLONED_DISK) { + const newSizeBundle = getPvcStorageSize( + getLoadedData(persistentVolumeClaims).find((p) => getName(p) === newPVCName), + ); + const [newSize, newUnit] = validate.split(newSizeBundle); + setSize(newSize); + setUnit(newUnit); + } + }; + + return ( +
+ {isEditing ? 'Edit' : 'Add'} Disk + +
+ + + {StorageUISource.getAll().map((uiType) => { + return ( + + ); + })} + + + {source.requiresURL() && ( + + setURL(v)} + /> + + )} + {source.requiresContainerImage() && ( + + setContainerImage(v)} + /> + + )} + {source.requiresNamespace() && ( + { + setPVCName(''); + onNamespaceChanged(sc); + }} + /> + )} + {source.requiresPVC() && ( + !(usedPVCNames && usedPVCNames.has(getName(p)))} + /> + )} + + setName(v), [setName])} + /> + + {source.requiresDatavolume() && ( + + )} + + setBus(DiskBus.fromString(diskBus)), [ + setBus, + ])} + value={asFormSelectValue(bus)} + id={asId('interface')} + isDisabled={inProgress} + > + + {DiskBus.getAll().map((b) => { + return ( + + ); + })} + + + {source.requiresDatavolume() && ( + setStorageClassName(sc)} + /> + )} + +
+ { + e.stopPropagation(); + cancel(); + }} + /> +
+ ); +}); + +export type DiskModalProps = { + disk?: DiskWrapper; + volume?: VolumeWrapper; + dataVolume?: DataVolumeWrapper; + onSubmit: ( + disk: DiskWrapper, + volume: VolumeWrapper, + dataVolume: DataVolumeWrapper, + ) => Promise; + namespaces?: FirehoseResult; + storageClasses?: FirehoseResult; + persistentVolumeClaims?: FirehoseResult; + vmName: string; + vmNamespace: string; + namespace: string; + onNamespaceChanged: (namespace: string) => void; + usedDiskNames: Set; + usedPVCNames: Set; +} & ModalComponentProps & + HandlePromiseProps; + +export const diskModal = createModalLauncher(DiskModal); diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/index.ts b/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/index.ts new file mode 100644 index 00000000000..bec8ea64614 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/index.ts @@ -0,0 +1 @@ +export * from './disk-modal'; diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/storage-ui-source.ts b/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/storage-ui-source.ts new file mode 100644 index 00000000000..6999382c552 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/storage-ui-source.ts @@ -0,0 +1,97 @@ +/* eslint-disable lines-between-class-members */ + +import { ValueEnum, VolumeType } from '../../../constants'; +import { DataVolumeSourceType } from '../../../constants/vm/storage'; +import { VolumeWrapper } from '../../../k8s/wrapper/vm/volume-wrapper'; +import { DataVolumeWrapper } from '../../../k8s/wrapper/vm/data-volume-wrapper'; + +export class StorageUISource extends ValueEnum { + static readonly BLANK = new StorageUISource( + 'Blank', + VolumeType.DATA_VOLUME, + DataVolumeSourceType.BLANK, + ); + static readonly URL = new StorageUISource( + 'URL', + VolumeType.DATA_VOLUME, + DataVolumeSourceType.HTTP, + ); + static readonly CONTAINER = new StorageUISource('Container', VolumeType.CONTAINER_DISK); + static readonly ATTACH_CLONED_DISK = new StorageUISource( + 'Attach Cloned Disk', + VolumeType.DATA_VOLUME, + DataVolumeSourceType.PVC, + ); + static readonly ATTACH_DISK = new StorageUISource( + 'Attach Disk', + VolumeType.PERSISTENT_VOLUME_CLAIM, + undefined, + ); + + private readonly volumeType: VolumeType; + private readonly dataVolumeSourceType: DataVolumeSourceType; + + private static readonly ALL = Object.freeze( + ValueEnum.getAllClassEnumProperties(StorageUISource), + ); + + private static readonly stringMapper = StorageUISource.ALL.reduce( + (accumulator, volumeType: StorageUISource) => ({ + ...accumulator, + [volumeType.value]: volumeType, + }), + {}, + ); + + protected constructor( + value: string, + volumeType: VolumeType, + dataVolumeSourceType?: DataVolumeSourceType, + ) { + super(value); + this.volumeType = volumeType; + this.dataVolumeSourceType = dataVolumeSourceType; + } + + static getAll = () => StorageUISource.ALL; + + static fromSerialized = (volumeType: { value: string }): StorageUISource => + StorageUISource.fromString(volumeType && volumeType.value); + + static fromString = (model: string): StorageUISource => StorageUISource.stringMapper[model]; + + static fromTypes = (volumeType: VolumeType, dataVolumeSourceType?: DataVolumeSourceType) => + StorageUISource.ALL.find( + (storageUIType) => + storageUIType.volumeType == volumeType && // eslint-disable-line eqeqeq + storageUIType.dataVolumeSourceType == dataVolumeSourceType, // eslint-disable-line eqeqeq + ); + + getVolumeType = () => this.volumeType; + + getDataVolumeSourceType = () => this.dataVolumeSourceType; + + requiresPVC = () => + this === StorageUISource.ATTACH_DISK || this === StorageUISource.ATTACH_CLONED_DISK; + + requiresContainerImage = () => this === StorageUISource.CONTAINER; + + requiresURL = () => this === StorageUISource.URL; + + requiresDatavolume = () => !!this.dataVolumeSourceType; + + requiresNamespace = () => this === StorageUISource.ATTACH_CLONED_DISK; + + isEditingSupported = () => !this.dataVolumeSourceType; + + getPVCName = (volume: VolumeWrapper, dataVolume: DataVolumeWrapper) => { + if (this === StorageUISource.ATTACH_DISK) { + return volume.getPersistentVolumeClaimName(); + } + if (this === StorageUISource.ATTACH_CLONED_DISK) { + return dataVolume.getPesistentVolumeClaimName(); + } + + return null; + }; +} diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/nic-modal/nic-modal-enhanced.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/nic-modal/nic-modal-enhanced.tsx index 496a4612f31..cff6b63010b 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/nic-modal/nic-modal-enhanced.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/nic-modal/nic-modal-enhanced.tsx @@ -10,7 +10,7 @@ import { NetworkType } from '../../../constants/vm'; import { getInterfaces, getUsedNetworks, asVM, getVMLikeModel } from '../../../selectors/vm'; import { NetworkInterfaceWrapper } from '../../../k8s/wrapper/vm/network-interface-wrapper'; import { VMLikeEntityKind } from '../../../types'; -import { getAddNicPatches } from '../../../k8s/patches/vm/vm-nic-patches'; +import { getUpdateNICPatches } from '../../../k8s/patches/vm/vm-nic-patches'; import { getSimpleName } from '../../../selectors/utils'; import { NetworkWrapper } from '../../../k8s/wrapper/vm/network-wrapper'; import { NICModal } from './nic-modal'; @@ -50,7 +50,7 @@ const NICModalFirehoseComponent: React.FC = (pro k8sPatch( getVMLikeModel(vmLikeEntity), vmLikeEntity, - getAddNicPatches(vmLikeEntity, { + getUpdateNICPatches(vmLikeEntity, { nic: NetworkInterfaceWrapper.mergeWrappers( nicWrapper, resultNetworkInterfaceWrapper, @@ -121,13 +121,13 @@ type NICModalFirehoseProps = ModalComponentProps & { hasNADs: boolean; }; -const cloneVMModalStateToProps = ({ k8s }) => { +const nicModalStateToProps = ({ k8s }) => { const hasNADs = !!k8s.getIn(['RESOURCES', 'models', NetworkAttachmentDefinitionModel.kind]); return { hasNADs, }; }; -const NICModalConnected = connect(cloneVMModalStateToProps)(NICModalFirehose); +const NICModalConnected = connect(nicModalStateToProps)(NICModalFirehose); export const nicModalEnhanced = createModalLauncher(NICModalConnected); diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/nic-modal/nic-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/nic-modal/nic-modal.tsx index 46a15f25cfd..ae4643987e2 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/nic-modal/nic-modal.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/nic-modal/nic-modal.tsx @@ -94,17 +94,19 @@ export const Network: React.FC = ({ : '--- Select Network Definition ---' } /> - {ignoreCaseSort(networkChoices, ['readableName']).map((networkWrapper: NetworkWrapper) => { - const readableName = networkWrapper.getReadableName(); - return ( - - ); - })} + {ignoreCaseSort(networkChoices, undefined, (n) => n.getReadableName()).map( + (networkWrapper: NetworkWrapper) => { + const readableName = networkWrapper.getReadableName(); + return ( + + ); + }, + )} ); @@ -112,7 +114,6 @@ export const Network: React.FC = ({ export const NICModal = withHandlePromise((props: NICModalProps) => { const { - network, nads, usedInterfacesNames, usedMultusNetworkNames, @@ -126,6 +127,7 @@ export const NICModal = withHandlePromise((props: NICModalProps) => { } = props; const asId = prefixedID.bind(null, 'nic'); const nic = props.nic || NetworkInterfaceWrapper.EMPTY; + const network = props.network || NetworkWrapper.EMPTY; const isEditing = nic !== NetworkInterfaceWrapper.EMPTY; const [name, setName] = React.useState( @@ -193,8 +195,7 @@ export const NICModal = withHandlePromise((props: NICModalProps) => { fieldId={asId('name')} isRequired isLoading={!usedInterfacesNames} - validationMessage={nameValidation && nameValidation.message} - validationType={nameValidation && nameValidation.type} + validation={nameValidation} > { { }); export type NICModalProps = { - nic: NetworkInterfaceWrapper; - network: NetworkWrapper; + nic?: NetworkInterfaceWrapper; + network?: NetworkWrapper; onSubmit: (networkInterface: NetworkInterfaceWrapper, network: NetworkWrapper) => Promise; nads?: FirehoseResult; usedInterfacesNames: Set; diff --git a/frontend/packages/kubevirt-plugin/src/components/table/validation-cell.scss b/frontend/packages/kubevirt-plugin/src/components/table/validation-cell.scss new file mode 100644 index 00000000000..681821d32b2 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/table/validation-cell.scss @@ -0,0 +1,3 @@ +.kubevirt-nic-row__cell--error { + color: var(--pf-global--danger-color--100); +} diff --git a/frontend/packages/kubevirt-plugin/src/components/table/validation-cell.tsx b/frontend/packages/kubevirt-plugin/src/components/table/validation-cell.tsx new file mode 100644 index 00000000000..9ee22043adc --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/table/validation-cell.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import * as classNames from 'classnames'; +import { ValidationErrorType, ValidationObject } from '../../utils/validations/types'; + +import './validation-cell.scss'; + +export type SimpleCellProps = { + children?: React.ReactNode; + validation?: ValidationObject; +}; + +export const ValidationCell: React.FC = ({ children, validation }) => { + return ( + <> + {children} + {validation && validation.type !== ValidationErrorType.TrivialError && ( +
+ {validation.message} +
+ )} + + ); +}; diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-disks/_create-device-row.scss b/frontend/packages/kubevirt-plugin/src/components/vm-disks/_create-device-row.scss deleted file mode 100644 index f036d04d019..00000000000 --- a/frontend/packages/kubevirt-plugin/src/components/vm-disks/_create-device-row.scss +++ /dev/null @@ -1,21 +0,0 @@ -.kubevirt-vm-create-device-row__cell_addendum { - margin-left: 0.5em; - width: 2em; -} - -.kubevirt-vm-create-device-row__cell_field--with-addendum { - width: calc(100% - 2.5em) !important; - display: inline-block !important; -} - -.kubevirt-vm-create-device-row__cell--no_bottom { - margin-bottom: 0; -} - -.kubevirt-vm-create-device-row__confirmation-buttons { - width: 90px; -} - -.kubevirt-vm-create-device-error { - margin-bottom: 1.5em; -} diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-disks/create-disk-row.tsx b/frontend/packages/kubevirt-plugin/src/components/vm-disks/create-disk-row.tsx deleted file mode 100644 index 4a8a01975b9..00000000000 --- a/frontend/packages/kubevirt-plugin/src/components/vm-disks/create-disk-row.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import * as React from 'react'; -import { - Text, - Integer, - Dropdown, - CancelAcceptButtons, - getStorageClassConfigMap, -} from 'kubevirt-web-ui-components'; -import { TableData, TableRow } from '@console/internal/components/factory'; -import { Firehose, FirehoseResult, LoadingInline } from '@console/internal/components/utils'; -import { HelpBlock, FormGroup } from 'patternfly-react'; -import { StorageClassModel } from '@console/internal/models'; -import { getName } from '@console/shared'; -import { useSafetyFirst } from '@console/internal/components/safety-first'; -import { k8sGet, k8sPatch, K8sResourceKind } from '@console/internal/module/k8s'; -import { getVmPreferableDiskBus, getVMLikeModel } from '../../selectors/vm'; -import { getAddDiskPatches } from '../../k8s/patches/vm/vm-disk-patches'; -import { VMLikeEntityKind } from '../../types'; -import { validateDiskName } from '../../utils/validations/vm'; -import { GENERAL_ERROR_MSG } from '../../utils/validations/strings'; -import { ValidationErrorType } from '../../utils/validations/types'; -import { getResource } from '../../utils'; -import { VMDiskRowProps } from './types'; -import './_create-device-row.scss'; - -const createDisk = async ({ - vmLikeEntity, - disk, -}: { - vmLikeEntity: VMLikeEntityKind; - disk: any; -}): Promise => { - const storageClassConfigMap = await getStorageClassConfigMap({ k8sGet }); - return k8sPatch( - getVMLikeModel(vmLikeEntity), - vmLikeEntity, - getAddDiskPatches(vmLikeEntity, disk, storageClassConfigMap), - ); -}; - -type StorageClassColumn = { - storageClass: string; - onChange: (string) => void; - storageClasses: FirehoseResult; - creating: boolean; -}; - -const StorageClassColumn: React.FC = ({ - storageClass, - onChange, - storageClasses, - creating, -}) => { - if (storageClasses.loaded) { - const loadedClasses = storageClasses.data; - const storageClassValue = - storageClass || - (loadedClasses.length === 0 - ? '--- No Storage Class Available ---' - : '--- Select Storage Class ---'); - return ( - getName(sc))} - value={storageClassValue} - onChange={onChange} - disabled={loadedClasses.length === 0 || creating} - /> - ); - } - return ; -}; - -type CreateDiskRowProps = VMDiskRowProps & { storageClasses?: FirehoseResult }; - -export const CreateDiskRow: React.FC = ({ - storageClasses, - customData: { vm, vmLikeEntity, diskLookup, onCreateRowDismiss, onCreateRowError, forceRerender }, - index, - style, -}) => { - const [creating, setCreating] = useSafetyFirst(false); - const [name, setName] = React.useState(''); - const [size, setSize] = React.useState(''); - const [storageClass, setStorageClass] = React.useState(null); - - const id = 'create-disk-row'; - - const nameError = validateDiskName(name, diskLookup); - const isValid = !nameError && size; - - const bus = getVmPreferableDiskBus(vm); - return ( - - - - { - setName(v); - forceRerender(); - }} - value={name} - /> - - {nameError && nameError.type === ValidationErrorType.Error && nameError.message} - - - - - - Gi - - {bus} - - - - - { - setCreating(true); - createDisk({ vmLikeEntity, disk: { name, size, bus, storageClass } }) - .then(onCreateRowDismiss) - .catch((error) => { - onCreateRowError((error && error.message) || GENERAL_ERROR_MSG); - setCreating(false); - }); - }} - disabled={!isValid} - /> - - - ); -}; - -export const CreateDiskRowFirehose: React.FC = (props) => { - const resources = [ - getResource(StorageClassModel, { - prop: 'storageClasses', - }), - ]; - - return ( - - - - ); -}; diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-disks/disk-row.tsx b/frontend/packages/kubevirt-plugin/src/components/vm-disks/disk-row.tsx index 8ba6d208dfb..2ba4dc827a9 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-disks/disk-row.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vm-disks/disk-row.tsx @@ -8,21 +8,62 @@ import { } from '@console/internal/components/utils'; import { getDeletetionTimestamp, DASH } from '@console/shared'; import { TemplateModel } from '@console/internal/models'; -import { BUS_VIRTIO } from '../../constants/vm'; import { deleteDeviceModal, DeviceType } from '../modals/delete-device-modal'; import { VMLikeEntityKind } from '../../types'; -import { getDiskBus, isVM } from '../../selectors/vm'; +import { asVM, isVM, isVMRunning } from '../../selectors/vm'; import { VirtualMachineModel } from '../../models'; -import { VMDiskRowProps } from './types'; +import { dimensifyRow } from '../../utils/table'; +import { ValidationCell } from '../table/validation-cell'; +import { VMNicRowActionOpts } from '../vm-nics/types'; +import { diskModalEnhanced } from '../modals/disk-modal/disk-modal-enhanced'; +import { + StorageBundle, + StorageSimpleData, + StorageSimpleDataValidation, + VMStorageRowActionOpts, + VMStorageRowCustomData, +} from './types'; -const menuActionDelete = (vmLikeEntity: VMLikeEntityKind, disk): KebabOption => ({ +const menuActionEdit = ( + disk, + volume, + dataVolume, + vmLikeEntity: VMLikeEntityKind, + { withProgress }: VMNicRowActionOpts, +): KebabOption => ({ + label: 'Edit', + callback: () => + withProgress( + diskModalEnhanced({ + vmLikeEntity, + disk, + volume, + dataVolume, + }).result, + ), + accessReview: asAccessReview( + isVM(vmLikeEntity) ? VirtualMachineModel : TemplateModel, + vmLikeEntity, + 'patch', + ), +}); + +const menuActionDelete = ( + disk, + volume, + dataVolume, + vmLikeEntity: VMLikeEntityKind, + { withProgress }: VMNicRowActionOpts, +): KebabOption => ({ label: 'Delete', callback: () => - deleteDeviceModal({ - deviceType: DeviceType.DISK, - device: disk, - vmLikeEntity, - }), + withProgress( + deleteDeviceModal({ + deviceType: DeviceType.DISK, + device: disk, + vmLikeEntity, + }).result, + ), accessReview: asAccessReview( isVM(vmLikeEntity) ? VirtualMachineModel : TemplateModel, vmLikeEntity, @@ -30,35 +71,103 @@ const menuActionDelete = (vmLikeEntity: VMLikeEntityKind, disk): KebabOption => ), }); -const getActions = (vmLikeEntity: VMLikeEntityKind, disk) => { - const actions = [menuActionDelete]; - return actions.map((a) => a(vmLikeEntity, disk)); +const getActions = ( + disk, + volume, + dataVolume, + vmLikeEntity: VMLikeEntityKind, + opts: VMStorageRowActionOpts, +) => { + const actions = []; + if (isVMRunning(asVM(vmLikeEntity))) { + return actions; + } + if (opts.isEditingEnabled) { + actions.push(menuActionEdit); + } + actions.push(menuActionDelete); + return actions.map((a) => a(disk, volume, dataVolume, vmLikeEntity, opts)); }; -export const DiskRow: React.FC = ({ - obj: { disk, size, storageClass }, - customData: { vmLikeEntity }, +export type VMDiskSimpleRowProps = { + data: StorageSimpleData; + validation?: StorageSimpleDataValidation; + columnClasses: string[]; + actionsComponent: React.ReactNode; + index: number; + style: object; +}; + +export const DiskSimpleRow: React.FC = ({ + data: { name, size, diskInterface, storageClass }, + validation = {}, + columnClasses, + actionsComponent, index, style, }) => { - const diskName = disk.name; - const sizeColumn = size === undefined ? : size; - const storageColumn = storageClass === undefined ? : storageClass; + const dimensify = dimensifyRow(columnClasses); + const isSizeLoading = size === undefined; + const isStorageClassLoading = size === undefined; return ( - - {diskName} - {sizeColumn || DASH} - {getDiskBus(disk, BUS_VIRTIO)} - {storageColumn || DASH} - - + + + {name} + + + {isSizeLoading && } + {!isSizeLoading && ( + {size || DASH} + )} + + {diskInterface} + + + {isStorageClassLoading && } + {!isStorageClassLoading && ( + + {storageClass || DASH} + + )} + + {actionsComponent} ); }; + +export type VMDiskRowProps = { + obj: StorageBundle; + customData: VMStorageRowCustomData; + index: number; + style: object; +}; + +export const DiskRow: React.FC = ({ + obj: { name, disk, volume, dataVolume, isEditingEnabled, ...restData }, + customData: { isDisabled, withProgress, vmLikeEntity, columnClasses }, + index, + style, +}) => { + return ( + + } + /> + ); +}; diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-disks/types.ts b/frontend/packages/kubevirt-plugin/src/components/vm-disks/types.ts index 2ac77bec828..f3ed805065f 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-disks/types.ts +++ b/frontend/packages/kubevirt-plugin/src/components/vm-disks/types.ts @@ -1,29 +1,34 @@ -import { EntityMap } from '@console/shared'; -import { VMLikeEntityKind, VMKind } from '../../types'; +import { VMLikeEntityKind } from '../../types'; +import { ValidationObject } from '../../utils/validations/types'; -export enum StorageRowType { - STORAGE_TYPE_VM = 'storage-type-vm', - STORAGE_TYPE_CREATE = 'storage-type-create', -} +export type StorageSimpleData = { + name?: string; + diskInterface?: string; + size?: string; + storageClass?: string; +}; + +export type StorageSimpleDataValidation = { + name?: ValidationObject; + diskInterface?: ValidationObject; + size?: ValidationObject; + storageClass?: ValidationObject; +}; -export type StorageBundle = { - name: string; - size: string; - storageClass: string; - storageType: StorageRowType; +export type StorageBundle = StorageSimpleData & { disk: any; + volume: any; + dataVolume: any; + isEditingEnabled: boolean; }; -export type VMDiskRowProps = { - obj: StorageBundle; - index: number; - style: object; - customData: { - vmLikeEntity: VMLikeEntityKind; - vm: VMKind; - diskLookup: EntityMap; - onCreateRowDismiss: () => void; - onCreateRowError: (error: string) => void; - forceRerender: () => void; - }; +export type VMStorageRowActionOpts = { + withProgress: (promise: Promise) => void; + isEditingEnabled: boolean; }; + +export type VMStorageRowCustomData = { + vmLikeEntity: VMLikeEntityKind; + columnClasses: string[]; + isDisabled: boolean; +} & VMStorageRowActionOpts; diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-disks/utils.ts b/frontend/packages/kubevirt-plugin/src/components/vm-disks/utils.ts new file mode 100644 index 00000000000..05481e1eaf7 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/vm-disks/utils.ts @@ -0,0 +1,10 @@ +import * as classNames from 'classnames'; +import { Kebab } from '@console/internal/components/utils'; + +export const diskTableColumnClasses = [ + classNames('col-lg-3'), + classNames('col-lg-3'), + classNames('col-lg-3'), + classNames('col-lg-3'), + Kebab.columnClass, +]; diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-disks/vm-disks.tsx b/frontend/packages/kubevirt-plugin/src/components/vm-disks/vm-disks.tsx index 88050672717..9547de1bb6a 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-disks/vm-disks.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vm-disks/vm-disks.tsx @@ -1,23 +1,15 @@ import * as React from 'react'; -import { Button } from 'patternfly-react'; -import { Alert, AlertActionCloseButton } from '@patternfly/react-core'; +import { Button, ButtonVariant } from '@patternfly/react-core'; import { Table } from '@console/internal/components/factory'; import { PersistentVolumeClaimModel } from '@console/internal/models'; -import { Firehose, FirehoseResult, Kebab } from '@console/internal/components/utils'; +import { Firehose, FirehoseResult } from '@console/internal/components/utils'; import { getNamespace, getName, createBasicLookup, createLookup } from '@console/shared'; import { useSafetyFirst } from '@console/internal/components/safety-first'; import { K8sResourceKind } from '@console/internal/module/k8s'; import { sortable } from '@patternfly/react-table'; import { DataVolumeModel } from '../../models'; import { VMLikeEntityKind } from '../../types'; -import { - asVM, - getDataVolumeTemplates, - getDisks, - getVolumeDataVolumeName, - getVolumePersistentVolumeClaimName, - getVolumes, -} from '../../selectors/vm'; +import { asVM, getDataVolumeTemplates, getDisks, getVolumes, isVM } from '../../selectors/vm'; import { getPvcStorageClassName, getPvcStorageSize } from '../../selectors/pvc/selectors'; import { getDataVolumeStorageClassName, @@ -26,52 +18,44 @@ import { import { VMLikeEntityTabProps } from '../vms/types'; import { getResource } from '../../utils'; import { getSimpleName } from '../../selectors/utils'; +import { wrapWithProgress } from '../../utils/utils'; +import { dimensifyHeader } from '../../utils/table'; +import { DiskWrapper } from '../../k8s/wrapper/vm/disk-wrapper'; +import { VolumeWrapper } from '../../k8s/wrapper/vm/volume-wrapper'; +import { DiskType, VolumeType } from '../../constants/vm/storage'; +import { diskModalEnhanced } from '../modals/disk-modal/disk-modal-enhanced'; +import { StorageUISource } from '../modals/disk-modal/storage-ui-source'; +import { DataVolumeWrapper } from '../../k8s/wrapper/vm/data-volume-wrapper'; +import { StorageBundle } from './types'; import { DiskRow } from './disk-row'; -import { StorageBundle, StorageRowType, VMDiskRowProps } from './types'; -import { CreateDiskRowFirehose } from './create-disk-row'; - -export const VMDiskRow: React.FC = (props) => { - switch (props.obj.storageType) { - case StorageRowType.STORAGE_TYPE_VM: - return ; - case StorageRowType.STORAGE_TYPE_CREATE: - return ; - default: - return null; - } -}; - -const getStoragesData = ( - { - vmLikeEntity, - datavolumes, - pvcs, - }: { - vmLikeEntity: VMLikeEntityKind; - pvcs: FirehoseResult; - datavolumes: FirehoseResult; - }, - addNewDisk: boolean, - rerenderFlag: boolean, -): StorageBundle[] => { +import { diskTableColumnClasses } from './utils'; + +const getStoragesData = ({ + vmLikeEntity, + datavolumes, + pvcs, +}: { + vmLikeEntity: VMLikeEntityKind; + pvcs: FirehoseResult; + datavolumes: FirehoseResult; +}): StorageBundle[] => { const vm = asVM(vmLikeEntity); const pvcLookup = createLookup(pvcs, getName); const datavolumeLookup = createLookup(datavolumes, getName); const volumeLookup = createBasicLookup(getVolumes(vm), getSimpleName); - const datavolumeTemplatesLookup = createBasicLookup(getDataVolumeTemplates(vm), getName); + const datavolumeTemplatesLookup = createBasicLookup(getDataVolumeTemplates(vm), getName); - const disksWithType = getDisks(vm).map((disk) => { - const volume = volumeLookup[disk.name]; - - const pvcName = getVolumePersistentVolumeClaimName(volume); - const dataVolumeName = getVolumeDataVolumeName(volume); + return getDisks(vm).map((disk) => { + const diskWrapper = DiskWrapper.initialize(disk); + const volume = volumeLookup[diskWrapper.getName()]; + const volumeWrapper = VolumeWrapper.initialize(volume); let size = null; let storageClass = null; - if (pvcName) { - const pvc = pvcLookup[pvcName]; + if (volumeWrapper.getType() === VolumeType.PERSISTENT_VOLUME_CLAIM) { + const pvc = pvcLookup[volumeWrapper.getPersistentVolumeClaimName()]; if (pvc) { size = getPvcStorageSize(pvc); storageClass = getPvcStorageClassName(pvc); @@ -79,9 +63,10 @@ const getStoragesData = ( size = undefined; storageClass = undefined; } - } else if (dataVolumeName) { + } else if (volumeWrapper.getType() === VolumeType.DATA_VOLUME) { const dataVolumeTemplate = - datavolumeTemplatesLookup[dataVolumeName] || datavolumeLookup[dataVolumeName]; + datavolumeTemplatesLookup[volumeWrapper.getDataVolumeName()] || + datavolumeLookup[volumeWrapper.getDataVolumeName()]; if (dataVolumeTemplate) { size = getDataVolumeStorageSize(dataVolumeTemplate); @@ -92,62 +77,47 @@ const getStoragesData = ( } } + const dataVolume = datavolumeTemplatesLookup[volumeWrapper.getDataVolumeName()]; + const source = StorageUISource.fromTypes( + volumeWrapper.getType(), + DataVolumeWrapper.initialize(dataVolume).getType(), + ); + const isTemplate = vmLikeEntity && !isVM(vmLikeEntity); return { - ...disk, // for sorting + disk, + volume, + dataVolume, + isEditingEnabled: isTemplate || (source && source.isEditingSupported()), + // for sorting + name: diskWrapper.getName(), + diskInterface: + diskWrapper.getType() === DiskType.DISK ? diskWrapper.getReadableDiskBus() : undefined, size, storageClass, - storageType: StorageRowType.STORAGE_TYPE_VM, - disk, }; }); - - return addNewDisk - ? [{ storageType: StorageRowType.STORAGE_TYPE_CREATE, rerenderFlag }, ...disksWithType] - : disksWithType; }; -export const VMDisks: React.FC = ({ vmLikeEntity, pvcs, datavolumes }) => { - const [isCreating, setIsCreating] = useSafetyFirst(false); - const [createError, setCreateError] = useSafetyFirst(null); - const [forceRerenderFlag, setForceRerenderFlag] = useSafetyFirst(false); // TODO: HACK: fire changes in Virtualize Table for CreateNicRow. Remove after deprecating CreateNicRow - - const vm = asVM(vmLikeEntity); +export type VMDisksTableProps = { + data?: any[]; + customData?: object; + row: React.ComponentClass | React.ComponentType; + columnClasses: string[]; +}; +export const VMDisksTable: React.FC = ({ + data, + customData, + row: Row, + columnClasses, +}) => { return ( -
-
-
- -
-
-
- {createError && ( - setCreateError(null)} />} - /> - )} - [ +
+ dimensifyHeader( + [ { title: 'Name', sortField: 'name', @@ -160,7 +130,7 @@ export const VMDisks: React.FC = ({ vmLikeEntity, pvcs, datavolume }, { title: 'Interface', - sortField: 'disk.bus', + sortField: 'diskInterface', transforms: [sortable], }, { @@ -170,37 +140,64 @@ export const VMDisks: React.FC = ({ vmLikeEntity, pvcs, datavolume }, { title: '', - props: { className: Kebab.columnClass }, }, - ]} - Row={VMDiskRow} + ], + columnClasses, + ) + } + Row={Row} + customData={{ ...customData, columnClasses }} + virtualize + loaded + /> + ); +}; + +type VMDisksProps = { + vmLikeEntity?: VMLikeEntityKind; + pvcs?: FirehoseResult; + datavolumes?: FirehoseResult; +}; + +export const VMDisks: React.FC = ({ vmLikeEntity, pvcs, datavolumes }) => { + const [isLocked, setIsLocked] = useSafetyFirst(false); + const withProgress = wrapWithProgress(setIsLocked); + return ( +
+
+
+ +
+
+
+ { - setIsCreating(false); - }, - onCreateRowError: (error) => { - setIsCreating(false); - setCreateError(error); - }, - forceRerender: () => setForceRerenderFlag(!forceRerenderFlag), + withProgress, + isDisabled: isLocked, }} - virtualize - loaded + row={DiskRow} + columnClasses={diskTableColumnClasses} />
); }; -interface VMDisksProps { - vmLikeEntity?: VMLikeEntityKind; - pvcs?: FirehoseResult; - datavolumes?: FirehoseResult; -} - export const VMDisksFirehose: React.FC = ({ obj: vmLikeEntity }) => { const namespace = getNamespace(vmLikeEntity); diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-nics/nic-row.tsx b/frontend/packages/kubevirt-plugin/src/components/vm-nics/nic-row.tsx index d405086bb55..c2edb7f822a 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-nics/nic-row.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vm-nics/nic-row.tsx @@ -5,21 +5,34 @@ import { DASH, getDeletetionTimestamp } from '@console/shared/src'; import { TemplateModel } from '@console/internal/models'; import { deleteDeviceModal, DeviceType } from '../modals/delete-device-modal'; import { VirtualMachineModel } from '../../models'; -import { isVM } from '../../selectors/vm'; +import { asVM, isVM, isVMRunning } from '../../selectors/vm'; import { dimensifyRow } from '../../utils/table'; import { VMLikeEntityKind } from '../../types'; import { nicModalEnhanced } from '../modals/nic-modal/nic-modal-enhanced'; -import { nicTableColumnClasses } from './utils'; -import { VMNicRowProps } from './types'; +import { ValidationCell } from '../table/validation-cell'; +import { + VMNicRowActionOpts, + NetworkBundle, + NetworkSimpleData, + NetworkSimpleDataValidation, + VMNicRowCustomData, +} from './types'; -const menuActionEdit = (nic, network, vmLikeEntity: VMLikeEntityKind): KebabOption => ({ +const menuActionEdit = ( + nic, + network, + vmLikeEntity: VMLikeEntityKind, + { withProgress }: VMNicRowActionOpts, +): KebabOption => ({ label: 'Edit', callback: () => - nicModalEnhanced({ - vmLikeEntity, - nic, - network, - }), + withProgress( + nicModalEnhanced({ + vmLikeEntity, + nic, + network, + }).result, + ), accessReview: asAccessReview( isVM(vmLikeEntity) ? VirtualMachineModel : TemplateModel, vmLikeEntity, @@ -27,14 +40,21 @@ const menuActionEdit = (nic, network, vmLikeEntity: VMLikeEntityKind): KebabOpti ), }); -const menuActionDelete = (nic, network, vmLikeEntity: VMLikeEntityKind): KebabOption => ({ +const menuActionDelete = ( + nic, + network, + vmLikeEntity: VMLikeEntityKind, + { withProgress }: VMNicRowActionOpts, +): KebabOption => ({ label: 'Delete', callback: () => - deleteDeviceModal({ - deviceType: DeviceType.NIC, - device: nic, - vmLikeEntity, - }), + withProgress( + deleteDeviceModal({ + deviceType: DeviceType.NIC, + device: nic, + vmLikeEntity, + }).result, + ), accessReview: asAccessReview( isVM(vmLikeEntity) ? VirtualMachineModel : TemplateModel, vmLikeEntity, @@ -42,34 +62,83 @@ const menuActionDelete = (nic, network, vmLikeEntity: VMLikeEntityKind): KebabOp ), }); -const getActions = (nic, network, vmLikeEntity: VMLikeEntityKind) => { +const getActions = (nic, network, vmLikeEntity: VMLikeEntityKind, opts: VMNicRowActionOpts) => { + if (isVMRunning(asVM(vmLikeEntity))) { + return []; + } const actions = [menuActionEdit, menuActionDelete]; - return actions.map((a) => a(nic, network, vmLikeEntity)); + return actions.map((a) => a(nic, network, vmLikeEntity, opts)); }; -export const NicRow: React.FC = ({ - obj: { name, model, networkName, interfaceType, macAddress, nic, network }, - customData: { vmLikeEntity }, +export type VMNicSimpleRowProps = { + data: NetworkSimpleData; + validation?: NetworkSimpleDataValidation; + columnClasses: string[]; + actionsComponent: React.ReactNode; + index: number; + style: object; +}; + +export const NicSimpleRow: React.FC = ({ + data: { name, model, networkName, interfaceType, macAddress }, + validation = {}, + columnClasses, + actionsComponent, index, style, }) => { - const dimensify = dimensifyRow(nicTableColumnClasses); + const dimensify = dimensifyRow(columnClasses); return ( - {name} - {model || DASH} - {networkName || DASH} - {interfaceType || DASH} - {macAddress || DASH} - - + + {name} + + + {model || DASH} + + + {networkName || DASH} + + + + {interfaceType || DASH} + + + + {macAddress || DASH} + {actionsComponent} ); }; + +export type VMNicRowProps = { + obj: NetworkBundle; + customData: VMNicRowCustomData; + index: number; + style: object; +}; + +export const NicRow: React.FC = ({ + obj: { name, nic, network, ...restData }, + customData: { isDisabled, withProgress, vmLikeEntity, columnClasses }, + index, + style, +}) => ( + + } + /> +); diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-nics/types.ts b/frontend/packages/kubevirt-plugin/src/components/vm-nics/types.ts index eff2354fcd9..a0a716d4958 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-nics/types.ts +++ b/frontend/packages/kubevirt-plugin/src/components/vm-nics/types.ts @@ -1,22 +1,31 @@ import { VMLikeEntityKind } from '../../types'; +import { ValidationObject } from '../../utils/validations/types'; -export type NetworkBundle = { +export type NetworkSimpleData = { name?: string; model?: string; - networkName: string; + networkName?: string; interfaceType?: string; macAddress?: string; +}; + +export type NetworkSimpleDataValidation = { + name?: ValidationObject; + model?: ValidationObject; + network?: ValidationObject; + interfaceType?: ValidationObject; + macAddress?: ValidationObject; +}; + +export type NetworkBundle = NetworkSimpleData & { nic: any; network: any; }; +export type VMNicRowActionOpts = { withProgress: (promise: Promise) => void }; + export type VMNicRowCustomData = { vmLikeEntity: VMLikeEntityKind; -}; - -export type VMNicRowProps = { - obj: NetworkBundle; - customData: VMNicRowCustomData; - index: number; - style: object; -}; + columnClasses: string[]; + isDisabled: boolean; +} & VMNicRowActionOpts; diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-nics/vm-nics.tsx b/frontend/packages/kubevirt-plugin/src/components/vm-nics/vm-nics.tsx index 920f74a5898..b44901580a8 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-nics/vm-nics.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vm-nics/vm-nics.tsx @@ -1,8 +1,9 @@ import * as React from 'react'; -import { Button } from 'patternfly-react'; import { Table } from '@console/internal/components/factory'; import { sortable } from '@patternfly/react-table'; import { createBasicLookup } from '@console/shared'; +import { useSafetyFirst } from '@console/internal/components/safety-first'; +import { Button, ButtonVariant } from '@patternfly/react-core'; import { VMLikeEntityKind } from '../../types'; import { getInterfaces, getNetworks, asVM } from '../../selectors/vm'; import { dimensifyHeader } from '../../utils/table'; @@ -11,6 +12,7 @@ import { NetworkInterfaceWrapper } from '../../k8s/wrapper/vm/network-interface- import { nicModalEnhanced } from '../modals/nic-modal/nic-modal-enhanced'; import { getSimpleName } from '../../selectors/utils'; import { NetworkWrapper } from '../../k8s/wrapper/vm/network-wrapper'; +import { wrapWithProgress } from '../../utils/utils'; import { NicRow } from './nic-row'; import { NetworkBundle } from './types'; import { nicTableColumnClasses } from './utils'; @@ -36,69 +38,101 @@ const getNicsData = (vmLikeEntity: VMLikeEntityKind): NetworkBundle[] => { }); }; -export const VMNics: React.FC = ({ obj: vmLikeEntity }) => ( -
-
-
- +export type VMNicsTableProps = { + data?: any[]; + customData?: object; + row: React.ComponentClass | React.ComponentType; + columnClasses: string[]; +}; + +export const VMNicsTable: React.FC = ({ + data, + customData, + row: Row, + columnClasses, +}) => { + return ( +
+ dimensifyHeader( + [ + { + title: 'Name', + sortField: 'name', + transforms: [sortable], + }, + { + title: 'Model', + sortField: 'model', + transforms: [sortable], + }, + { + title: 'Network', + sortField: 'networkName', + transforms: [sortable], + }, + { + title: 'Type', + sortField: 'interfaceType', + transforms: [sortable], + }, + { + title: 'MAC Address', + sortField: 'macAddress', + transforms: [sortable], + }, + { + title: '', + }, + ], + columnClasses, + ) + } + Row={Row} + customData={{ ...customData, columnClasses }} + virtualize + loaded + /> + ); +}; + +export const VMNics: React.FC = ({ obj: vmLikeEntity }) => { + const [isLocked, setIsLocked] = useSafetyFirst(false); + const withProgress = wrapWithProgress(setIsLocked); + return ( +
+
+
+ +
+
+
+
-
-
- dimensifyHeader( - [ - { - title: 'Name', - sortField: 'name', - transforms: [sortable], - }, - { - title: 'Model', - sortField: 'model', - transforms: [sortable], - }, - { - title: 'Network', - sortField: 'networkName', - transforms: [sortable], - }, - { - title: 'Type', - sortField: 'interfaceType', - transforms: [sortable], - }, - { - title: 'MAC Address', - sortField: 'macAddress', - transforms: [sortable], - }, - { - title: '', - }, - ], - nicTableColumnClasses, - ) - } - Row={NicRow} - customData={{ - vmLikeEntity, - }} - virtualize - loaded - /> - - -); + ); +}; diff --git a/frontend/packages/kubevirt-plugin/src/constants/pvc/constants.ts b/frontend/packages/kubevirt-plugin/src/constants/pvc/constants.ts new file mode 100644 index 00000000000..23a40847608 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/constants/pvc/constants.ts @@ -0,0 +1,8 @@ +export const PVC_ACCESSMODE_RWO = 'ReadWriteOnce'; +export const PVC_ACCESSMODE_RWM = 'ReadWriteMany'; + +export const PVC_VOLUMEMODE_FS = 'Filesystem'; +export const PVC_VOLUMEMODE_BLOCK = 'Block'; + +export const PVC_ACCESSMODE_DEFAULT = PVC_ACCESSMODE_RWO; +export const PVC_VOLUMEMODE_DEFAULT = PVC_VOLUMEMODE_FS; diff --git a/frontend/packages/kubevirt-plugin/src/constants/pvc/index.ts b/frontend/packages/kubevirt-plugin/src/constants/pvc/index.ts new file mode 100644 index 00000000000..c94f80f843a --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/constants/pvc/index.ts @@ -0,0 +1 @@ +export * from './constants'; diff --git a/frontend/packages/kubevirt-plugin/src/constants/vm/index.ts b/frontend/packages/kubevirt-plugin/src/constants/vm/index.ts index 6af1fb13e01..1ba15252508 100644 --- a/frontend/packages/kubevirt-plugin/src/constants/vm/index.ts +++ b/frontend/packages/kubevirt-plugin/src/constants/vm/index.ts @@ -1,2 +1,3 @@ export * from './constants'; export * from './network'; +export * from './storage'; diff --git a/frontend/packages/kubevirt-plugin/src/constants/vm/storage.ts b/frontend/packages/kubevirt-plugin/src/constants/vm/storage.ts deleted file mode 100644 index be82778c21e..00000000000 --- a/frontend/packages/kubevirt-plugin/src/constants/vm/storage.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum StorageType { // TODO add other types / refactor - DATAVOLUME = 'datavolume', // compatible with web-ui-components constants - CONTAINER = 'container', -} diff --git a/frontend/packages/kubevirt-plugin/src/constants/vm/storage/data-volume-source-type.ts b/frontend/packages/kubevirt-plugin/src/constants/vm/storage/data-volume-source-type.ts new file mode 100644 index 00000000000..4a901f9c2d9 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/constants/vm/storage/data-volume-source-type.ts @@ -0,0 +1,31 @@ +/* eslint-disable lines-between-class-members */ +import { ValueEnum } from '../../value-enum'; + +export class DataVolumeSourceType extends ValueEnum { + static readonly BLANK = new DataVolumeSourceType('blank'); + static readonly HTTP = new DataVolumeSourceType('http'); + static readonly PVC = new DataVolumeSourceType('pvc'); + static readonly REGISTRY = new DataVolumeSourceType('registry'); + static readonly S3 = new DataVolumeSourceType('s3'); + static readonly UPLOAD = new DataVolumeSourceType('upload'); + + private static readonly ALL = Object.freeze( + ValueEnum.getAllClassEnumProperties(DataVolumeSourceType), + ); + + private static readonly stringMapper = DataVolumeSourceType.ALL.reduce( + (accumulator, dataVolumeSourceType: DataVolumeSourceType) => ({ + ...accumulator, + [dataVolumeSourceType.value]: dataVolumeSourceType, + }), + {}, + ); + + static getAll = () => DataVolumeSourceType.ALL; + + static fromSerialized = (volumeType: { value: string }): DataVolumeSourceType => + DataVolumeSourceType.fromString(volumeType && volumeType.value); + + static fromString = (model: string): DataVolumeSourceType => + DataVolumeSourceType.stringMapper[model]; +} diff --git a/frontend/packages/kubevirt-plugin/src/constants/vm/storage/disk-bus.ts b/frontend/packages/kubevirt-plugin/src/constants/vm/storage/disk-bus.ts new file mode 100644 index 00000000000..3f47fe3f224 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/constants/vm/storage/disk-bus.ts @@ -0,0 +1,35 @@ +/* eslint-disable lines-between-class-members */ +import { ValueEnum } from '../../value-enum'; +import { READABLE_VIRTIO } from '../constants'; + +export class DiskBus extends ValueEnum { + static readonly VIRTIO = new DiskBus('virtio'); + static readonly SATA = new DiskBus('sata'); + static readonly SCSI = new DiskBus('scsi'); + + private static readonly ALL = Object.freeze( + ValueEnum.getAllClassEnumProperties(DiskBus), + ); + + private static readonly stringMapper = DiskBus.ALL.reduce( + (accumulator, diskBusType: DiskBus) => ({ + ...accumulator, + [diskBusType.value]: diskBusType, + }), + {}, + ); + + static getAll = () => DiskBus.ALL; + + static fromSerialized = (diskBusType: { value: string }): DiskBus => + DiskBus.fromString(diskBusType && diskBusType.value); + + static fromString = (model: string): DiskBus => DiskBus.stringMapper[model]; + + toString = () => { + if (this === DiskBus.VIRTIO) { + return READABLE_VIRTIO; + } + return this.value; + }; +} diff --git a/frontend/packages/kubevirt-plugin/src/constants/vm/storage/disk-type.ts b/frontend/packages/kubevirt-plugin/src/constants/vm/storage/disk-type.ts new file mode 100644 index 00000000000..ff584b95345 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/constants/vm/storage/disk-type.ts @@ -0,0 +1,28 @@ +/* eslint-disable lines-between-class-members */ +import { ValueEnum } from '../../value-enum'; + +export class DiskType extends ValueEnum { + static readonly DISK = new DiskType('disk'); + static readonly CDROM = new DiskType('cdrom'); + static readonly FLOPPY = new DiskType('floppy'); + static readonly LUN = new DiskType('lun'); + + private static readonly ALL = Object.freeze( + ValueEnum.getAllClassEnumProperties(DiskType), + ); + + private static readonly stringMapper = DiskType.ALL.reduce( + (accumulator, diskType: DiskType) => ({ + ...accumulator, + [diskType.value]: diskType, + }), + {}, + ); + + static getAll = () => DiskType.ALL; + + static fromSerialized = (diskType: { value: string }): DiskType => + DiskType.fromString(diskType && diskType.value); + + static fromString = (model: string): DiskType => DiskType.stringMapper[model]; +} diff --git a/frontend/packages/kubevirt-plugin/src/constants/vm/storage/index.ts b/frontend/packages/kubevirt-plugin/src/constants/vm/storage/index.ts new file mode 100644 index 00000000000..e3671478d93 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/constants/vm/storage/index.ts @@ -0,0 +1,4 @@ +export * from './data-volume-source-type'; +export * from './disk-bus'; +export * from './disk-type'; +export * from './volume-type'; diff --git a/frontend/packages/kubevirt-plugin/src/constants/vm/storage/volume-type.ts b/frontend/packages/kubevirt-plugin/src/constants/vm/storage/volume-type.ts new file mode 100644 index 00000000000..be1980392c1 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/constants/vm/storage/volume-type.ts @@ -0,0 +1,34 @@ +/* eslint-disable lines-between-class-members */ +import { ValueEnum } from '../../value-enum'; + +export class VolumeType extends ValueEnum { + static readonly CLOUD_INIT_CONFIG_DRIVE = new VolumeType('cloudInitConfigDrive'); + static readonly CLOUD_INIT_NO_CLOUD = new VolumeType('cloudInitNoCloud'); + static readonly CONFIG_MAP = new VolumeType('configMap'); + static readonly CONTAINER_DISK = new VolumeType('containerDisk'); + static readonly DATA_VOLUME = new VolumeType('dataVolume'); + static readonly EMPTY_DISK = new VolumeType('emptyDisk'); + static readonly EPHEMERAL = new VolumeType('ephemeral'); + static readonly PERSISTENT_VOLUME_CLAIM = new VolumeType('persistentVolumeClaim'); + static readonly SECRET = new VolumeType('secret'); + static readonly SERVICE_ACCOUNT = new VolumeType('serviceAccount'); + + private static readonly ALL = Object.freeze( + ValueEnum.getAllClassEnumProperties(VolumeType), + ); + + private static readonly stringMapper = VolumeType.ALL.reduce( + (accumulator, volumeType: VolumeType) => ({ + ...accumulator, + [volumeType.value]: volumeType, + }), + {}, + ); + + static getAll = () => VolumeType.ALL; + + static fromSerialized = (volumeType: { value: string }): VolumeType => + VolumeType.fromString(volumeType && volumeType.value); + + static fromString = (model: string): VolumeType => VolumeType.stringMapper[model]; +} diff --git a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/utils.ts b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/utils.ts index c63a27da8d7..d60a64665eb 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/utils.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/utils.ts @@ -19,6 +19,7 @@ export const getShiftBootOrderPatches = ( .map((device) => { const patchedDevice = _.cloneDeep(device); patchedDevice.bootOrder = getDeviceBootOrder(patchedDevice) - 1; + return new PatchBuilder(path) .setListUpdate(patchedDevice, devicesWithoutRemovedDevice, getSimpleName) .build(); diff --git a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-disk-patches.ts b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-disk-patches.ts index 9fa3c22bd53..7ae285ce22c 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-disk-patches.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-disk-patches.ts @@ -1,70 +1,125 @@ -import { getName } from '@console/shared'; -import { getAddDiskPatch, getDeviceBootOrderPatch } from 'kubevirt-web-ui-components'; -import { ConfigMapKind, Patch } from '@console/internal/module/k8s'; +import { getName } from '@console/shared/src'; +import { Patch, k8sGet } from '@console/internal/module/k8s'; import { getDataVolumeTemplates, - getDeviceBootOrder, getDisks, + getInterfaces, getVolumeDataVolumeName, getVolumes, } from '../../../selectors/vm'; import { getVMLikePatches } from '../vm-template'; import { VMLikeEntityKind } from '../../../types'; +import { PatchBuilder } from '../../utils/patch'; +import { getSimpleName } from '../../../selectors/utils'; +import { DiskWrapper } from '../../wrapper/vm/disk-wrapper'; +import { V1Disk } from '../../../types/vm/disk/V1Disk'; +import { V1Volume } from '../../../types/vm/disk/V1Volume'; +import { V1alpha1DataVolume } from '../../../types/vm/disk/V1alpha1DataVolume'; +import { getStorageClassConfigMap } from '../../requests/config-map/storage-class'; +import { DataVolumeWrapper } from '../../wrapper/vm/data-volume-wrapper'; +import { + getDefaultSCAccessMode, + getDefaultSCVolumeMode, +} from '../../../selectors/config-map/sc-defaults'; +import { getShiftBootOrderPatches } from './utils'; export const getRemoveDiskPatches = (vmLikeEntity: VMLikeEntityKind, disk): Patch[] => { return getVMLikePatches(vmLikeEntity, (vm) => { - const diskName = disk.name; + const diskWrapper = DiskWrapper.initialize(disk); + const diskName = diskWrapper.getName(); const disks = getDisks(vm); const volumes = getVolumes(vm); + const volume = volumes.find((v) => getSimpleName(v) === diskName); - const diskIndex = disks.findIndex((d) => d.name === diskName); - const volumeIndex = volumes.findIndex((v) => v.name === diskName); - - const patches: Patch[] = []; - - if (diskIndex >= 0) { - patches.push({ - op: 'remove', - path: `/spec/template/spec/domain/devices/disks/${diskIndex}`, - }); - } - - if (volumeIndex >= 0) { - patches.push({ - op: 'remove', - path: `/spec/template/spec/volumes/${volumeIndex}`, - }); - } + const patches = [ + new PatchBuilder('/spec/template/spec/domain/devices/disks') + .setListRemove(disk, disks, getSimpleName) + .build(), + new PatchBuilder('/spec/template/spec/volumes') + .setListRemove(volume, volumes, getSimpleName) + .build(), + ]; - const dataVolumeName = getVolumeDataVolumeName(volumes[volumeIndex]); + const dataVolumeName = getVolumeDataVolumeName(volume); if (dataVolumeName) { - const dataVolumeIndex = getDataVolumeTemplates(vm).findIndex( - (dataVolume) => getName(dataVolume) === dataVolumeName, + patches.push( + new PatchBuilder('/spec/dataVolumeTemplates') + .setListRemoveSimpleValue(dataVolumeName, getDataVolumeTemplates(vm), getName) + .build(), ); - if (dataVolumeIndex >= 0) { - patches.push({ - op: 'remove', - path: `/spec/dataVolumeTemplates/${dataVolumeIndex}`, - }); - } } - const bootOrderIndex = getDeviceBootOrder(disk); - if (bootOrderIndex != null) { - return [...patches, ...getDeviceBootOrderPatch(vm, 'disks', diskName)]; + if (diskWrapper.hasBootOrder()) { + return [ + ...patches, + ...getShiftBootOrderPatches( + '/spec/template/spec/domain/devices/disks', + disks, + diskName, + diskWrapper.getBootOrder(), + ), + ...getShiftBootOrderPatches( + '/spec/template/spec/domain/devices/interfaces', + getInterfaces(vm), + null, + diskWrapper.getBootOrder(), + ), + ]; } return patches; }); }; -export const getAddDiskPatches = ( +export const getUpdateDiskPatches = async ( vmLikeEntity: VMLikeEntityKind, - disk: object, - storageClassConfigMap: ConfigMapKind, -): Patch[] => { + { + disk, + volume, + dataVolume, + oldDiskName, + oldVolumeName, + oldDataVolumeName, + }: { + disk: V1Disk; + volume: V1Volume; + dataVolume: V1alpha1DataVolume; + oldDiskName: string; + oldVolumeName: string; + oldDataVolumeName: string; + }, +): Promise => { + let finalDataVolume; + if (dataVolume) { + const dataVolumeWrapper = DataVolumeWrapper.initialize(dataVolume); + const storageClassConfigMap = await getStorageClassConfigMap({ k8sGet }); + const storageClassName = dataVolumeWrapper.getStorageClassName(); + + finalDataVolume = DataVolumeWrapper.mergeWrappers( + DataVolumeWrapper.initializeFromSimpleData({ + accessModes: [getDefaultSCAccessMode(storageClassConfigMap, storageClassName)], + volumeMode: getDefaultSCVolumeMode(storageClassConfigMap, storageClassName), + }), + dataVolumeWrapper, + ).asResource(); + } return getVMLikePatches(vmLikeEntity, (vm) => { - return getAddDiskPatch(vm, disk, storageClassConfigMap); + const disks = getDisks(vm, null); + const volumes = getVolumes(vm, null); + const dataVolumeTemplates = getDataVolumeTemplates(vm, null); + + return [ + new PatchBuilder('/spec/template/spec/domain/devices/disks') + .setListUpdate(disk, disks, getSimpleName, oldDiskName) + .build(), + new PatchBuilder('/spec/template/spec/volumes') + .setListUpdate(volume, volumes, getSimpleName, oldVolumeName) + .build(), + finalDataVolume && + new PatchBuilder('/spec/dataVolumeTemplates') + .setListUpdate(finalDataVolume, dataVolumeTemplates, getSimpleName, oldDataVolumeName) + .build(), + ].filter((patch) => patch); }); }; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-nic-patches.ts b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-nic-patches.ts index e57395328ba..1524599cc57 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-nic-patches.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-nic-patches.ts @@ -9,7 +9,7 @@ import { NetworkWrapper } from '../../wrapper/vm/network-wrapper'; import { NetworkInterfaceWrapper } from '../../wrapper/vm/network-interface-wrapper'; import { getShiftBootOrderPatches } from './utils'; -export const getRemoveNicPatches = (vmLikeEntity: VMLikeEntityKind, nic: any): Patch[] => { +export const getRemoveNICPatches = (vmLikeEntity: VMLikeEntityKind, nic: any): Patch[] => { return getVMLikePatches(vmLikeEntity, (vm) => { const nicName = nic.name; const nics = getInterfaces(vm); @@ -60,7 +60,7 @@ export const getRemoveNicPatches = (vmLikeEntity: VMLikeEntityKind, nic: any): P }); }; -export const getAddNicPatches = ( +export const getUpdateNICPatches = ( vmLikeEntity: VMLikeEntityKind, { nic, @@ -75,14 +75,10 @@ export const getAddNicPatches = ( return [ new PatchBuilder('/spec/template/spec/domain/devices/interfaces') - .setListUpdate(nic, nics, (currentNIC) => - currentNIC === nic ? oldNICName : getSimpleName(currentNIC), - ) + .setListUpdate(nic, nics, getSimpleName, oldNICName) .build(), new PatchBuilder('/spec/template/spec/networks') - .setListUpdate(network, networks, (currentNetwork) => - currentNetwork === network ? oldNetworkName : getSimpleName(currentNetwork), - ) + .setListUpdate(network, networks, getSimpleName, oldNetworkName) .build(), ].filter((patch) => patch); }); diff --git a/frontend/packages/kubevirt-plugin/src/k8s/requests/config-map/storage-class/constants.ts b/frontend/packages/kubevirt-plugin/src/k8s/requests/config-map/storage-class/constants.ts new file mode 100644 index 00000000000..1f9bf3add96 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/k8s/requests/config-map/storage-class/constants.ts @@ -0,0 +1,3 @@ +export const STORAGE_CLASS_CONFIG_MAP_NAME = 'kubevirt-storage-class-defaults'; +// Different releases, different locations. Respect the order when resolving. Otherwise the configMap name/namespace is considered as well-known. +export const STORAGE_CLASS_CONFIG_MAP_NAMESPACES = ['openshift-cnv', 'openshift']; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/requests/config-map/storage-class/index.ts b/frontend/packages/kubevirt-plugin/src/k8s/requests/config-map/storage-class/index.ts new file mode 100644 index 00000000000..3ee498e7134 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/k8s/requests/config-map/storage-class/index.ts @@ -0,0 +1 @@ +export * from './storageClassConfigMap'; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/requests/config-map/storage-class/storageClassConfigMap.ts b/frontend/packages/kubevirt-plugin/src/k8s/requests/config-map/storage-class/storageClassConfigMap.ts new file mode 100644 index 00000000000..4507e151ef7 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/k8s/requests/config-map/storage-class/storageClassConfigMap.ts @@ -0,0 +1,44 @@ +import { ConfigMapModel } from '@console/internal/models'; +import { ConfigMapKind } from '@console/internal/module/k8s'; +import { STORAGE_CLASS_CONFIG_MAP_NAME, STORAGE_CLASS_CONFIG_MAP_NAMESPACES } from './constants'; + +const { warn } = console; + +const getStorageClassConfigMapInNamespace = async ({ + k8sGet, + namespace, +}: { + namespace: string; + k8sGet: (...opts: any) => Promise; +}): Promise => { + try { + return await k8sGet(ConfigMapModel, STORAGE_CLASS_CONFIG_MAP_NAME, namespace, null, { + disableHistory: true, + }); + } catch (e) { + return null; + } +}; + +export const getStorageClassConfigMap = async (props: { + k8sGet: (...opts: any[]) => Promise; +}): Promise => { + // query namespaces sequentially to respect order + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let index = 0; index < STORAGE_CLASS_CONFIG_MAP_NAMESPACES.length; index++) { + // eslint-disable-next-line no-await-in-loop + const configMap = await getStorageClassConfigMapInNamespace({ + namespace: STORAGE_CLASS_CONFIG_MAP_NAMESPACES[index], + ...props, + }); + if (configMap) { + return configMap; + } + } + warn( + `The ${STORAGE_CLASS_CONFIG_MAP_NAME} can not be found in none of following namespaces: `, + JSON.stringify(STORAGE_CLASS_CONFIG_MAP_NAMESPACES), + '. The PVCs will be created with default values.', + ); + return null; +}; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/utils/patch.ts b/frontend/packages/kubevirt-plugin/src/k8s/utils/patch.ts index 6b90a6c8dcc..c49210bd4f2 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/utils/patch.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/utils/patch.ts @@ -39,12 +39,19 @@ export class PatchBuilder { return this; }; - setListRemove = (value: T, items: T[], compareGetter?: (t: T) => any) => { + setListRemove = (value: T, items: T[], compareGetter?: (t: T) => any) => + this.setListRemoveSimpleValue( + compareGetter ? compareGetter(value) : value, + items, + compareGetter, + ); + + setListRemoveSimpleValue = (value: T | U, items: T[], compareGetter?: (t: T) => U) => { this.value = undefined; this.operation = PatchOperation.REMOVE; if (items) { const foundIndex = items.findIndex((t) => - compareGetter ? compareGetter(t) === compareGetter(value) : t === value, + compareGetter ? compareGetter(t) === (value as U) : t === (value as T), ); if (foundIndex < 0) { this.valid = false; // do not do anything @@ -57,11 +64,18 @@ export class PatchBuilder { return this; }; - setListUpdate = (value: T, items?: T[], compareGetter?: (t: T) => any) => { + setListUpdate = ( + value: T, + items?: T[], + compareGetter?: (t: T) => U, + oldSimpleValue?: T | U, + ) => { if (items) { this.value = value; const foundIndex = items.findIndex((t) => - compareGetter ? compareGetter(t) === compareGetter(value) : t === value, + compareGetter + ? compareGetter(t) === ((oldSimpleValue as U) || compareGetter(value)) + : t === (oldSimpleValue || value), ); if (foundIndex < 0) { this.valueIndex = items.length; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/data-volume-wrapper.ts b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/data-volume-wrapper.ts new file mode 100644 index 00000000000..b143b6a0b9a --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/data-volume-wrapper.ts @@ -0,0 +1,129 @@ +import * as _ from 'lodash'; +import { getName } from '@console/shared/src'; +import { validate } from '@console/internal/components/utils'; +import { ObjectWithTypePropertyWrapper } from '../common/object-with-type-property-wrapper'; +import { V1alpha1DataVolume } from '../../../types/vm/disk/V1alpha1DataVolume'; +import { DataVolumeSourceType } from '../../../constants/vm/storage'; +import { + getDataVolumeStorageClassName, + getDataVolumeStorageSize, +} from '../../../selectors/dv/selectors'; + +type CombinedTypeData = { + name?: string; + namespace?: string; + url?: string; +}; + +const sanitizeTypeData = (type: DataVolumeSourceType, typeData: CombinedTypeData) => { + if (!type || !typeData) { + return null; + } + const { name, namespace, url } = typeData; + + if (type === DataVolumeSourceType.BLANK) { + return {}; + } + if (type === DataVolumeSourceType.HTTP) { + return { url }; + } + if (type === DataVolumeSourceType.PVC) { + return { name, namespace }; + } + + return null; +}; + +export class DataVolumeWrapper extends ObjectWithTypePropertyWrapper< + V1alpha1DataVolume, + DataVolumeSourceType +> { + static readonly EMPTY = new DataVolumeWrapper(); + + static mergeWrappers = (...datavolumeWrappers: DataVolumeWrapper[]): DataVolumeWrapper => + ObjectWithTypePropertyWrapper.defaultMergeWrappersWithType( + DataVolumeWrapper, + datavolumeWrappers, + ); + + static initializeFromSimpleData = ( + params?: { + name?: string; + type?: DataVolumeSourceType; + typeData?: CombinedTypeData; + accessModes?: object[] | string[]; + volumeMode?: object | string; + size?: string | number; + unit?: string; + storageClassName?: string; + }, + opts?: { sanitizeTypeData: boolean }, + ) => { + if (!params) { + return DataVolumeWrapper.EMPTY; + } + const { name, type, typeData, accessModes, volumeMode, size, unit, storageClassName } = params; + const resources = + size == null + ? undefined + : { + requests: { + storage: size && unit ? `${size}${unit}` : size, + }, + }; + + return new DataVolumeWrapper( + { + metadata: { + name, + }, + spec: { + pvc: { + accessModes: _.cloneDeep(accessModes), + volumeMode: _.cloneDeep(volumeMode), + resources, + storageClassName, + }, + source: {}, + }, + }, + { + initializeWithType: type, + initializeWithTypeData: + opts && opts.sanitizeTypeData ? sanitizeTypeData(type, typeData) : _.cloneDeep(typeData), + }, + ); + }; + + static initialize = (dataVolumeTemplate?: V1alpha1DataVolume, copy?: boolean) => + new DataVolumeWrapper(dataVolumeTemplate, copy && { copy }); + + protected constructor( + dataVolumeTemplate?: V1alpha1DataVolume, + opts?: { + initializeWithType?: DataVolumeSourceType; + initializeWithTypeData?: any; + copy?: boolean; + }, + ) { + super(dataVolumeTemplate, opts, DataVolumeSourceType, ['spec', 'source']); + } + + getName = () => getName(this.data as any); + + getStorageClassName = () => getDataVolumeStorageClassName(this.data as any); + + getPesistentVolumeClaimName = () => this.getIn(['spec', 'source', 'pvc', 'name']); + + getURL = () => this.getIn(['spec', 'source', 'http', 'url']); + + getSize = (): { value: number; unit: string } => { + const parts = validate.split(getDataVolumeStorageSize(this.data as any) || ''); + return { + value: parts[0], + unit: parts[1], + }; + }; + + hasSize = () => this.getSize().value > 0; +} diff --git a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/disk-wrapper.ts b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/disk-wrapper.ts new file mode 100644 index 00000000000..9bdbaec17c2 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/disk-wrapper.ts @@ -0,0 +1,56 @@ +import { ObjectWithTypePropertyWrapper } from '../common/object-with-type-property-wrapper'; +import { V1Disk } from '../../../types/vm/disk/V1Disk'; +import { DiskType, DiskBus } from '../../../constants/vm/storage'; + +export class DiskWrapper extends ObjectWithTypePropertyWrapper { + static readonly EMPTY = new DiskWrapper(); + + static mergeWrappers = (...disks: DiskWrapper[]): DiskWrapper => + ObjectWithTypePropertyWrapper.defaultMergeWrappersWithType(DiskWrapper, disks); + + static initializeFromSimpleData = (params?: { + name?: string; + type?: DiskType; + bus?: DiskBus; + bootOrder?: number; + }) => { + if (!params) { + return DiskWrapper.EMPTY; + } + const { name, type, bus, bootOrder } = params; + return new DiskWrapper( + { + name, + bootOrder, + }, + { + initializeWithType: type, + initializeWithTypeData: type === DiskType.DISK ? { bus: bus.getValue() } : undefined, + }, + ); + }; + + static initialize = (disk?: V1Disk, copy?: boolean) => new DiskWrapper(disk, copy && { copy }); + + protected constructor( + disk?: V1Disk, + opts?: { initializeWithType?: DiskType; initializeWithTypeData?: any; copy?: boolean }, + ) { + super(disk, opts, DiskType); + } + + getName = () => this.get('name'); + + getDiskBus = (): DiskBus => DiskBus.fromString(this.getIn(['disk', 'bus'])); + + getReadableDiskBus = () => { + const diskBus = this.getDiskBus(); + return diskBus && diskBus.toString(); + }; + + getBootOrder = () => this.get('bootOrder'); + + isFirstBootableDevice = () => this.getBootOrder() === 1; + + hasBootOrder = () => this.getBootOrder() != null; +} diff --git a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/volume-wrapper.ts b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/volume-wrapper.ts new file mode 100644 index 00000000000..f286062586e --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/volume-wrapper.ts @@ -0,0 +1,82 @@ +import * as _ from 'lodash'; +import { ObjectWithTypePropertyWrapper } from '../common/object-with-type-property-wrapper'; +import { V1Volume } from '../../../types/vm/disk/V1Volume'; +import { VolumeType } from '../../../constants/vm/storage'; +import { + getVolumeContainerImage, + getVolumeDataVolumeName, + getVolumePersistentVolumeClaimName, +} from '../../../selectors/vm'; + +type CombinedTypeData = { + name?: string; + claimName?: string; + image?: string; +}; + +const sanitizeTypeData = (type: VolumeType, typeData: CombinedTypeData) => { + if (!type || !typeData) { + return null; + } + const { name, claimName, image } = typeData; + + if (type === VolumeType.DATA_VOLUME) { + return { name }; + } + if (type === VolumeType.PERSISTENT_VOLUME_CLAIM) { + return { claimName }; + } + + if (type === VolumeType.CONTAINER_DISK) { + return { image }; + } + + return null; +}; + +export class VolumeWrapper extends ObjectWithTypePropertyWrapper { + static readonly EMPTY = new VolumeWrapper(); + + static mergeWrappers = (...volumes: VolumeWrapper[]): VolumeWrapper => + ObjectWithTypePropertyWrapper.defaultMergeWrappersWithType(VolumeWrapper, volumes); + + static initializeFromSimpleData = ( + params?: { + name?: string; + type?: VolumeType; + typeData?: CombinedTypeData; + }, + opts?: { sanitizeTypeData: boolean }, + ) => { + if (!params) { + return VolumeWrapper.EMPTY; + } + const { name, type, typeData } = params; + return new VolumeWrapper( + { name }, + { + initializeWithType: type, + initializeWithTypeData: + opts && opts.sanitizeTypeData ? sanitizeTypeData(type, typeData) : _.cloneDeep(typeData), + }, + ); + }; + + static initialize = (volume?: V1Volume, copy?: boolean) => + new VolumeWrapper(volume, copy && { copy }); + + protected constructor( + volume?: V1Volume, + opts?: { initializeWithType?: VolumeType; initializeWithTypeData?: any; copy?: boolean }, + ) { + super(volume, opts, VolumeType); + } + + getName = () => this.get('name'); + + getPersistentVolumeClaimName = () => getVolumePersistentVolumeClaimName(this.data); + + getDataVolumeName = () => getVolumeDataVolumeName(this.data); + + getContainerImage = () => getVolumeContainerImage(this.data); +} diff --git a/frontend/packages/kubevirt-plugin/src/selectors/config-map/sc-defaults.ts b/frontend/packages/kubevirt-plugin/src/selectors/config-map/sc-defaults.ts new file mode 100644 index 00000000000..144252d50d0 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/selectors/config-map/sc-defaults.ts @@ -0,0 +1,42 @@ +import * as _ from 'lodash'; +import { ConfigMapKind } from '@console/internal/module/k8s'; +import { PVC_ACCESSMODE_DEFAULT, PVC_VOLUMEMODE_DEFAULT } from '../../constants/pvc'; + +const getSCConfigMapAttribute = ( + storageClassConfigMap: ConfigMapKind, + storageClassName: string, + attributeName: string, + defaultValue: string, +): string => { + const hasSubAttribute = + storageClassName && + attributeName && + _.has(storageClassConfigMap, ['data', `${storageClassName}.${attributeName}`]); + return _.get( + storageClassConfigMap, + ['data', hasSubAttribute ? `${storageClassName}.${attributeName}` : attributeName], + defaultValue, + ); +}; + +export const getDefaultSCAccessMode = ( + storageClassConfigMap: ConfigMapKind, + storageClassName: string, +) => + getSCConfigMapAttribute( + storageClassConfigMap, + storageClassName, + 'accessMode', + PVC_ACCESSMODE_DEFAULT, + ); + +export const getDefaultSCVolumeMode = ( + storageClassConfigMap: ConfigMapKind, + storageClassName: string, +) => + getSCConfigMapAttribute( + storageClassConfigMap, + storageClassName, + 'volumeMode', + PVC_VOLUMEMODE_DEFAULT, + ); diff --git a/frontend/packages/kubevirt-plugin/src/selectors/utils.ts b/frontend/packages/kubevirt-plugin/src/selectors/utils.ts index 6fca265a73e..6b336c6fdb1 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/utils.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/utils.ts @@ -16,4 +16,4 @@ export const findKeySuffixValue = (obj: StringHashMap, keyPrefix: string) => { return index > 0 ? key.substring(index + 1) : null; }; -export const getSimpleName = (obj) => obj && obj.name; +export const getSimpleName = (obj): string => obj && obj.name; diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm/selectors.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm/selectors.ts index 5817a53ab93..aa58d34093d 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vm/selectors.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm/selectors.ts @@ -7,7 +7,7 @@ import { TEMPLATE_OS_NAME_ANNOTATION, TEMPLATE_WORKLOAD_LABEL, } from '../../constants/vm'; -import { V1NetworkInterface, VMKind, VMLikeEntityKind, CPURaw } from '../../types'; +import { V1Network, V1NetworkInterface, VMKind, VMLikeEntityKind, CPURaw } from '../../types'; import { findKeySuffixValue, getSimpleName, getValueByPrefix } from '../utils'; import { getAnnotations, getLabels } from '../selectors'; import { NetworkWrapper } from '../../k8s/wrapper/vm/network-wrapper'; @@ -27,12 +27,12 @@ export const getInterfaces = (vm: VMKind, defaultValue = []): V1NetworkInterface ? defaultValue : vm.spec.template.spec.domain.devices.interfaces; -export const getNetworks = (vm: VMKind, defaultValue = []) => +export const getNetworks = (vm: VMKind, defaultValue = []): V1Network[] => _.get(vm, 'spec.template.spec.networks') == null ? defaultValue : vm.spec.template.spec.networks; export const getVolumes = (vm: VMKind, defaultValue = []) => _.get(vm, 'spec.template.spec.volumes') == null ? defaultValue : vm.spec.template.spec.volumes; export const getDataVolumeTemplates = (vm: VMKind, defaultValue = []) => - _.get(vm, 'spec.dataVolumeTemplates') == null ? defaultValue : vm.spec.dataVolumeTemplate; + _.get(vm, 'spec.dataVolumeTemplates') == null ? defaultValue : vm.spec.dataVolumeTemplates; export const getOperatingSystem = (vm: VMLikeEntityKind) => findKeySuffixValue(getLabels(vm), TEMPLATE_OS_LABEL); diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm/volume.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm/volume.ts index bf207df2f45..f269aca726a 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vm/volume.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm/volume.ts @@ -5,3 +5,6 @@ export const getVolumePersistentVolumeClaimName = (volume) => export const getVolumeDataVolumeName = (volume) => _.get(volume, 'dataVolume.name'); export const getVolumeCloudInitUserData = (volume) => _.get(volume, 'cloudInitNoCloud.userData'); + +export const getVolumeContainerImage = (volume) => + volume && volume.containerDisk && volume.containerDisk.image; diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1CDRomTarget.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1CDRomTarget.ts new file mode 100644 index 00000000000..727304fd4be --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1CDRomTarget.ts @@ -0,0 +1,38 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * + * @export + * @interface V1CDRomTarget + */ +export interface V1CDRomTarget { + /** + * Bus indicates the type of disk device to emulate. supported values: virtio, sata, scsi. + * @type {string} + * @memberof V1CDRomTarget + */ + bus?: string; + /** + * ReadOnly. Defaults to true. + * @type {boolean} + * @memberof V1CDRomTarget + */ + readonly?: boolean; + /** + * Tray indicates if the tray of the device is open or closed. Allowed values are \"open\" and \"closed\". Defaults to closed. +optional + * @type {string} + * @memberof V1CDRomTarget + */ + tray?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1CloudInitConfigDriveSource.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1CloudInitConfigDriveSource.ts new file mode 100644 index 00000000000..e8f5a731499 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1CloudInitConfigDriveSource.ts @@ -0,0 +1,58 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { V1LocalObjectReference } from './V1LocalObjectReference'; + +/** + * Represents a cloud-init config drive user data source. More info: https://cloudinit.readthedocs.io/en/latest/topics/datasources/configdrive.html + * @export + * @interface V1CloudInitConfigDriveSource + */ +export interface V1CloudInitConfigDriveSource { + /** + * NetworkData contains config drive inline cloud-init networkdata. + optional + * @type {string} + * @memberof V1CloudInitConfigDriveSource + */ + networkData?: string; + /** + * NetworkDataBase64 contains config drive cloud-init networkdata as a base64 encoded string. + optional + * @type {string} + * @memberof V1CloudInitConfigDriveSource + */ + networkDataBase64?: string; + /** + * + * @type {V1LocalObjectReference} + * @memberof V1CloudInitConfigDriveSource + */ + networkDataSecretRef?: V1LocalObjectReference; + /** + * + * @type {V1LocalObjectReference} + * @memberof V1CloudInitConfigDriveSource + */ + secretRef?: V1LocalObjectReference; + /** + * UserData contains config drive inline cloud-init userdata. + optional + * @type {string} + * @memberof V1CloudInitConfigDriveSource + */ + userData?: string; + /** + * UserDataBase64 contains config drive cloud-init userdata as a base64 encoded string. + optional + * @type {string} + * @memberof V1CloudInitConfigDriveSource + */ + userDataBase64?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1CloudInitNoCloudSource.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1CloudInitNoCloudSource.ts new file mode 100644 index 00000000000..4605d57ecac --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1CloudInitNoCloudSource.ts @@ -0,0 +1,58 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { V1LocalObjectReference } from './V1LocalObjectReference'; + +/** + * Represents a cloud-init nocloud user data source. More info: http://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html + * @export + * @interface V1CloudInitNoCloudSource + */ +export interface V1CloudInitNoCloudSource { + /** + * NetworkData contains NoCloud inline cloud-init networkdata. + optional + * @type {string} + * @memberof V1CloudInitNoCloudSource + */ + networkData?: string; + /** + * NetworkDataBase64 contains NoCloud cloud-init networkdata as a base64 encoded string. + optional + * @type {string} + * @memberof V1CloudInitNoCloudSource + */ + networkDataBase64?: string; + /** + * + * @type {V1LocalObjectReference} + * @memberof V1CloudInitNoCloudSource + */ + networkDataSecretRef?: V1LocalObjectReference; + /** + * + * @type {V1LocalObjectReference} + * @memberof V1CloudInitNoCloudSource + */ + secretRef?: V1LocalObjectReference; + /** + * UserData contains NoCloud inline cloud-init userdata. + optional + * @type {string} + * @memberof V1CloudInitNoCloudSource + */ + userData?: string; + /** + * UserDataBase64 contains NoCloud cloud-init userdata as a base64 encoded string. + optional + * @type {string} + * @memberof V1CloudInitNoCloudSource + */ + userDataBase64?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ConfigMapVolumeSource.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ConfigMapVolumeSource.ts new file mode 100644 index 00000000000..8906314747d --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ConfigMapVolumeSource.ts @@ -0,0 +1,32 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * ConfigMapVolumeSource adapts a ConfigMap into a volume. More info: https://kubernetes.io/docs/concepts/storage/volumes/#configmap + * @export + * @interface V1ConfigMapVolumeSource + */ +export interface V1ConfigMapVolumeSource { + /** + * Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + * @type {string} + * @memberof V1ConfigMapVolumeSource + */ + name?: string; + /** + * Specify whether the ConfigMap or it\'s keys must be defined +optional + * @type {boolean} + * @memberof V1ConfigMapVolumeSource + */ + optional?: boolean; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ContainerDiskSource.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ContainerDiskSource.ts new file mode 100644 index 00000000000..5ed16f9dc16 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ContainerDiskSource.ts @@ -0,0 +1,44 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * Represents a docker image with an embedded disk. + * @export + * @interface V1ContainerDiskSource + */ +export interface V1ContainerDiskSource { + /** + * Image is the name of the image with the embedded disk. + * @type {string} + * @memberof V1ContainerDiskSource + */ + image: string; + /** + * Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images +optional + * @type {string} + * @memberof V1ContainerDiskSource + */ + imagePullPolicy?: string; + /** + * ImagePullSecret is the name of the Docker registry secret required to pull the image. The secret must already exist. + * @type {string} + * @memberof V1ContainerDiskSource + */ + imagePullSecret?: string; + /** + * Path defines the path to disk file in the container + * @type {string} + * @memberof V1ContainerDiskSource + */ + path?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1DataVolumeSource.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1DataVolumeSource.ts new file mode 100644 index 00000000000..e909043e51b --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1DataVolumeSource.ts @@ -0,0 +1,26 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * + * @export + * @interface V1DataVolumeSource + */ +export interface V1DataVolumeSource { + /** + * Name represents the name of the DataVolume in the same namespace + * @type {string} + * @memberof V1DataVolumeSource + */ + name: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Disk.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Disk.ts new file mode 100644 index 00000000000..8d612ae2b20 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Disk.ts @@ -0,0 +1,79 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { V1CDRomTarget } from './V1CDRomTarget'; +import { V1DiskTarget } from './V1DiskTarget'; +import { V1FloppyTarget } from './V1FloppyTarget'; +import { V1LunTarget } from './V1LunTarget'; + +/** + * + * @export + * @interface V1Disk + */ +export interface V1Disk { + /** + * BootOrder is an integer value > 0, used to determine ordering of boot devices. Lower values take precedence. Each disk or interface that has a boot order must have a unique value. Disks without a boot order are not tried if a disk with a boot order exists. +optional + * @type {number} + * @memberof V1Disk + */ + bootOrder?: number; + /** + * Cache specifies which kvm disk cache mode should be used. +optional + * @type {string} + * @memberof V1Disk + */ + cache?: string; + /** + * + * @type {V1CDRomTarget} + * @memberof V1Disk + */ + cdrom?: V1CDRomTarget; + /** + * dedicatedIOThread indicates this disk should have an exclusive IO Thread. Enabling this implies useIOThreads = true. Defaults to false. +optional + * @type {boolean} + * @memberof V1Disk + */ + dedicatedIOThread?: boolean; + /** + * + * @type {V1DiskTarget} + * @memberof V1Disk + */ + disk?: V1DiskTarget; + /** + * + * @type {V1FloppyTarget} + * @memberof V1Disk + */ + floppy?: V1FloppyTarget; + /** + * + * @type {V1LunTarget} + * @memberof V1Disk + */ + lun?: V1LunTarget; + /** + * Name is the device name + * @type {string} + * @memberof V1Disk + */ + name: string; + /** + * Serial provides the ability to specify a serial number for the disk device. +optional + * @type {string} + * @memberof V1Disk + */ + serial?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1DiskTarget.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1DiskTarget.ts new file mode 100644 index 00000000000..1e18bb3a2c2 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1DiskTarget.ts @@ -0,0 +1,38 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * + * @export + * @interface V1DiskTarget + */ +export interface V1DiskTarget { + /** + * Bus indicates the type of disk device to emulate. supported values: virtio, sata, scsi. + * @type {string} + * @memberof V1DiskTarget + */ + bus?: string; + /** + * If specified, the virtual disk will be placed on the guests pci address with the specifed PCI address. For example: 0000:81:01.10 +optional + * @type {string} + * @memberof V1DiskTarget + */ + pciAddress?: string; + /** + * ReadOnly. Defaults to false. + * @type {boolean} + * @memberof V1DiskTarget + */ + readonly?: boolean; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1EmptyDiskSource.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1EmptyDiskSource.ts new file mode 100644 index 00000000000..b596424cf3d --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1EmptyDiskSource.ts @@ -0,0 +1,26 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * EmptyDisk represents a temporary disk which shares the vmis lifecycle. + * @export + * @interface V1EmptyDiskSource + */ +export interface V1EmptyDiskSource { + /** + * Capacity of the sparse disk. + * @type {string} + * @memberof V1EmptyDiskSource + */ + capacity: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1EphemeralVolumeSource.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1EphemeralVolumeSource.ts new file mode 100644 index 00000000000..4e68bb5afc7 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1EphemeralVolumeSource.ts @@ -0,0 +1,28 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { V1PersistentVolumeClaimVolumeSource } from './V1PersistentVolumeClaimVolumeSource'; + +/** + * + * @export + * @interface V1EphemeralVolumeSource + */ +export interface V1EphemeralVolumeSource { + /** + * + * @type {V1PersistentVolumeClaimVolumeSource} + * @memberof V1EphemeralVolumeSource + */ + persistentVolumeClaim?: V1PersistentVolumeClaimVolumeSource; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1FloppyTarget.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1FloppyTarget.ts new file mode 100644 index 00000000000..01ebcf63e37 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1FloppyTarget.ts @@ -0,0 +1,32 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * + * @export + * @interface V1FloppyTarget + */ +export interface V1FloppyTarget { + /** + * ReadOnly. Defaults to false. + * @type {boolean} + * @memberof V1FloppyTarget + */ + readonly?: boolean; + /** + * Tray indicates if the tray of the device is open or closed. Allowed values are \"open\" and \"closed\". Defaults to closed. +optional + * @type {string} + * @memberof V1FloppyTarget + */ + tray?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1HostDisk.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1HostDisk.ts new file mode 100644 index 00000000000..6c33c1a1fae --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1HostDisk.ts @@ -0,0 +1,44 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * Represents a disk created on the cluster level + * @export + * @interface V1HostDisk + */ +export interface V1HostDisk { + /** + * Capacity of the sparse disk +optional + * @type {string} + * @memberof V1HostDisk + */ + capacity?: string; + /** + * The path to HostDisk image located on the cluster + * @type {string} + * @memberof V1HostDisk + */ + path: string; + /** + * Shared indicate whether the path is shared between nodes + * @type {boolean} + * @memberof V1HostDisk + */ + shared?: boolean; + /** + * Contains information if disk.img exists or should be created allowed options are \'Disk\' and \'DiskOrCreate\' + * @type {string} + * @memberof V1HostDisk + */ + type: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Initializer.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Initializer.ts new file mode 100644 index 00000000000..8fc95dcdcf7 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Initializer.ts @@ -0,0 +1,26 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * Initializer is information about an initializer that has not yet completed. + * @export + * @interface V1Initializer + */ +export interface V1Initializer { + /** + * name of the process that is responsible for initializing this object. + * @type {string} + * @memberof V1Initializer + */ + name: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Initializers.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Initializers.ts new file mode 100644 index 00000000000..36e7ca29cb9 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Initializers.ts @@ -0,0 +1,35 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { V1Initializer } from './V1Initializer'; +import { V1Status } from './V1Status'; + +/** + * Initializers tracks the progress of initialization. + * @export + * @interface V1Initializers + */ +export interface V1Initializers { + /** + * Pending is a list of initializers that must execute in order before this object is visible. When the last pending initializer is removed, and no failing result is set, the initializers struct will be set to nil and the object is considered as initialized and visible to all clients. + * @type {Array} + * @memberof V1Initializers + */ + pending: V1Initializer[]; + /** + * + * @type {V1Status} + * @memberof V1Initializers + */ + result?: V1Status; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1LabelSelector.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1LabelSelector.ts new file mode 100644 index 00000000000..16c4efb3014 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1LabelSelector.ts @@ -0,0 +1,34 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { V1LabelSelectorRequirement } from './V1LabelSelectorRequirement'; + +/** + * A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects. + * @export + * @interface V1LabelSelector + */ +export interface V1LabelSelector { + /** + * matchExpressions is a list of label selector requirements. The requirements are ANDed. + * @type {Array} + * @memberof V1LabelSelector + */ + matchExpressions?: V1LabelSelectorRequirement[]; + /** + * matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed. + * @type {object} + * @memberof V1LabelSelector + */ + matchLabels?: object; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1LabelSelectorRequirement.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1LabelSelectorRequirement.ts new file mode 100644 index 00000000000..ae893253677 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1LabelSelectorRequirement.ts @@ -0,0 +1,38 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + * @export + * @interface V1LabelSelectorRequirement + */ +export interface V1LabelSelectorRequirement { + /** + * key is the label key that the selector applies to. + * @type {string} + * @memberof V1LabelSelectorRequirement + */ + key: string; + /** + * operator represents a key\'s relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + * @type {string} + * @memberof V1LabelSelectorRequirement + */ + operator: string; + /** + * values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + * @type {Array} + * @memberof V1LabelSelectorRequirement + */ + values?: string[]; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ListMeta.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ListMeta.ts new file mode 100644 index 00000000000..622d0b3bcaa --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ListMeta.ts @@ -0,0 +1,38 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}. + * @export + * @interface V1ListMeta + */ +export interface V1ListMeta { + /** + * continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message. + * @type {string} + * @memberof V1ListMeta + */ + _continue?: string; + /** + * String that identifies the server\'s internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency + * @type {string} + * @memberof V1ListMeta + */ + resourceVersion?: string; + /** + * selfLink is a URL representing this object. Populated by the system. Read-only. + * @type {string} + * @memberof V1ListMeta + */ + selfLink?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1LocalObjectReference.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1LocalObjectReference.ts new file mode 100644 index 00000000000..7af7145976b --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1LocalObjectReference.ts @@ -0,0 +1,26 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace. + * @export + * @interface V1LocalObjectReference + */ +export interface V1LocalObjectReference { + /** + * Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + * @type {string} + * @memberof V1LocalObjectReference + */ + name?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1LunTarget.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1LunTarget.ts new file mode 100644 index 00000000000..4c64cc5b568 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1LunTarget.ts @@ -0,0 +1,32 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * + * @export + * @interface V1LunTarget + */ +export interface V1LunTarget { + /** + * Bus indicates the type of disk device to emulate. supported values: virtio, sata, scsi. + * @type {string} + * @memberof V1LunTarget + */ + bus?: string; + /** + * ReadOnly. Defaults to false. + * @type {boolean} + * @memberof V1LunTarget + */ + readonly?: boolean; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ObjectMeta.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ObjectMeta.ts new file mode 100644 index 00000000000..c5ab19d5f05 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ObjectMeta.ts @@ -0,0 +1,113 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { V1Initializers } from './V1Initializers'; +import { V1OwnerReference } from './V1OwnerReference'; + +/** + * ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create. + * @export + * @interface V1ObjectMeta + */ +export interface V1ObjectMeta { + /** + * Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations + * @type {object} + * @memberof V1ObjectMeta + */ + annotations?: object; + /** + * The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request. + * @type {string} + * @memberof V1ObjectMeta + */ + clusterName?: string; + /** + * Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only. + * @type {number} + * @memberof V1ObjectMeta + */ + deletionGracePeriodSeconds?: number; + /** + * DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested. Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + * @type {string} + * @memberof V1ObjectMeta + */ + deletionTimestamp?: string; + /** + * Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. + * @type {Array} + * @memberof V1ObjectMeta + */ + finalizers?: string[]; + /** + * GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency + * @type {string} + * @memberof V1ObjectMeta + */ + generateName?: string; + /** + * A sequence number representing a specific generation of the desired state. Populated by the system. Read-only. + * @type {number} + * @memberof V1ObjectMeta + */ + generation?: number; + /** + * + * @type {V1Initializers} + * @memberof V1ObjectMeta + */ + initializers?: V1Initializers; + /** + * Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels + * @type {object} + * @memberof V1ObjectMeta + */ + labels?: object; + /** + * Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names + * @type {string} + * @memberof V1ObjectMeta + */ + name?: string; + /** + * Namespace defines the space within each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces + * @type {string} + * @memberof V1ObjectMeta + */ + namespace?: string; + /** + * List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller. + * @type {Array} + * @memberof V1ObjectMeta + */ + ownerReferences?: V1OwnerReference[]; + /** + * An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency + * @type {string} + * @memberof V1ObjectMeta + */ + resourceVersion?: string; + /** + * SelfLink is a URL representing this object. Populated by the system. Read-only. + * @type {string} + * @memberof V1ObjectMeta + */ + selfLink?: string; + /** + * UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. Populated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids + * @type {string} + * @memberof V1ObjectMeta + */ + uid?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1OwnerReference.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1OwnerReference.ts new file mode 100644 index 00000000000..29838e3358c --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1OwnerReference.ts @@ -0,0 +1,56 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field. + * @export + * @interface V1OwnerReference + */ +export interface V1OwnerReference { + /** + * API version of the referent. + * @type {string} + * @memberof V1OwnerReference + */ + apiVersion: string; + /** + * If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned. + * @type {boolean} + * @memberof V1OwnerReference + */ + blockOwnerDeletion?: boolean; + /** + * If true, this reference points to the managing controller. + * @type {boolean} + * @memberof V1OwnerReference + */ + controller?: boolean; + /** + * Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds + * @type {string} + * @memberof V1OwnerReference + */ + kind: string; + /** + * Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names + * @type {string} + * @memberof V1OwnerReference + */ + name: string; + /** + * UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids + * @type {string} + * @memberof V1OwnerReference + */ + uid: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1PersistentVolumeClaimSpec.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1PersistentVolumeClaimSpec.ts new file mode 100644 index 00000000000..e58aaca2991 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1PersistentVolumeClaimSpec.ts @@ -0,0 +1,66 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { V1LabelSelector } from './V1LabelSelector'; +import { V1ResourceRequirements } from './V1ResourceRequirements'; +import { V1TypedLocalObjectReference } from './V1TypedLocalObjectReference'; + +/** + * PersistentVolumeClaimSpec describes the common attributes of storage devices and allows a Source for provider-specific attributes + * @export + * @interface V1PersistentVolumeClaimSpec + */ +export interface V1PersistentVolumeClaimSpec { + /** + * AccessModes contains the desired access modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + * @type {Array} + * @memberof V1PersistentVolumeClaimSpec + */ + accessModes?: object[] | string[]; + /** + * + * @type {V1TypedLocalObjectReference} + * @memberof V1PersistentVolumeClaimSpec + */ + dataSource?: V1TypedLocalObjectReference; + /** + * + * @type {V1ResourceRequirements} + * @memberof V1PersistentVolumeClaimSpec + */ + resources?: V1ResourceRequirements; + /** + * + * @type {V1LabelSelector} + * @memberof V1PersistentVolumeClaimSpec + */ + selector?: V1LabelSelector; + /** + * Name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + * @type {string} + * @memberof V1PersistentVolumeClaimSpec + */ + storageClassName?: string; + /** + * + * @type {object} + * @memberof V1PersistentVolumeClaimSpec + */ + volumeMode?: object | string; + /** + * VolumeName is the binding reference to the PersistentVolume backing this claim. + * @type {string} + * @memberof V1PersistentVolumeClaimSpec + */ + volumeName?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1PersistentVolumeClaimVolumeSource.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1PersistentVolumeClaimVolumeSource.ts new file mode 100644 index 00000000000..52009b75fc2 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1PersistentVolumeClaimVolumeSource.ts @@ -0,0 +1,32 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * PersistentVolumeClaimVolumeSource references the user\'s PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system). + * @export + * @interface V1PersistentVolumeClaimVolumeSource + */ +export interface V1PersistentVolumeClaimVolumeSource { + /** + * ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + * @type {string} + * @memberof V1PersistentVolumeClaimVolumeSource + */ + claimName: string; + /** + * Will force the ReadOnly setting in VolumeMounts. Default false. + * @type {boolean} + * @memberof V1PersistentVolumeClaimVolumeSource + */ + readOnly?: boolean; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ResourceRequirements.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ResourceRequirements.ts new file mode 100644 index 00000000000..b2f18b4fe31 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ResourceRequirements.ts @@ -0,0 +1,38 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * + * @export + * @interface V1ResourceRequirements + */ +export interface V1ResourceRequirements { + /** + * Limits describes the maximum amount of compute resources allowed. Valid resource keys are \"memory\" and \"cpu\". +optional + * @type {object} + * @memberof V1ResourceRequirements + */ + limits?: object; + /** + * Don\'t ask the scheduler to take the guest-management overhead into account. Instead put the overhead only into the container\'s memory limit. This can lead to crashes if all memory is in use on a node. Defaults to false. + * @type {boolean} + * @memberof V1ResourceRequirements + */ + overcommitGuestOverhead?: boolean; + /** + * Requests is a description of the initial vmi resources. Valid resource keys are \"memory\" and \"cpu\". +optional + * @type {object} + * @memberof V1ResourceRequirements + */ + requests?: object; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1SecretVolumeSource.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1SecretVolumeSource.ts new file mode 100644 index 00000000000..f23ee5ad78c --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1SecretVolumeSource.ts @@ -0,0 +1,32 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * SecretVolumeSource adapts a Secret into a volume. + * @export + * @interface V1SecretVolumeSource + */ +export interface V1SecretVolumeSource { + /** + * Specify whether the Secret or it\'s keys must be defined +optional + * @type {boolean} + * @memberof V1SecretVolumeSource + */ + optional?: boolean; + /** + * Name of the secret in the pod\'s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret +optional + * @type {string} + * @memberof V1SecretVolumeSource + */ + secretName?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ServiceAccountVolumeSource.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ServiceAccountVolumeSource.ts new file mode 100644 index 00000000000..bedfd0c5a98 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1ServiceAccountVolumeSource.ts @@ -0,0 +1,26 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * ServiceAccountVolumeSource adapts a ServiceAccount into a volume. + * @export + * @interface V1ServiceAccountVolumeSource + */ +export interface V1ServiceAccountVolumeSource { + /** + * Name of the service account in the pod\'s namespace to use. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + * @type {string} + * @memberof V1ServiceAccountVolumeSource + */ + serviceAccountName?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Status.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Status.ts new file mode 100644 index 00000000000..e6f32857165 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Status.ts @@ -0,0 +1,71 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { V1ListMeta } from './V1ListMeta'; +import { V1StatusDetails } from './V1StatusDetails'; + +/** + * Status is a return value for calls that don\'t return other objects. + * @export + * @interface V1Status + */ +export interface V1Status { + /** + * APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources + * @type {string} + * @memberof V1Status + */ + apiVersion?: string; + /** + * Suggested HTTP return code for this status, 0 if not set. + * @type {number} + * @memberof V1Status + */ + code?: number; + /** + * + * @type {V1StatusDetails} + * @memberof V1Status + */ + details?: V1StatusDetails; + /** + * Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds + * @type {string} + * @memberof V1Status + */ + kind?: string; + /** + * A human-readable description of the status of this operation. + * @type {string} + * @memberof V1Status + */ + message?: string; + /** + * + * @type {V1ListMeta} + * @memberof V1Status + */ + metadata?: V1ListMeta; + /** + * A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it. + * @type {string} + * @memberof V1Status + */ + reason?: string; + /** + * Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + * @type {string} + * @memberof V1Status + */ + status?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1StatusCause.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1StatusCause.ts new file mode 100644 index 00000000000..c1b297fd8e5 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1StatusCause.ts @@ -0,0 +1,38 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered. + * @export + * @interface V1StatusCause + */ +export interface V1StatusCause { + /** + * The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. Examples: \"name\" - the field \"name\" on the current resource \"items[0].name\" - the field \"name\" on the first array entry in \"items\" + * @type {string} + * @memberof V1StatusCause + */ + field?: string; + /** + * A human-readable description of the cause of the error. This field may be presented as-is to a reader. + * @type {string} + * @memberof V1StatusCause + */ + message?: string; + /** + * A machine-readable description of the cause of the error. If this value is empty there is no information available. + * @type {string} + * @memberof V1StatusCause + */ + reason?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1StatusDetails.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1StatusDetails.ts new file mode 100644 index 00000000000..917d6b4d68a --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1StatusDetails.ts @@ -0,0 +1,58 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { V1StatusCause } from './V1StatusCause'; + +/** + * StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined. + * @export + * @interface V1StatusDetails + */ +export interface V1StatusDetails { + /** + * The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes. + * @type {Array} + * @memberof V1StatusDetails + */ + causes?: V1StatusCause[]; + /** + * The group attribute of the resource associated with the status StatusReason. + * @type {string} + * @memberof V1StatusDetails + */ + group?: string; + /** + * The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds + * @type {string} + * @memberof V1StatusDetails + */ + kind?: string; + /** + * The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described). + * @type {string} + * @memberof V1StatusDetails + */ + name?: string; + /** + * If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action. + * @type {number} + * @memberof V1StatusDetails + */ + retryAfterSeconds?: number; + /** + * UID of the resource. (when there is a single resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids + * @type {string} + * @memberof V1StatusDetails + */ + uid?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1TypedLocalObjectReference.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1TypedLocalObjectReference.ts new file mode 100644 index 00000000000..1c80d4b0ef0 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1TypedLocalObjectReference.ts @@ -0,0 +1,38 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * TypedLocalObjectReference contains enough information to let you locate the typed referenced object inside the same namespace. + * @export + * @interface V1TypedLocalObjectReference + */ +export interface V1TypedLocalObjectReference { + /** + * APIGroup is the group for the resource being referenced. If APIGroup is not specified, the specified Kind must be in the core API group. For any other third-party types, APIGroup is required. + * @type {string} + * @memberof V1TypedLocalObjectReference + */ + apiGroup: string; + /** + * Kind is the type of resource being referenced + * @type {string} + * @memberof V1TypedLocalObjectReference + */ + kind: string; + /** + * Name is the name of resource being referenced + * @type {string} + * @memberof V1TypedLocalObjectReference + */ + name: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Volume.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Volume.ts new file mode 100644 index 00000000000..dc54d65d1f4 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1Volume.ts @@ -0,0 +1,104 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { V1CloudInitConfigDriveSource } from './V1CloudInitConfigDriveSource'; +import { V1CloudInitNoCloudSource } from './V1CloudInitNoCloudSource'; +import { V1ConfigMapVolumeSource } from './V1ConfigMapVolumeSource'; +import { V1ContainerDiskSource } from './V1ContainerDiskSource'; +import { V1DataVolumeSource } from './V1DataVolumeSource'; +import { V1EmptyDiskSource } from './V1EmptyDiskSource'; +import { V1EphemeralVolumeSource } from './V1EphemeralVolumeSource'; +import { V1HostDisk } from './V1HostDisk'; +import { V1PersistentVolumeClaimVolumeSource } from './V1PersistentVolumeClaimVolumeSource'; +import { V1SecretVolumeSource } from './V1SecretVolumeSource'; +import { V1ServiceAccountVolumeSource } from './V1ServiceAccountVolumeSource'; + +/** + * Volume represents a named volume in a vmi. + * @export + * @interface V1Volume + */ +export interface V1Volume { + /** + * + * @type {V1CloudInitConfigDriveSource} + * @memberof V1Volume + */ + cloudInitConfigDrive?: V1CloudInitConfigDriveSource; + /** + * + * @type {V1CloudInitNoCloudSource} + * @memberof V1Volume + */ + cloudInitNoCloud?: V1CloudInitNoCloudSource; + /** + * + * @type {V1ConfigMapVolumeSource} + * @memberof V1Volume + */ + configMap?: V1ConfigMapVolumeSource; + /** + * + * @type {V1ContainerDiskSource} + * @memberof V1Volume + */ + containerDisk?: V1ContainerDiskSource; + /** + * + * @type {V1DataVolumeSource} + * @memberof V1Volume + */ + dataVolume?: V1DataVolumeSource; + /** + * + * @type {V1EmptyDiskSource} + * @memberof V1Volume + */ + emptyDisk?: V1EmptyDiskSource; + /** + * + * @type {V1EphemeralVolumeSource} + * @memberof V1Volume + */ + ephemeral?: V1EphemeralVolumeSource; + /** + * + * @type {V1HostDisk} + * @memberof V1Volume + */ + hostDisk?: V1HostDisk; + /** + * Volume\'s name. Must be a DNS_LABEL and unique within the vmi. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + * @type {string} + * @memberof V1Volume + */ + name: string; + /** + * + * @type {V1PersistentVolumeClaimVolumeSource} + * @memberof V1Volume + */ + persistentVolumeClaim?: V1PersistentVolumeClaimVolumeSource; + /** + * + * @type {V1SecretVolumeSource} + * @memberof V1Volume + */ + secret?: V1SecretVolumeSource; + /** + * + * @type {V1ServiceAccountVolumeSource} + * @memberof V1Volume + */ + serviceAccount?: V1ServiceAccountVolumeSource; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolume.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolume.ts new file mode 100644 index 00000000000..27fe82345bf --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolume.ts @@ -0,0 +1,54 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { V1ObjectMeta } from './V1ObjectMeta'; +import { V1alpha1DataVolumeSpec } from './V1alpha1DataVolumeSpec'; +import { V1alpha1DataVolumeStatus } from './V1alpha1DataVolumeStatus'; + +/** + * DataVolume provides a representation of our data volume +genclient +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + * @export + * @interface V1alpha1DataVolume + */ +export interface V1alpha1DataVolume { + /** + * APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources + * @type {string} + * @memberof V1alpha1DataVolume + */ + apiVersion?: string; + /** + * Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds + * @type {string} + * @memberof V1alpha1DataVolume + */ + kind?: string; + /** + * + * @type {V1ObjectMeta} + * @memberof V1alpha1DataVolume + */ + metadata?: V1ObjectMeta; + /** + * + * @type {V1alpha1DataVolumeSpec} + * @memberof V1alpha1DataVolume + */ + spec: V1alpha1DataVolumeSpec; + /** + * + * @type {V1alpha1DataVolumeStatus} + * @memberof V1alpha1DataVolume + */ + status?: V1alpha1DataVolumeStatus; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSource.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSource.ts new file mode 100644 index 00000000000..cec7495735e --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSource.ts @@ -0,0 +1,61 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { V1alpha1DataVolumeSourceHTTP } from './V1alpha1DataVolumeSourceHTTP'; +import { V1alpha1DataVolumeSourcePVC } from './V1alpha1DataVolumeSourcePVC'; +import { V1alpha1DataVolumeSourceRegistry } from './V1alpha1DataVolumeSourceRegistry'; +import { V1alpha1DataVolumeSourceS3 } from './V1alpha1DataVolumeSourceS3'; + +/** + * DataVolumeSource represents the source for our Data Volume, this can be HTTP, S3, Registry or an existing PVC + * @export + * @interface V1alpha1DataVolumeSource + */ +export interface V1alpha1DataVolumeSource { + /** + * DataVolumeBlankImage provides the parameters to create a new raw blank image for the PVC + * @type {object} + * @memberof V1alpha1DataVolumeSource + */ + blank?: object; + /** + * + * @type {V1alpha1DataVolumeSourceHTTP} + * @memberof V1alpha1DataVolumeSource + */ + http?: V1alpha1DataVolumeSourceHTTP; + /** + * + * @type {V1alpha1DataVolumeSourcePVC} + * @memberof V1alpha1DataVolumeSource + */ + pvc?: V1alpha1DataVolumeSourcePVC; + /** + * + * @type {V1alpha1DataVolumeSourceRegistry} + * @memberof V1alpha1DataVolumeSource + */ + registry?: V1alpha1DataVolumeSourceRegistry; + /** + * + * @type {V1alpha1DataVolumeSourceS3} + * @memberof V1alpha1DataVolumeSource + */ + s3?: V1alpha1DataVolumeSourceS3; + /** + * DataVolumeSourceUpload provides the parameters to create a Data Volume by uploading the source + * @type {object} + * @memberof V1alpha1DataVolumeSource + */ + upload?: object; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSourceHTTP.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSourceHTTP.ts new file mode 100644 index 00000000000..66b962671de --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSourceHTTP.ts @@ -0,0 +1,38 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * DataVolumeSourceHTTP provides the parameters to create a Data Volume from an HTTP source + * @export + * @interface V1alpha1DataVolumeSourceHTTP + */ +export interface V1alpha1DataVolumeSourceHTTP { + /** + * CertConfigMap provides a reference to the Registry certs + * @type {string} + * @memberof V1alpha1DataVolumeSourceHTTP + */ + certConfigMap?: string; + /** + * SecretRef provides the secret reference needed to access the HTTP source + * @type {string} + * @memberof V1alpha1DataVolumeSourceHTTP + */ + secretRef?: string; + /** + * URL is the URL of the http source + * @type {string} + * @memberof V1alpha1DataVolumeSourceHTTP + */ + url?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSourcePVC.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSourcePVC.ts new file mode 100644 index 00000000000..0de02d097c7 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSourcePVC.ts @@ -0,0 +1,32 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * DataVolumeSourcePVC provides the parameters to create a Data Volume from an existing PVC + * @export + * @interface V1alpha1DataVolumeSourcePVC + */ +export interface V1alpha1DataVolumeSourcePVC { + /** + * + * @type {string} + * @memberof V1alpha1DataVolumeSourcePVC + */ + name?: string; + /** + * + * @type {string} + * @memberof V1alpha1DataVolumeSourcePVC + */ + namespace?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSourceRegistry.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSourceRegistry.ts new file mode 100644 index 00000000000..144ffb0f9aa --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSourceRegistry.ts @@ -0,0 +1,38 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * DataVolumeSourceRegistry provides the parameters to create a Data Volume from an registry source + * @export + * @interface V1alpha1DataVolumeSourceRegistry + */ +export interface V1alpha1DataVolumeSourceRegistry { + /** + * CertConfigMap provides a reference to the Registry certs + * @type {string} + * @memberof V1alpha1DataVolumeSourceRegistry + */ + certConfigMap?: string; + /** + * SecretRef provides the secret reference needed to access the Registry source + * @type {string} + * @memberof V1alpha1DataVolumeSourceRegistry + */ + secretRef?: string; + /** + * URL is the url of the Registry source + * @type {string} + * @memberof V1alpha1DataVolumeSourceRegistry + */ + url?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSourceS3.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSourceS3.ts new file mode 100644 index 00000000000..b9597159bda --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSourceS3.ts @@ -0,0 +1,32 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * DataVolumeSourceS3 provides the parameters to create a Data Volume from an S3 source + * @export + * @interface V1alpha1DataVolumeSourceS3 + */ +export interface V1alpha1DataVolumeSourceS3 { + /** + * SecretRef provides the secret reference needed to access the S3 source + * @type {string} + * @memberof V1alpha1DataVolumeSourceS3 + */ + secretRef?: string; + /** + * URL is the url of the S3 source + * @type {string} + * @memberof V1alpha1DataVolumeSourceS3 + */ + url?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSpec.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSpec.ts new file mode 100644 index 00000000000..ec125acb7ba --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeSpec.ts @@ -0,0 +1,41 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { V1PersistentVolumeClaimSpec } from './V1PersistentVolumeClaimSpec'; +import { V1alpha1DataVolumeSource } from './V1alpha1DataVolumeSource'; + +/** + * DataVolumeSpec defines our specification for a DataVolume type + * @export + * @interface V1alpha1DataVolumeSpec + */ +export interface V1alpha1DataVolumeSpec { + /** + * DataVolumeContentType options: \"kubevirt\", \"archive\" + * @type {string} + * @memberof V1alpha1DataVolumeSpec + */ + contentType?: string; + /** + * + * @type {V1PersistentVolumeClaimSpec} + * @memberof V1alpha1DataVolumeSpec + */ + pvc: V1PersistentVolumeClaimSpec; + /** + * + * @type {V1alpha1DataVolumeSource} + * @memberof V1alpha1DataVolumeSpec + */ + source: V1alpha1DataVolumeSource; +} diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeStatus.ts b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeStatus.ts new file mode 100644 index 00000000000..4305660e26a --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolumeStatus.ts @@ -0,0 +1,32 @@ +// tslint:disable +/** + * KubeVirt API + * This is KubeVirt API an add-on for Kubernetes. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: kubevirt-dev@googlegroups.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** + * DataVolumeStatus provides the parameters to store the phase of the Data Volume + * @export + * @interface V1alpha1DataVolumeStatus + */ +export interface V1alpha1DataVolumeStatus { + /** + * Phase is the current phase of the data volume + * @type {string} + * @memberof V1alpha1DataVolumeStatus + */ + phase?: string; + /** + * + * @type {string} + * @memberof V1alpha1DataVolumeStatus + */ + progress?: string; +} diff --git a/frontend/packages/kubevirt-plugin/src/utils/immutable.ts b/frontend/packages/kubevirt-plugin/src/utils/immutable.ts index 4732feaf434..53e6b63c691 100644 --- a/frontend/packages/kubevirt-plugin/src/utils/immutable.ts +++ b/frontend/packages/kubevirt-plugin/src/utils/immutable.ts @@ -3,7 +3,21 @@ import { List } from 'immutable'; export const concatImmutableLists = (...args) => args.filter((list) => list).reduce((acc, nextArray) => acc.concat(nextArray), List()); -export const immutableListToShallowJS = (list, defaultValue = []) => +export const iFirehoseResultToJS = (immutableValue, isList = true) => { + if (!immutableValue) { + return {}; + } + + const data = immutableValue.get('data'); + + return { + data: data && isList ? data.toArray().map((p) => p.toJSON()) : data.toJS(), + loadError: immutableValue.get('loadError'), + loaded: immutableValue.get('loaded'), + }; +}; + +export const immutableListToShallowJS = (list, defaultValue: A[] = []): A[] => list ? list.toArray().map((p) => p.toJSON()) : defaultValue; export const hasTruthyValue = (obj) => !!(obj && !!obj.find((value) => value)); diff --git a/frontend/packages/kubevirt-plugin/src/utils/sort.ts b/frontend/packages/kubevirt-plugin/src/utils/sort.ts index 08810ebad51..99c95c14257 100644 --- a/frontend/packages/kubevirt-plugin/src/utils/sort.ts +++ b/frontend/packages/kubevirt-plugin/src/utils/sort.ts @@ -30,9 +30,19 @@ export const flavorSort = (array = []) => return resolvedFlavorA - resolvedFlavorB; }); -export const ignoreCaseSort = (array = [], byPath: string[] = undefined) => - array.sort((a, b) => - (byPath ? _.get(a, byPath, '') : a) - .toLowerCase() - .localeCompare((byPath ? _.get(b, byPath, '') : b).toLowerCase()), - ); +export const ignoreCaseSort = ( + array: T[] = [], + byPath: string[] = undefined, + byValueResolver: (item: T) => string = undefined, +) => { + const resolve = (v) => { + const result = _.isFunction(byValueResolver) + ? byValueResolver(v) + : byPath + ? _.get(v, byPath, '') + : v; + + return result == null ? '' : result.toLowerCase(); + }; + return array.sort((a, b) => resolve(a).localeCompare(resolve(b))); +}; diff --git a/frontend/packages/kubevirt-plugin/src/utils/utils.ts b/frontend/packages/kubevirt-plugin/src/utils/utils.ts index cf3213e7552..1de3edc4fc2 100644 --- a/frontend/packages/kubevirt-plugin/src/utils/utils.ts +++ b/frontend/packages/kubevirt-plugin/src/utils/utils.ts @@ -1,5 +1,6 @@ import { referenceForModel } from '@console/internal/module/k8s'; import { getName, getNamespace } from '@console/shared/src'; +import * as _ from 'lodash'; import { VirtualMachineModel } from '../models'; export const getSequence = (from, to) => Array.from({ length: to - from + 1 }, (v, i) => i + from); @@ -19,6 +20,19 @@ export const setNativeValue = (element, value) => { export const getFullResourceId = (obj) => `${referenceForModel(obj)}~${getNamespace(obj)}~${getName(obj)}`; +export const getNextIDResolver = (entities: { id?: string }[]) => { + let maxNetworkID = + _.max(entities.map((entity) => (entity.id == null ? 0 : _.toSafeInteger(entity.id)))) || 0; + return () => _.toString(++maxNetworkID); +}; + +export const wrapWithProgress = (setProgress: (inProgress: boolean) => void) => ( + promise: Promise, +) => { + setProgress(true); + promise.then(() => setProgress(false)).catch(() => setProgress(false)); +}; + export const getVMLikeModelName = (isCreateTemplate: boolean) => isCreateTemplate ? 'virtual machine template' : VirtualMachineModel.label.toLowerCase(); diff --git a/frontend/packages/kubevirt-plugin/src/utils/validations/strings.ts b/frontend/packages/kubevirt-plugin/src/utils/validations/strings.ts index 189e539322e..e7203c4df8e 100644 --- a/frontend/packages/kubevirt-plugin/src/utils/validations/strings.ts +++ b/frontend/packages/kubevirt-plugin/src/utils/validations/strings.ts @@ -15,3 +15,6 @@ export const VIRTUAL_MACHINE_TEMPLATE_EXISTS = 'is already used in another templ export const MAC_ADDRESS_INVALID_ERROR = 'Invalid MAC address format'; export const NIC_NAME_EXISTS = 'Interface with this name already exists'; +export const NETWORK_MULTUS_NAME_EXISTS = 'Multus network with this name already exists'; + +export const POSITIVE_SIZE_ERROR = 'must be positive'; diff --git a/frontend/packages/kubevirt-plugin/src/utils/validations/types.ts b/frontend/packages/kubevirt-plugin/src/utils/validations/types.ts index 457c31bf8c1..9db7da6be72 100644 --- a/frontend/packages/kubevirt-plugin/src/utils/validations/types.ts +++ b/frontend/packages/kubevirt-plugin/src/utils/validations/types.ts @@ -1,6 +1,6 @@ export enum ValidationErrorType { Error = 'error', - TrivialError = 'trivial-error', + TrivialError = 'trivial-error', // should not be visible but affects data validation Warn = 'warning', Info = 'info', } diff --git a/frontend/packages/kubevirt-plugin/src/utils/validations/vm/disk.ts b/frontend/packages/kubevirt-plugin/src/utils/validations/vm/disk.ts index b119bafb1b6..50bafa9bc3f 100644 --- a/frontend/packages/kubevirt-plugin/src/utils/validations/vm/disk.ts +++ b/frontend/packages/kubevirt-plugin/src/utils/validations/vm/disk.ts @@ -1,21 +1,117 @@ -import { EntityMap } from '@console/shared'; -import { validateDNS1123SubdomainValue } from '../common'; -import { addMissingSubject } from '../../grammar'; -import { ValidationErrorType } from '../types'; +import { + getValidationObject, + validateDNS1123SubdomainValue, + validateTrim, + validateURL, +} from '../common'; +import { addMissingSubject, makeSentence } from '../../grammar'; +import { DiskWrapper } from '../../../k8s/wrapper/vm/disk-wrapper'; +import { VolumeWrapper } from '../../../k8s/wrapper/vm/volume-wrapper'; +import { DataVolumeWrapper } from '../../../k8s/wrapper/vm/data-volume-wrapper'; +import { ValidationErrorType, ValidationObject } from '../types'; +import { POSITIVE_SIZE_ERROR } from '../strings'; +import { StorageUISource } from '../../../components/modals/disk-modal/storage-ui-source'; -export const validateDiskName = (name: string, diskLookup: EntityMap) => { +const validateDiskName = (name: string, usedDiskNames: Set): ValidationObject => { let validation = validateDNS1123SubdomainValue(name); if (validation) { validation.message = addMissingSubject(validation.message, 'Name'); } - if (!validation && diskLookup[name]) { - validation = { - type: ValidationErrorType.Error, - message: 'Disk with this name already exists!', - }; + if (!validation && usedDiskNames && usedDiskNames.has(name)) { + validation = getValidationObject('Disk with this name already exists!'); } return validation; }; + +const validatePVCName = (pvcName: string, usedPVCNames: Set): ValidationObject => { + if (usedPVCNames && usedPVCNames.has(pvcName)) { + getValidationObject('PVC with this name is already used by this VM!'); + } + + return null; +}; + +const getEmptyDiskSizeValidation = (): ValidationObject => + getValidationObject( + makeSentence(addMissingSubject(POSITIVE_SIZE_ERROR, 'Size')), + ValidationErrorType.TrivialError, + ); + +export const validateDisk = ( + disk: DiskWrapper, + volume: VolumeWrapper, + dataVolume: DataVolumeWrapper, + { + usedDiskNames, + usedPVCNames, + }: { + usedDiskNames?: Set; + usedPVCNames?: Set; + }, +): UIDiskValidation => { + const validations = { + name: validateDiskName(disk && disk.getName(), usedDiskNames), + size: null, + url: null, + container: null, + pvc: null, + }; + let hasAllRequiredFilled = disk && disk.getName() && volume && volume.hasType(); + + const addRequired = (addon) => { + if (hasAllRequiredFilled) { + hasAllRequiredFilled = hasAllRequiredFilled && addon; + } + }; + + const type = StorageUISource.fromTypes(volume.getType(), dataVolume && dataVolume.getType()); + + if (type) { + if (type.requiresURL()) { + const url = dataVolume && dataVolume.getURL(); + addRequired(url); + validations.url = validateURL(url, { subject: 'URL' }); + } + + if (type.requiresContainerImage()) { + const container = volume.getContainerImage(); + addRequired(container); + validations.container = validateTrim(container, { subject: 'Container' }); + } + + if (type.requiresDatavolume()) { + addRequired(dataVolume); + if (!dataVolume || !dataVolume.hasSize()) { + addRequired(null); + validations.size = getEmptyDiskSizeValidation(); + } + } + + if (type.requiresPVC()) { + const pvcName = type.getPVCName(volume, dataVolume); + addRequired(pvcName); + validations.pvc = validatePVCName(pvcName, usedPVCNames); + } + } + + return { + validations, + hasAllRequiredFilled: !!hasAllRequiredFilled, + isValid: !!hasAllRequiredFilled && !Object.keys(validations).find((key) => validations[key]), + }; +}; + +export type UIDiskValidation = { + validations: { + name?: ValidationObject; + size?: ValidationObject; + url?: ValidationObject; + container?: ValidationObject; + pvc?: ValidationObject; + }; + isValid: boolean; + hasAllRequiredFilled: boolean; +}; diff --git a/frontend/packages/kubevirt-plugin/src/utils/validations/vm/nic.ts b/frontend/packages/kubevirt-plugin/src/utils/validations/vm/nic.ts index 70e6241eda4..adacefab211 100644 --- a/frontend/packages/kubevirt-plugin/src/utils/validations/vm/nic.ts +++ b/frontend/packages/kubevirt-plugin/src/utils/validations/vm/nic.ts @@ -1,9 +1,10 @@ import { getValidationObject, validateDNS1123SubdomainValue } from '../common'; import { makeSentence } from '../../grammar'; -import { MAC_ADDRESS_INVALID_ERROR, NIC_NAME_EXISTS } from '../strings'; +import { MAC_ADDRESS_INVALID_ERROR, NETWORK_MULTUS_NAME_EXISTS, NIC_NAME_EXISTS } from '../strings'; import { ValidationObject } from '../types'; import { NetworkInterfaceWrapper } from '../../../k8s/wrapper/vm/network-interface-wrapper'; import { NetworkWrapper } from '../../../k8s/wrapper/vm/network-wrapper'; +import { NetworkType } from '../../../constants/vm'; import { isValidMAC } from './validations'; export const validateNicName = ( @@ -20,19 +21,41 @@ export const validateNicName = ( return validation; }; +export const validateNetwork = ( + network: NetworkWrapper, + usedMultusNetworkNames: Set, +): ValidationObject => { + if ( + network.getType() === NetworkType.MULTUS && + usedMultusNetworkNames && + usedMultusNetworkNames.has(network.getMultusNetworkName()) + ) { + return getValidationObject(NETWORK_MULTUS_NAME_EXISTS); + } + + return null; +}; + export const validateMACAddress = (mac: string): ValidationObject => { - const isValid = mac === '' || (mac && isValidMAC(mac)); + const isValid = !mac || isValidMAC(mac); return isValid ? null : getValidationObject(makeSentence(MAC_ADDRESS_INVALID_ERROR)); }; export const validateNIC = ( interfaceWrapper: NetworkInterfaceWrapper, network: NetworkWrapper, - { usedInterfacesNames }: { usedInterfacesNames?: Set }, + { + usedInterfacesNames, + usedMultusNetworkNames, + }: { + usedInterfacesNames?: Set; + usedMultusNetworkNames?: Set; + }, ): UINetworkInterfaceValidation => { const validations = { name: validateNicName(interfaceWrapper && interfaceWrapper.getName(), usedInterfacesNames), macAddress: validateMACAddress(interfaceWrapper && interfaceWrapper.getMACAddress()), + network: validateNetwork(network, usedMultusNetworkNames), }; const hasAllRequiredFilled = @@ -55,6 +78,7 @@ export type UINetworkInterfaceValidation = { validations: { name?: ValidationObject; macAddress?: ValidationObject; + network?: ValidationObject; }; isValid: boolean; hasAllRequiredFilled: boolean;