Skip to content

Commit a6161fd

Browse files
authored
Resolves: MTV-3380 | Allow setting converter pod label, node selectors and affinities (kubev2v#2260)
* Resolves: MTV-3380 | Allow setting converter pod label, node selectors and affinities Signed-off-by: Aviv Turgeman <aturgema@redhat.com> * Resolves: MTV-3380 | fix comment from jpuzz0 Signed-off-by: Aviv Turgeman <aturgema@redhat.com> --------- Signed-off-by: Aviv Turgeman <aturgema@redhat.com>
1 parent 70e0c52 commit a6161fd

File tree

16 files changed

+604
-183
lines changed

16 files changed

+604
-183
lines changed

locales/en/plugin__forklift-console-plugin.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
"{{vmCount}} VM{{isPlural}} migration paused until cutover scheduled": "{{vmCount}} VM{{isPlural}} migration paused until cutover scheduled",
4343
"{children}": "{children}",
4444
"<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>}",
45+
"<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>",
46+
"<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>",
4547
"<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>",
4648
"<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>",
4749
"<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>",
@@ -278,6 +280,9 @@
278280
"Controller transfer network": "Controller transfer network",
279281
"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.",
280282
"Conversion migration": "Conversion migration",
283+
"Convertor pod affinity rules": "Convertor pod affinity rules",
284+
"Convertor pod labels": "Convertor pod labels",
285+
"Convertor pod node selector": "Convertor pod node selector",
281286
"Copied": "Copied",
282287
"Copy to clipboard": "Copy to clipboard",
283288
"Core concepts": "Core concepts",
@@ -369,6 +374,9 @@
369374
"Edit affinity rule": "Edit affinity rule",
370375
"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.",
371376
"Edit appliance management": "Edit appliance management",
377+
"Edit convertor pod affinity rules": "Edit convertor pod affinity rules",
378+
"Edit convertor pod labels": "Edit convertor pod labels",
379+
"Edit convertor pod node selector": "Edit convertor pod node selector",
372380
"Edit cutover": "Edit cutover",
373381
"Edit default transfer network": "Edit default transfer network",
374382
"Edit description": "Edit description",
@@ -1080,9 +1088,12 @@
10801088
"Source provider:": "Source provider:",
10811089
"Source storage": "Source storage",
10821090
"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.",
1091+
"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.",
10831092
"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).",
10841093
"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.",
1094+
"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.",
10851095
"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).",
1096+
"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.",
10861097
"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.",
10871098
"Staging": "Staging",
10881099
"Start": "Start",

src/components/AffinityModal/AffinityModal.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import { useState } from 'react';
22

33
import ModalForm from '@components/ModalForm/ModalForm';
4-
import type {
5-
K8sIoApiCoreV1Affinity,
6-
K8sResourceCommon,
7-
V1beta1PlanSpecTargetAffinity,
8-
} from '@forklift-ui/types';
4+
import type { K8sIoApiCoreV1Affinity, K8sResourceCommon } from '@forklift-ui/types';
95
import type { ModalComponent } from '@openshift-console/dynamic-plugin-sdk/lib/app/modal-support/ModalProvider';
106
import { ModalVariant } from '@patternfly/react-core';
117
import { isEmpty } from '@utils/helpers';
@@ -23,7 +19,7 @@ import AffinityList from './AffinityList';
2319
export type AffinityModalProps = {
2420
title?: string;
2521
onConfirm: (updatedAffinity: K8sIoApiCoreV1Affinity) => Promise<K8sResourceCommon>;
26-
initialAffinity: V1beta1PlanSpecTargetAffinity | undefined;
22+
initialAffinity: K8sIoApiCoreV1Affinity | undefined;
2723
};
2824

2925
const AffinityModal: ModalComponent<AffinityModalProps> = ({

src/components/AffinityModal/utils/affinityToRowsData.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import type {
2+
K8sIoApiCoreV1Affinity,
3+
K8sIoApiCoreV1NodeAffinity,
24
K8sIoApiCoreV1PodAffinity,
35
K8sIoApiCoreV1PodAntiAffinity,
46
K8sIoApimachineryPkgApisMetaV1LabelSelectorRequirement,
5-
V1beta1PlanSpecTargetAffinity,
6-
V1beta1PlanSpecTargetAffinityNodeAffinity,
77
} from '@forklift-ui/types';
88

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

1515
const getNodeAffinityRows = (
16-
nodeAffinity: V1beta1PlanSpecTargetAffinityNodeAffinity | undefined,
16+
nodeAffinity: K8sIoApiCoreV1NodeAffinity | undefined,
1717
): AffinityRowData[] => {
1818
const requiredTerms =
1919
nodeAffinity?.requiredDuringSchedulingIgnoredDuringExecution?.nodeSelectorTerms ?? [];
@@ -68,7 +68,7 @@ const getPodLikeAffinityRows = (
6868
return [...required, ...preferred] as AffinityRowData[];
6969
};
7070

71-
export const affinityToRowsData = (affinity: V1beta1PlanSpecTargetAffinity): AffinityRowData[] => [
71+
export const affinityToRowsData = (affinity: K8sIoApiCoreV1Affinity): AffinityRowData[] => [
7272
...getNodeAffinityRows(affinity?.nodeAffinity),
7373
...getPodLikeAffinityRows(affinity?.podAffinity),
7474
...getPodLikeAffinityRows(affinity?.podAntiAffinity, true),

src/components/AffinityViewDetailsItemContent/AffinityViewDetailsItemContent.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import type { FC } from 'react';
22

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

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

99
type AffinityViewDetailsItemContentProps = {
10-
affinity: V1beta1PlanSpecTargetAffinity | undefined;
10+
affinity: K8sIoApiCoreV1Affinity | undefined;
1111
};
1212

1313
const AffinityViewDetailsItemContent: FC<AffinityViewDetailsItemContentProps> = ({ affinity }) => {

src/components/AffinityViewDetailsItemContent/utils/getAffinityRules.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import type {
2+
K8sIoApiCoreV1Affinity,
3+
K8sIoApiCoreV1NodeAffinity,
4+
K8sIoApiCoreV1NodeSelectorTerm,
25
K8sIoApiCoreV1PodAffinity,
36
K8sIoApiCoreV1PodAffinityTerm,
47
K8sIoApiCoreV1PodAntiAffinity,
8+
K8sIoApiCoreV1PreferredSchedulingTerm,
59
K8sIoApiCoreV1WeightedPodAffinityTerm,
6-
V1beta1PlanSpecTargetAffinity,
7-
V1beta1PlanSpecTargetAffinityNodeAffinity,
8-
V1beta1PlanSpecTargetAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecution,
9-
V1beta1PlanSpecTargetAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecutionNodeSelectorTerms,
1010
} from '@forklift-ui/types';
1111

1212
enum AffinityCondition {
@@ -15,11 +15,8 @@ enum AffinityCondition {
1515
}
1616

1717
const getNodeAffinity = (
18-
nodeAffinity: V1beta1PlanSpecTargetAffinityNodeAffinity | undefined,
19-
): (
20-
| V1beta1PlanSpecTargetAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecution
21-
| V1beta1PlanSpecTargetAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecutionNodeSelectorTerms
22-
)[] => {
18+
nodeAffinity: K8sIoApiCoreV1NodeAffinity | undefined,
19+
): (K8sIoApiCoreV1PreferredSchedulingTerm | K8sIoApiCoreV1NodeSelectorTerm)[] => {
2320
return [
2421
...(nodeAffinity?.[AffinityCondition.preferred] ?? []),
2522
...(nodeAffinity?.[AffinityCondition.required]?.nodeSelectorTerms ?? []),
@@ -36,11 +33,11 @@ const getPodAffinity = (
3633
};
3734

3835
export const getAffinityRules = (
39-
affinity: V1beta1PlanSpecTargetAffinity | undefined,
36+
affinity: K8sIoApiCoreV1Affinity | undefined,
4037
): (
41-
| V1beta1PlanSpecTargetAffinityNodeAffinityPreferredDuringSchedulingIgnoredDuringExecution
38+
| K8sIoApiCoreV1NodeSelectorTerm
4239
| K8sIoApiCoreV1PodAffinityTerm
43-
| V1beta1PlanSpecTargetAffinityNodeAffinityRequiredDuringSchedulingIgnoredDuringExecutionNodeSelectorTerms
40+
| K8sIoApiCoreV1PreferredSchedulingTerm
4441
| K8sIoApiCoreV1WeightedPodAffinityTerm
4542
)[] => {
4643
const nodeAffinity = getNodeAffinity(affinity?.nodeAffinity);

src/plans/details/tabs/Details/components/SettingsSection/SettingsSection.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import { useFeatureFlags } from '@utils/hooks/useFeatureFlags';
1212

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

15+
import ConvertorAffinityDetailsItem from './components/ConvertorAffinity/ConvertorAffinityDetailsItem';
16+
import ConvertorLabelsDetailsItem from './components/ConvertorLabels/ConvertorLabelsDetailsItem';
17+
import ConvertorNodeSelectorDetailsItem from './components/ConvertorNodeSelector/ConvertorNodeSelectorDetailsItem';
1518
import GuestConversionDetailsItem from './components/GuestConversion/GuestConversionDetailsItem';
1619
import NetworkNameTemplateDetailsItem from './components/NetworkNameTemplate/NetworkNameTemplateDetailsItem';
1720
import SharedDisksDetailsItem from './components/PlanMigrateSharedDisks/MigrateSharedDisksDetailsItem';
@@ -77,6 +80,9 @@ const SettingsSection: FC<SettingsSectionProps> = ({ plan }) => {
7780
<TargetLabelsDetailsItem plan={plan} canPatch={canPatch} />
7881
<TargetNodeSelectorDetailsItem plan={plan} canPatch={canPatch} />
7982
<TargetAffinityDetailsItem plan={plan} canPatch={canPatch} />
83+
<ConvertorLabelsDetailsItem plan={plan} canPatch={canPatch} shouldRender={isVsphere} />
84+
<ConvertorNodeSelectorDetailsItem plan={plan} canPatch={canPatch} shouldRender={isVsphere} />
85+
<ConvertorAffinityDetailsItem plan={plan} canPatch={canPatch} shouldRender={isVsphere} />
8086
</DescriptionList>
8187
);
8288
};
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { FC } from 'react';
2+
import { isPlanEditable } from 'src/plans/details/components/PlanStatus/utils/utils';
3+
4+
import AffinityModal, { type AffinityModalProps } from '@components/AffinityModal/AffinityModal';
5+
import AffinityViewDetailsItemContent from '@components/AffinityViewDetailsItemContent/AffinityViewDetailsItemContent';
6+
import { DetailsItem } from '@components/DetailItems/DetailItem';
7+
import type { K8sIoApiCoreV1Affinity, V1beta1Plan } from '@forklift-ui/types';
8+
import { useModal } from '@openshift-console/dynamic-plugin-sdk';
9+
import { useForkliftTranslation } from '@utils/i18n';
10+
import { DOC_MAIN_HELP_LINK } from '@utils/links';
11+
12+
import type { EditableDetailsItemProps } from '../../../utils/types';
13+
import { patchPlanSpec } from '../../utils/patchPlanSpec';
14+
15+
const ConvertorAffinityDetailsItem: FC<EditableDetailsItemProps> = ({
16+
canPatch,
17+
plan,
18+
shouldRender,
19+
}) => {
20+
const { t } = useForkliftTranslation();
21+
const launcher = useModal();
22+
23+
if (!shouldRender) {
24+
return null;
25+
}
26+
27+
const description = t(
28+
'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.',
29+
);
30+
31+
const onConfirm = async (updatedAffinity: K8sIoApiCoreV1Affinity): Promise<V1beta1Plan> =>
32+
patchPlanSpec({
33+
currentValue: plan?.spec?.convertorAffinity,
34+
newValue: updatedAffinity,
35+
path: '/spec/convertorAffinity',
36+
plan,
37+
});
38+
39+
const initialAffinity = plan?.spec?.convertorAffinity as K8sIoApiCoreV1Affinity;
40+
41+
return (
42+
<DetailsItem
43+
testId="convertor-affinity-rules-detail-item"
44+
title={t('Convertor pod affinity rules')}
45+
content={<AffinityViewDetailsItemContent affinity={initialAffinity} />}
46+
helpContent={description}
47+
crumbs={['spec', 'convertorAffinity']}
48+
moreInfoLink={DOC_MAIN_HELP_LINK}
49+
onEdit={() => {
50+
launcher<AffinityModalProps>(AffinityModal, {
51+
initialAffinity,
52+
onConfirm,
53+
title: t('Edit convertor pod affinity rules'),
54+
});
55+
}}
56+
canEdit={canPatch && isPlanEditable(plan)}
57+
/>
58+
);
59+
};
60+
61+
export default ConvertorAffinityDetailsItem;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import type { FC } from 'react';
2+
import { isPlanEditable } from 'src/plans/details/components/PlanStatus/utils/utils';
3+
4+
import { DetailsItem } from '@components/DetailItems/DetailItem';
5+
import LabelsModal, { type LabelsModalProps } from '@components/LabelsModal/LabelsModal';
6+
import LabelsViewDetailsItemContent from '@components/LabelsViewDetailsItemContent/LabelsViewDetailsItemContent';
7+
import type { V1beta1Plan } from '@forklift-ui/types';
8+
import { useModal } from '@openshift-console/dynamic-plugin-sdk';
9+
import { Stack, StackItem } from '@patternfly/react-core';
10+
import { ForkliftTrans, useForkliftTranslation } from '@utils/i18n';
11+
import { DOC_MAIN_HELP_LINK } from '@utils/links';
12+
13+
import type { EditableDetailsItemProps } from '../../../utils/types';
14+
import { patchPlanSpec } from '../../utils/patchPlanSpec';
15+
16+
const ConvertorLabelsDetailsItem: FC<EditableDetailsItemProps> = ({
17+
canPatch,
18+
plan,
19+
shouldRender,
20+
}) => {
21+
const { t } = useForkliftTranslation();
22+
const launcher = useModal();
23+
24+
if (!shouldRender) {
25+
return null;
26+
}
27+
28+
const description = t(
29+
'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.',
30+
);
31+
32+
const onConfirm = async (newLabels: Record<string, string | null>): Promise<V1beta1Plan> =>
33+
patchPlanSpec({
34+
currentValue: plan?.spec?.convertorLabels,
35+
newValue: newLabels,
36+
path: '/spec/convertorLabels',
37+
plan,
38+
});
39+
40+
return (
41+
<DetailsItem
42+
testId="convertor-labels-detail-item"
43+
title={t('Convertor pod labels')}
44+
content={<LabelsViewDetailsItemContent labels={plan?.spec?.convertorLabels} />}
45+
helpContent={description}
46+
crumbs={['spec', 'convertorLabels']}
47+
moreInfoLink={DOC_MAIN_HELP_LINK}
48+
onEdit={() => {
49+
launcher<LabelsModalProps>(LabelsModal, {
50+
description: (
51+
<ForkliftTrans>
52+
<Stack hasGutter>
53+
<StackItem>{description}</StackItem>
54+
<StackItem>
55+
Enter <strong>key=value</strong> pair(s). For example: project=myProject
56+
</StackItem>
57+
</Stack>
58+
</ForkliftTrans>
59+
),
60+
initialLabels: plan?.spec?.convertorLabels,
61+
onConfirm,
62+
title: t('Edit convertor pod labels'),
63+
});
64+
}}
65+
canEdit={canPatch && isPlanEditable(plan)}
66+
/>
67+
);
68+
};
69+
70+
export default ConvertorLabelsDetailsItem;

0 commit comments

Comments
 (0)