Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions locales/en/plugin__forklift-console-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
"{{vmCount}} VM{{isPlural}} migration paused until cutover scheduled": "{{vmCount}} VM{{isPlural}} migration paused until cutover scheduled",
"{children}": "{children}",
"<0>{isOvaProvider\n ? ovaMigrationMessage\n : t('Start the {{migrationType}} migration for plan', { migrationType })} <2>{name}</2>?</0>{isOvaProvider ? null : <StackItem>{migrationMessage}</StackItem>}": "<0>{isOvaProvider\n ? ovaMigrationMessage\n : t('Start the {{migrationType}} migration for plan', { migrationType })} <2>{name}</2>?</0>{isOvaProvider ? null : <StackItem>{migrationMessage}</StackItem>}",
"<0><0>{description}</0><1>Add labels to specify qualifying nodes. For each nodes label, set <2>key, value</2> pair(s). For example: key set to <5>beta.kubernetes.io/os</5> and value set to <7>linux</7></1></0>": "<0><0>{description}</0><1>Add labels to specify qualifying nodes. For each nodes label, set <2>key, value</2> pair(s). For example: key set to <5>beta.kubernetes.io/os</5> and value set to <7>linux</7></1></0>",
"<0><0>{description}</0><1>Enter <1>key=value</1> pair(s). For example: project=myProject</1></0>": "<0><0>{description}</0><1>Enter <1>key=value</1> pair(s). For example: project=myProject</1></0>",
"<0><0>{TARGET_LABELS_DETAILS_ITEM_DESCRIPTION}</0><1>Enter <1>key=value</1> pair(s). For example: project=myProject</1></0>": "<0><0>{TARGET_LABELS_DETAILS_ITEM_DESCRIPTION}</0><1>Enter <1>key=value</1> pair(s). For example: project=myProject</1></0>",
"<0><0>{TARGET_NODE_SELECTOR_DETAILS_ITEM_DESCRIPTION}</0><1>Add labels to specify qualifying nodes. For each nodes label, set <2>key, value</2> pair(s). For example: key set to <5>beta.kubernetes.io/os</5> and value set to <7>linux</7></1></0>": "<0><0>{TARGET_NODE_SELECTOR_DETAILS_ITEM_DESCRIPTION}</0><1>Add labels to specify qualifying nodes. For each nodes label, set <2>key, value</2> pair(s). For example: key set to <5>beta.kubernetes.io/os</5> and value set to <7>linux</7></1></0>",
"<0><0>A root device is the storage device or partition that contains the root filesystem. For example, naming a root device \"/dev/sda2\" would mean to use the second partition on the first hard drive.</0><1>If you do not provide a root device, the first root device will be used. If the named root device does not exist or is not detected as a root device, the migration will fail.</1><2><0>Learn more</0></2></0>": "<0><0>A root device is the storage device or partition that contains the root filesystem. For example, naming a root device \"/dev/sda2\" would mean to use the second partition on the first hard drive.</0><1>If you do not provide a root device, the first root device will be used. If the named root device does not exist or is not detected as a root device, the migration will fail.</1><2><0>Learn more</0></2></0>",
Expand Down Expand Up @@ -276,6 +278,9 @@
"Controller transfer network": "Controller transfer network",
"Controls the interval at which a new snapshot is requested prior to initiating a warm migration. The default value is 60 minutes.": "Controls the interval at which a new snapshot is requested prior to initiating a warm migration. The default value is 60 minutes.",
"Conversion migration": "Conversion migration",
"Convertor pod affinity rules": "Convertor pod affinity rules",
"Convertor pod labels": "Convertor pod labels",
"Convertor pod node selector": "Convertor pod node selector",
"Copied": "Copied",
"Copy to clipboard": "Copy to clipboard",
"Core concepts": "Core concepts",
Expand Down Expand Up @@ -368,6 +373,9 @@
"Edit affinity rule": "Edit affinity rule",
"Edit Ansible hook configuration for your migration plan. Hooks are applied to all virtual machines in the plan.": "Edit Ansible hook configuration for your migration plan. Hooks are applied to all virtual machines in the plan.",
"Edit appliance management": "Edit appliance management",
"Edit convertor pod affinity rules": "Edit convertor pod affinity rules",
"Edit convertor pod labels": "Edit convertor pod labels",
"Edit convertor pod node selector": "Edit convertor pod node selector",
"Edit cutover": "Edit cutover",
"Edit default transfer network": "Edit default transfer network",
"Edit description": "Edit description",
Expand Down Expand Up @@ -1079,9 +1087,12 @@
"Source provider:": "Source provider:",
"Source storage": "Source storage",
"Specify a list of passphrases for the Linux Unified Key Setup (LUKS)-encrypted devices for the VMs that you want to migrate.": "Specify a list of passphrases for the Linux Unified Key Setup (LUKS)-encrypted devices for the VMs that you want to migrate.",
"Specify affinity rules for virt-v2v convertor pods during migration. This can apply hard and soft affinity and anti-affinity rules for convertor pods against workloads and nodes - for performance optimization (co-locating with storage) and ensuring network proximity to source infrastructure.": "Specify affinity rules for virt-v2v convertor pods during migration. This can apply hard and soft affinity and anti-affinity rules for convertor pods against workloads and nodes - for performance optimization (co-locating with storage) and ensuring network proximity to source infrastructure.",
"Specify affinity rules that will be applied after migration to all target virtual machines of the migration plan.\n This can apply hard and soft affinity and anti-affinity rules for migrated virtual machines against workloads (of virtual machines and Pods) and against nodes - for performance optimization (co-locating related workloads) and High availability (spread virtual machines across nodes or zones).": "Specify affinity rules that will be applied after migration to all target virtual machines of the migration plan.\n This can apply hard and soft affinity and anti-affinity rules for migrated virtual machines against workloads (of virtual machines and Pods) and against nodes - for performance optimization (co-locating related workloads) and High availability (spread virtual machines across nodes or zones).",
"Specify custom labels that will be applied after migration to all target virtual machines of the migration plan. This can apply organizational or operational labels to migrated virtual machines for further identification and management.": "Specify custom labels that will be applied after migration to all target virtual machines of the migration plan. This can apply organizational or operational labels to migrated virtual machines for further identification and management.",
"Specify custom labels to apply to virt-v2v convertor pods during migration. This can help with organizational labeling, monitoring, or targeting convertor pods with network policies.": "Specify custom labels to apply to virt-v2v convertor pods during migration. This can help with organizational labeling, monitoring, or targeting convertor pods with network policies.",
"Specify node labels that will be applied after migration to all target virtual machines of the migration plan for constraining virtual machines scheduling to specific nodes, based on node labels. This will ensure that the migrated virtual machines will run on nodes with required capabilities (GPU, storage type, CPU architecture).": "Specify node labels that will be applied after migration to all target virtual machines of the migration plan for constraining virtual machines scheduling to specific nodes, based on node labels. This will ensure that the migrated virtual machines will run on nodes with required capabilities (GPU, storage type, CPU architecture).",
"Specify node selector labels for virt-v2v convertor pods to constrain their scheduling to specific nodes. This ensures convertor pods run on nodes with required capabilities such as network proximity to source infrastructure or specific storage access.": "Specify node selector labels for virt-v2v convertor pods to constrain their scheduling to specific nodes. This ensures convertor pods run on nodes with required capabilities such as network proximity to source infrastructure or specific storage access.",
"Specify the type of source provider. Allowed values are ova, ovirt, vsphere,\n openshift, and openstack. This label is needed to verify the credentials are correct when the remote system is accessible and, for RHV, to retrieve the Manager CA certificate when\n a third-party certificate is specified.": "Specify the type of source provider. Allowed values are ova, ovirt, vsphere,\n openshift, and openstack. This label is needed to verify the credentials are correct when the remote system is accessible and, for RHV, to retrieve the Manager CA certificate when\n a third-party certificate is specified.",
"Staging": "Staging",
"Start": "Start",
Expand Down
8 changes: 2 additions & 6 deletions src/components/AffinityModal/AffinityModal.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { useState } from 'react';

import ModalForm from '@components/ModalForm/ModalForm';
import type {
K8sIoApiCoreV1Affinity,
K8sResourceCommon,
V1beta1PlanSpecTargetAffinity,
} from '@forklift-ui/types';
import type { K8sIoApiCoreV1Affinity, K8sResourceCommon } from '@forklift-ui/types';
import type { ModalComponent } from '@openshift-console/dynamic-plugin-sdk/lib/app/modal-support/ModalProvider';
import { ModalVariant } from '@patternfly/react-core';
import { isEmpty } from '@utils/helpers';
Expand All @@ -23,7 +19,7 @@ import AffinityList from './AffinityList';
export type AffinityModalProps = {
title?: string;
onConfirm: (updatedAffinity: K8sIoApiCoreV1Affinity) => Promise<K8sResourceCommon>;
initialAffinity: V1beta1PlanSpecTargetAffinity | undefined;
initialAffinity: K8sIoApiCoreV1Affinity | undefined;
};

const AffinityModal: ModalComponent<AffinityModalProps> = ({
Expand Down
8 changes: 4 additions & 4 deletions src/components/AffinityModal/utils/affinityToRowsData.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type {
K8sIoApiCoreV1Affinity,
K8sIoApiCoreV1NodeAffinity,
K8sIoApiCoreV1PodAffinity,
K8sIoApiCoreV1PodAntiAffinity,
K8sIoApimachineryPkgApisMetaV1LabelSelectorRequirement,
V1beta1PlanSpecTargetAffinity,
V1beta1PlanSpecTargetAffinityNodeAffinity,
} from '@forklift-ui/types';

import { AffinityCondition, type AffinityRowData, AffinityType } from './types';
Expand All @@ -13,7 +13,7 @@ const setIDsToEntity = (
) => entity?.map((elm, index) => ({ ...elm, id: index }));

const getNodeAffinityRows = (
nodeAffinity: V1beta1PlanSpecTargetAffinityNodeAffinity | undefined,
nodeAffinity: K8sIoApiCoreV1NodeAffinity | undefined,
): AffinityRowData[] => {
const requiredTerms =
nodeAffinity?.requiredDuringSchedulingIgnoredDuringExecution?.nodeSelectorTerms ?? [];
Expand Down Expand Up @@ -68,7 +68,7 @@ const getPodLikeAffinityRows = (
return [...required, ...preferred] as AffinityRowData[];
};

export const affinityToRowsData = (affinity: V1beta1PlanSpecTargetAffinity): AffinityRowData[] => [
export const affinityToRowsData = (affinity: K8sIoApiCoreV1Affinity): AffinityRowData[] => [
...getNodeAffinityRows(affinity?.nodeAffinity),
...getPodLikeAffinityRows(affinity?.podAffinity),
...getPodLikeAffinityRows(affinity?.podAntiAffinity, true),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { FC } from 'react';

import type { V1beta1PlanSpecTargetAffinity } from '@forklift-ui/types/dist/generated/forklift/models/V1beta1PlanSpecTargetAffinity';
import type { K8sIoApiCoreV1Affinity } from '@forklift-ui/types';
import { Label } from '@patternfly/react-core';
import { useForkliftTranslation } from '@utils/i18n';

import { getAffinityRules } from './utils/getAffinityRules';

type AffinityViewDetailsItemContentProps = {
affinity: V1beta1PlanSpecTargetAffinity | undefined;
affinity: K8sIoApiCoreV1Affinity | undefined;
};

const AffinityViewDetailsItemContent: FC<AffinityViewDetailsItemContentProps> = ({ affinity }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type {
K8sIoApiCoreV1Affinity,
K8sIoApiCoreV1NodeAffinity,
K8sIoApiCoreV1NodeSelectorTerm,
K8sIoApiCoreV1PodAffinity,
K8sIoApiCoreV1PodAffinityTerm,
K8sIoApiCoreV1PodAntiAffinity,
K8sIoApiCoreV1PreferredSchedulingTerm,
K8sIoApiCoreV1WeightedPodAffinityTerm,
V1beta1PlanSpecTargetAffinity,
V1beta1PlanSpecTargetAffinityNodeAffinity,
V1beta1PlanSpecTargetAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecution,
V1beta1PlanSpecTargetAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecutionNodeSelectorTerms,
} from '@forklift-ui/types';

enum AffinityCondition {
Expand All @@ -15,11 +15,8 @@ enum AffinityCondition {
}

const getNodeAffinity = (
nodeAffinity: V1beta1PlanSpecTargetAffinityNodeAffinity | undefined,
): (
| V1beta1PlanSpecTargetAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecution
| V1beta1PlanSpecTargetAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecutionNodeSelectorTerms
)[] => {
nodeAffinity: K8sIoApiCoreV1NodeAffinity | undefined,
): (K8sIoApiCoreV1PreferredSchedulingTerm | K8sIoApiCoreV1NodeSelectorTerm)[] => {
return [
...(nodeAffinity?.[AffinityCondition.preferred] ?? []),
...(nodeAffinity?.[AffinityCondition.required]?.nodeSelectorTerms ?? []),
Expand All @@ -36,11 +33,11 @@ const getPodAffinity = (
};

export const getAffinityRules = (
affinity: V1beta1PlanSpecTargetAffinity | undefined,
affinity: K8sIoApiCoreV1Affinity | undefined,
): (
| V1beta1PlanSpecTargetAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecution
| K8sIoApiCoreV1NodeSelectorTerm
| K8sIoApiCoreV1PodAffinityTerm
| V1beta1PlanSpecTargetAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecutionNodeSelectorTerms
| K8sIoApiCoreV1PreferredSchedulingTerm
| K8sIoApiCoreV1WeightedPodAffinityTerm
)[] => {
const nodeAffinity = getNodeAffinity(affinity?.nodeAffinity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { useFeatureFlags } from '@utils/hooks/useFeatureFlags';

import usePlanSourceProvider from '../../../../hooks/usePlanSourceProvider';

import ConvertorAffinityDetailsItem from './components/ConvertorAffinity/ConvertorAffinityDetailsItem';
import ConvertorLabelsDetailsItem from './components/ConvertorLabels/ConvertorLabelsDetailsItem';
import ConvertorNodeSelectorDetailsItem from './components/ConvertorNodeSelector/ConvertorNodeSelectorDetailsItem';
import GuestConversionDetailsItem from './components/GuestConversion/GuestConversionDetailsItem';
import NetworkNameTemplateDetailsItem from './components/NetworkNameTemplate/NetworkNameTemplateDetailsItem';
import SharedDisksDetailsItem from './components/PlanMigrateSharedDisks/MigrateSharedDisksDetailsItem';
Expand Down Expand Up @@ -77,6 +80,9 @@ const SettingsSection: FC<SettingsSectionProps> = ({ plan }) => {
<TargetLabelsDetailsItem plan={plan} canPatch={canPatch} />
<TargetNodeSelectorDetailsItem plan={plan} canPatch={canPatch} />
<TargetAffinityDetailsItem plan={plan} canPatch={canPatch} />
<ConvertorLabelsDetailsItem plan={plan} canPatch={canPatch} shouldRender={isVsphere} />
<ConvertorNodeSelectorDetailsItem plan={plan} canPatch={canPatch} shouldRender={isVsphere} />
<ConvertorAffinityDetailsItem plan={plan} canPatch={canPatch} shouldRender={isVsphere} />
</DescriptionList>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { FC } from 'react';
import { isPlanEditable } from 'src/plans/details/components/PlanStatus/utils/utils';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: partially addressed from previous review — @components/* and @utils/* aliases are now used, but isPlanEditable still imports via 'src/plans/...' path. Consistent with the sibling target components though, so more of a future cleanup note.


import AffinityModal, { type AffinityModalProps } from '@components/AffinityModal/AffinityModal';
import AffinityViewDetailsItemContent from '@components/AffinityViewDetailsItemContent/AffinityViewDetailsItemContent';
import { DetailsItem } from '@components/DetailItems/DetailItem';
import type { K8sIoApiCoreV1Affinity, V1beta1Plan } from '@forklift-ui/types';
import { useModal } from '@openshift-console/dynamic-plugin-sdk';
import { useForkliftTranslation } from '@utils/i18n';
import { DOC_MAIN_HELP_LINK } from '@utils/links';

import type { EditableDetailsItemProps } from '../../../utils/types';
import { patchPlanSpec } from '../../utils/patchPlanSpec';

const ConvertorAffinityDetailsItem: FC<EditableDetailsItemProps> = ({
canPatch,
plan,
shouldRender,
}) => {
const { t } = useForkliftTranslation();
const launcher = useModal();

if (!shouldRender) {
return null;
}

const description = t(
'Specify affinity rules for virt-v2v convertor pods during migration. This can apply hard and soft affinity and anti-affinity rules for convertor pods against workloads and nodes - for performance optimization (co-locating with storage) and ensuring network proximity to source infrastructure.',
);

const onConfirm = async (updatedAffinity: K8sIoApiCoreV1Affinity): Promise<V1beta1Plan> =>
patchPlanSpec({
currentValue: plan?.spec?.convertorAffinity,
newValue: updatedAffinity,
path: '/spec/convertorAffinity',
plan,
});

return (
<DetailsItem
testId="convertor-affinity-rules-detail-item"
title={t('Convertor pod affinity rules')}
content={
<AffinityViewDetailsItemContent
affinity={plan?.spec?.convertorAffinity as K8sIoApiCoreV1Affinity}
/>
}
helpContent={description}
crumbs={['spec', 'convertorAffinity']}
moreInfoLink={DOC_MAIN_HELP_LINK}
onEdit={() => {
launcher<AffinityModalProps>(AffinityModal, {
initialAffinity: plan?.spec?.convertorAffinity as K8sIoApiCoreV1Affinity,
onConfirm,
title: t('Edit convertor pod affinity rules'),
});
}}
canEdit={canPatch && isPlanEditable(plan)}
/>
);
};

export default ConvertorAffinityDetailsItem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { FC } from 'react';
import { isPlanEditable } from 'src/plans/details/components/PlanStatus/utils/utils';

import { DetailsItem } from '@components/DetailItems/DetailItem';
import LabelsModal, { type LabelsModalProps } from '@components/LabelsModal/LabelsModal';
import LabelsViewDetailsItemContent from '@components/LabelsViewDetailsItemContent/LabelsViewDetailsItemContent';
import type { V1beta1Plan } from '@forklift-ui/types';
import { useModal } from '@openshift-console/dynamic-plugin-sdk';
import { Stack, StackItem } from '@patternfly/react-core';
import { ForkliftTrans, useForkliftTranslation } from '@utils/i18n';
import { DOC_MAIN_HELP_LINK } from '@utils/links';

import type { EditableDetailsItemProps } from '../../../utils/types';
import { patchPlanSpec } from '../../utils/patchPlanSpec';

const ConvertorLabelsDetailsItem: FC<EditableDetailsItemProps> = ({
canPatch,
plan,
shouldRender,
}) => {
const { t } = useForkliftTranslation();
const launcher = useModal();

if (!shouldRender) {
return null;
}

const description = t(
'Specify custom labels to apply to virt-v2v convertor pods during migration. This can help with organizational labeling, monitoring, or targeting convertor pods with network policies.',
);

const onConfirm = async (newLabels: Record<string, string | null>): Promise<V1beta1Plan> =>
patchPlanSpec({
currentValue: plan?.spec?.convertorLabels,
newValue: newLabels,
path: '/spec/convertorLabels',
plan,
});

return (
<DetailsItem
testId="convertor-labels-detail-item"
title={t('Convertor pod labels')}
content={<LabelsViewDetailsItemContent labels={plan?.spec?.convertorLabels} />}
helpContent={description}
crumbs={['spec', 'convertorLabels']}
moreInfoLink={DOC_MAIN_HELP_LINK}
onEdit={() => {
launcher<LabelsModalProps>(LabelsModal, {
description: (
<ForkliftTrans>
<Stack hasGutter>
<StackItem>{description}</StackItem>
<StackItem>
Enter <strong>key=value</strong> pair(s). For example: project=myProject
</StackItem>
</Stack>
</ForkliftTrans>
),
initialLabels: plan?.spec?.convertorLabels,
onConfirm,
title: t('Edit convertor pod labels'),
});
}}
canEdit={canPatch && isPlanEditable(plan)}
/>
);
};

export default ConvertorLabelsDetailsItem;
Loading