Skip to content

Commit bec9899

Browse files
Merge pull request #13506 from rawagner/node_maintenance_new
OCPBUGS-25788: Add Node Maintenance menu actions to Node list/details
2 parents a0f652a + b72bbe3 commit bec9899

31 files changed

Lines changed: 194 additions & 1486 deletions

frontend/packages/console-app/console-extensions.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1820,5 +1820,17 @@
18201820
}
18211821
}
18221822
}
1823+
},
1824+
{
1825+
"type": "console.action/resource-provider",
1826+
"properties": {
1827+
"model": {
1828+
"version": "v1",
1829+
"kind": "Node"
1830+
},
1831+
"provider": {
1832+
"$codeRef": "nodeActions.useNodeActions"
1833+
}
1834+
}
18231835
}
18241836
]

frontend/packages/console-app/locales/en/console-app.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,9 +359,9 @@
359359
"Add allowed source": "Add allowed source",
360360
"Add allowed destination": "Add allowed destination",
361361
"Remove peer": "Remove peer",
362-
"This action cannot be undone. Deleting a node will instruct Kubernetes that the node is down or unrecoverable and delete all pods scheduled to that node. If the node is still running but unresponsive and the node is deleted, stateful workloads and persistent volumes may suffer corruption or data loss. Only delete a node that you have confirmed is completely stopped and cannot be restored.": "This action cannot be undone. Deleting a node will instruct Kubernetes that the node is down or unrecoverable and delete all pods scheduled to that node. If the node is still running but unresponsive and the node is deleted, stateful workloads and persistent volumes may suffer corruption or data loss. Only delete a node that you have confirmed is completely stopped and cannot be restored.",
363-
"Delete node": "Delete node",
362+
"Mark as schedulable": "Mark as schedulable",
364363
"Mark as unschedulable": "Mark as unschedulable",
364+
"This action cannot be undone. Deleting a node will instruct Kubernetes that the node is down or unrecoverable and delete all pods scheduled to that node. If the node is still running but unresponsive and the node is deleted, stateful workloads and persistent volumes may suffer corruption or data loss. Only delete a node that you have confirmed is completely stopped and cannot be restored.": "This action cannot be undone. Deleting a node will instruct Kubernetes that the node is down or unrecoverable and delete all pods scheduled to that node. If the node is still running but unresponsive and the node is deleted, stateful workloads and persistent volumes may suffer corruption or data loss. Only delete a node that you have confirmed is completely stopped and cannot be restored.",
365365
"Unschedulable nodes won't accept new pods. This is useful for scheduling maintenance or preparing to decommission a node.": "Unschedulable nodes won't accept new pods. This is useful for scheduling maintenance or preparing to decommission a node.",
366366
"Mark unschedulable": "Mark unschedulable",
367367
"View events": "View events",
@@ -439,7 +439,6 @@
439439
"Certificate approval required": "Certificate approval required",
440440
"An error occurred. Please try again": "An error occurred. Please try again",
441441
"No new Pods or workloads will be placed on this Node until it's marked as schedulable.": "No new Pods or workloads will be placed on this Node until it's marked as schedulable.",
442-
"Mark as schedulable": "Mark as schedulable",
443442
"Min available {{minAvailable}} of {{count}} pod_one": "Min available {{minAvailable}} of {{count}} pod",
444443
"Min available {{minAvailable}} of {{count}} pod_other": "Min available {{minAvailable}} of {{count}} pods",
445444
"Max unavailable {{maxUnavailable}} of {{count}} pod_one": "Max unavailable {{maxUnavailable}} of {{count}} pod",

frontend/packages/console-app/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
"dynamicPluginsHealthResource": "src/components/dashboards-page/dynamic-plugins-health-resource",
4848
"storageProvisioners": "src/components/storage/StorageClassProviders",
4949
"storageProvisionerDocs": "src/components/storage/Documentation",
50-
"nodeStatus": "src/components/nodes/status"
50+
"nodeStatus": "src/components/nodes/status",
51+
"nodeActions": "src/components/nodes/menu-actions.tsx"
5152
}
5253
}
5354
}

frontend/packages/console-app/src/components/nodes/NodeDetailsPage.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ import { ResourceEventStream } from '@console/internal/components/events';
33
import { DetailsPage } from '@console/internal/components/factory';
44
import { PodsPage } from '@console/internal/components/pod';
55
import { navFactory, PageComponentProps } from '@console/internal/components/utils';
6-
import { NodeKind } from '@console/internal/module/k8s';
6+
import { K8sModel, NodeKind, referenceForModel } from '@console/internal/module/k8s';
7+
import {
8+
ActionMenu,
9+
ActionMenuVariant,
10+
ActionServiceProvider,
11+
} from '@console/shared/src/components/actions';
712
import { isWindowsNode } from '@console/shared/src/selectors/node';
813
import { nodeStatus } from '../../status/node';
9-
import { menuActions } from './menu-actions';
1014
import NodeDashboard from './node-dashboard/NodeDashboard';
1115
import NodeDetails from './NodeDetails';
1216
import NodeLogs from './NodeLogs';
@@ -44,11 +48,25 @@ const NodeDetailsPage: React.FC<React.ComponentProps<typeof DetailsPage>> = (pro
4448
[],
4549
);
4650

51+
const customActionMenu = (kindObj: K8sModel, obj: NodeKind) => {
52+
const resourceKind = referenceForModel(kindObj);
53+
const context = { [resourceKind]: obj };
54+
return (
55+
<ActionServiceProvider context={context}>
56+
{({ actions, options, loaded }) =>
57+
loaded && (
58+
<ActionMenu actions={actions} options={options} variant={ActionMenuVariant.DROPDOWN} />
59+
)
60+
}
61+
</ActionServiceProvider>
62+
);
63+
};
64+
4765
return (
4866
<DetailsPage
4967
{...props}
5068
getResourceStatus={nodeStatus}
51-
menuActions={menuActions}
69+
customActionMenu={customActionMenu}
5270
pagesFor={pagesFor}
5371
/>
5472
);

frontend/packages/console-app/src/components/nodes/NodesPage.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import { PROMETHEUS_BASE_PATH } from '@console/internal/components/graphs';
2828
import { getPrometheusURL, PrometheusEndpoint } from '@console/internal/components/graphs/helpers';
2929
import {
3030
Kebab,
31-
ResourceKebab,
3231
ResourceLink,
3332
Timestamp,
3433
humanizeBinaryBytes,
@@ -41,6 +40,7 @@ import {
4140
NodeKind,
4241
referenceForModel,
4342
CertificateSigningRequestKind,
43+
referenceFor,
4444
} from '@console/internal/module/k8s';
4545
import {
4646
getName,
@@ -64,10 +64,10 @@ import {
6464
nodeReadiness,
6565
nodeRoles as nodeRolesSort,
6666
sortWithCSRResource,
67+
LazyActionMenu,
6768
} from '@console/shared';
6869
import { nodeStatus } from '../../status';
6970
import { getNodeClientCSRs, isCSRResource } from './csr';
70-
import { menuActions } from './menu-actions';
7171
import NodeUptime from './node-dashboard/NodeUptime';
7272
import NodeRoles from './NodeRoles';
7373
import { NodeStatusWithExtensions } from './NodeStatus';
@@ -270,6 +270,8 @@ const NodesTableRow: React.FC<RowProps<NodeKind, GetNodeStatusExtensions>> = ({
270270
const instanceType = node.metadata.labels?.['beta.kubernetes.io/instance-type'];
271271
const labels = getLabels(node);
272272
const zone = node.metadata.labels?.['topology.kubernetes.io/zone'];
273+
const resourceKind = referenceFor(node);
274+
const context = { [resourceKind]: node };
273275
return (
274276
<>
275277
<TableData
@@ -383,7 +385,7 @@ const NodesTableRow: React.FC<RowProps<NodeKind, GetNodeStatusExtensions>> = ({
383385
<NodeUptime obj={node} />
384386
</TableData>
385387
<TableData className={Kebab.columnClass} activeColumnIDs={activeColumnIDs} id="">
386-
<ResourceKebab actions={menuActions} kind={referenceForModel(NodeModel)} resource={node} />
388+
<LazyActionMenu context={context} />
387389
</TableData>
388390
</>
389391
);
Lines changed: 42 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import * as React from 'react';
22
import { useTranslation } from 'react-i18next';
3+
import { Action } from '@console/dynamic-plugin-sdk';
4+
import { useK8sModel } from '@console/dynamic-plugin-sdk/src/lib-core';
35
import { k8sUpdateResource } from '@console/dynamic-plugin-sdk/src/utils/k8s';
4-
import { deleteModal } from '@console/internal/components/modals/delete-modal';
5-
import { Kebab, KebabAction, asAccessReview } from '@console/internal/components/utils';
6-
import { CertificateSigningRequestModel, NodeModel } from '@console/internal/models';
7-
import { CertificateSigningRequestKind, K8sKind, NodeKind } from '@console/internal/module/k8s';
6+
import { asAccessReview } from '@console/internal/components/utils';
7+
import { CertificateSigningRequestModel } from '@console/internal/models';
8+
import {
9+
CertificateSigningRequestKind,
10+
ExtensionHook,
11+
NodeKind,
12+
referenceFor,
13+
} from '@console/internal/module/k8s';
814
import { isNodeUnschedulable } from '@console/shared';
15+
import { getCommonResourceActions } from '../../actions/creators/common-factory';
916
import { makeNodeSchedulable } from '../../k8s/requests/nodes';
1017
import { createConfigureUnschedulableModal } from './modals';
1118

@@ -37,66 +44,38 @@ export const approveCSR = (csr: CertificateSigningRequestKind) => updateCSR(csr,
3744

3845
export const denyCSR = (csr: CertificateSigningRequestKind) => updateCSR(csr, 'Denied');
3946

40-
export const MarkAsUnschedulable: KebabAction = (kind: K8sKind, obj: NodeKind) => ({
41-
labelKey: 'console-app~Mark as unschedulable',
42-
hidden: isNodeUnschedulable(obj),
43-
callback: () => createConfigureUnschedulableModal({ resource: obj }),
44-
accessReview: {
45-
group: kind.apiGroup,
46-
resource: kind.plural,
47-
name: obj.metadata.name,
48-
namespace: obj.metadata.namespace,
49-
verb: 'patch',
50-
},
51-
});
47+
export const useNodeActions: ExtensionHook<Action[], NodeKind> = (obj) => {
48+
const [kindObj, inFlight] = useK8sModel(referenceFor(obj));
49+
const { t } = useTranslation();
50+
const nodeActions = React.useMemo<Action[]>(() => {
51+
const actions: Action[] = [];
52+
if (isNodeUnschedulable(obj)) {
53+
actions.push({
54+
id: 'mark-as-schedulable',
55+
label: t('console-app~Mark as schedulable'),
56+
cta: () => makeNodeSchedulable(obj),
57+
accessReview: asAccessReview(kindObj, obj, 'patch'),
58+
});
59+
} else {
60+
actions.push({
61+
id: 'mark-as-unschedulable',
62+
label: t('console-app~Mark as unschedulable'),
63+
cta: () => createConfigureUnschedulableModal({ resource: obj }),
64+
accessReview: asAccessReview(kindObj, obj, 'patch'),
65+
});
66+
}
5267

53-
export const MarkAsSchedulable: KebabAction = (
54-
kind: K8sKind,
55-
obj: NodeKind,
56-
resources: {},
57-
{ nodeMaintenance } = { nodeMaintenance: false }, // NOTE: used by node actions in metal3-plugin
58-
) => ({
59-
labelKey: 'console-app~Mark as schedulable',
60-
hidden: !isNodeUnschedulable(obj) || nodeMaintenance,
61-
callback: () => makeNodeSchedulable(obj),
62-
accessReview: {
63-
group: kind.apiGroup,
64-
resource: kind.plural,
65-
name: obj.metadata.name,
66-
namespace: obj.metadata.namespace,
67-
verb: 'patch',
68-
},
69-
});
68+
const message = (
69+
<p>
70+
{t(
71+
'console-app~This action cannot be undone. Deleting a node will instruct Kubernetes that the node is down or unrecoverable and delete all pods scheduled to that node. If the node is still running but unresponsive and the node is deleted, stateful workloads and persistent volumes may suffer corruption or data loss. Only delete a node that you have confirmed is completely stopped and cannot be restored.',
72+
)}
73+
</p>
74+
);
7075

71-
export const Delete: KebabAction = (kindObj: K8sKind, node: NodeKind) => {
72-
const { t } = useTranslation();
73-
const message = (
74-
<p>
75-
{t(
76-
'console-app~This action cannot be undone. Deleting a node will instruct Kubernetes that the node is down or unrecoverable and delete all pods scheduled to that node. If the node is still running but unresponsive and the node is deleted, stateful workloads and persistent volumes may suffer corruption or data loss. Only delete a node that you have confirmed is completely stopped and cannot be restored.',
77-
)}
78-
</p>
79-
);
76+
actions.push(...getCommonResourceActions(kindObj, obj, message));
77+
return actions;
78+
}, [kindObj, obj, t]);
8079

81-
return {
82-
// t('console-app~Delete node')
83-
labelKey: 'console-app~Delete node',
84-
callback: () =>
85-
deleteModal({
86-
kind: kindObj,
87-
resource: node,
88-
message,
89-
}),
90-
accessReview: asAccessReview(NodeModel, node, 'delete'),
91-
};
80+
return [nodeActions, !inFlight, undefined];
9281
};
93-
94-
const { ModifyLabels, ModifyAnnotations, Edit } = Kebab.factory;
95-
export const menuActions = [
96-
MarkAsSchedulable,
97-
MarkAsUnschedulable,
98-
ModifyLabels,
99-
ModifyAnnotations,
100-
Edit,
101-
Delete,
102-
];

frontend/packages/metal3-plugin/console-extensions.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
[
2+
{
3+
"type": "console.flag/hookProvider",
4+
"properties": {
5+
"handler": { "$codeRef": "features.useDetectNodeMaintenance" }
6+
}
7+
},
28
{
39
"type": "console.navigation/resource-ns",
410
"properties": {
@@ -15,5 +21,20 @@
1521
"flags": {
1622
"required": ["BAREMETAL", "METAL3"]
1723
}
24+
},
25+
{
26+
"type": "console.action/resource-provider",
27+
"properties": {
28+
"model": {
29+
"version": "v1",
30+
"kind": "Node"
31+
},
32+
"provider": {
33+
"$codeRef": "nodeActions.useNodeMaintenanceActions"
34+
}
35+
},
36+
"flags": {
37+
"required": ["NODE_MAINTENANCE_ENABLED"]
38+
}
1839
}
1940
]

frontend/packages/metal3-plugin/locales/en/metal3-plugin.json

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -95,47 +95,6 @@
9595
"Maintenance": "Maintenance",
9696
"Unmanaged": "Unmanaged",
9797
"Other": "Other",
98-
"Node Details": "Node Details",
99-
"Node Name": "Node Name",
100-
"External ID": "External ID",
101-
"Node Addresses": "Node Addresses",
102-
"Node Labels": "Node Labels",
103-
"Taints": "Taints",
104-
"Annotations": "Annotations",
105-
"Bare Metal Host": "Bare Metal Host",
106-
"Provider ID": "Provider ID",
107-
"Created": "Created",
108-
"Operating System": "Operating System",
109-
"OS Image": "OS Image",
110-
"Architecture": "Architecture",
111-
"Kernel Version": "Kernel Version",
112-
"Boot ID": "Boot ID",
113-
"Container Runtime": "Container Runtime",
114-
"Kubelet Version": "Kubelet Version",
115-
"Kube-Proxy Version": "Kube-Proxy Version",
116-
"Terminal": "Terminal",
117-
"{{name}} · Details": "{{name}} · Details",
118-
"{{name}} Details": "{{name}} Details",
119-
"Nodes": "Nodes",
120-
"This node has requested to join the cluster. After approving its certificate signing request the node will begin running workloads.": "This node has requested to join the cluster. After approving its certificate signing request the node will begin running workloads.",
121-
"This node has a pending server certificate signing request. Approve the request to enable all networking functionality on this node.": "This node has a pending server certificate signing request. Approve the request to enable all networking functionality on this node.",
122-
"Certificate approval required": "Certificate approval required",
123-
"Request": "Request",
124-
"Deny": "Deny",
125-
"Not available": "Not available",
126-
"Image": "Image",
127-
"Images": "Images",
128-
"Bare Metal Operator not available": "Bare Metal Operator not available",
129-
"The Bare Metal Operator that enables this capability is not available or disabled.": "The Bare Metal Operator that enables this capability is not available or disabled.",
130-
"Power management not available": "Power management not available",
131-
"Power operations cannot be performed on this host until Baseboard Management Controller (BMC) credentials are provided for the underlying host.": "Power operations cannot be performed on this host until Baseboard Management Controller (BMC) credentials are provided for the underlying host.",
132-
"Approve Server CSR": "Approve Server CSR",
133-
"Approve Node Server CSR": "Approve Node Server CSR",
134-
"Are you sure you want to approve server CSR for {{name}}?": "Are you sure you want to approve server CSR for {{name}}?",
135-
"Approve": "Approve",
136-
"Ready": "Ready",
137-
"Not Ready": "Not Ready",
138-
"Approval Required": "Approval Required",
13998
"Starting maintenance": "Starting maintenance",
14099
"Node is entering maintenance. The cluster will automatically rebuild node's data 30 minutes after entering maintenance.": "Node is entering maintenance. The cluster will automatically rebuild node's data 30 minutes after entering maintenance.",
141100
"Maintenance reason:": "Maintenance reason:",

frontend/packages/metal3-plugin/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
"pkijs": "^2.1.89"
1111
},
1212
"consolePlugin": {
13-
"entry": "src/plugin.tsx"
13+
"entry": "src/plugin.tsx",
14+
"exposedModules": {
15+
"nodeActions": "src/components/maintenance/actions.tsx",
16+
"features": "src/features.ts"
17+
}
1418
}
1519
}

frontend/packages/metal3-plugin/src/components/baremetal-hosts/BareMetalHostDetailsPage.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type BareMetalHostDetailsPageProps = {
2222

2323
const BareMetalHostDetailsPage: React.FC<BareMetalHostDetailsPageProps> = (props) => {
2424
const { t } = useTranslation();
25-
const [hasNodeMaintenanceCapability, maintenanceModel] = useMaintenanceCapability();
25+
const [maintenanceModel] = useMaintenanceCapability();
2626
const bmoEnabled = useFlag(BMO_ENABLED_FLAG);
2727
const resources: FirehoseResource[] = [
2828
{
@@ -45,7 +45,7 @@ const BareMetalHostDetailsPage: React.FC<BareMetalHostDetailsPageProps> = (props
4545
},
4646
];
4747

48-
if (hasNodeMaintenanceCapability) {
48+
if (maintenanceModel) {
4949
resources.push({
5050
kind: referenceForModel(maintenanceModel),
5151
namespaced: false,
@@ -93,7 +93,12 @@ const BareMetalHostDetailsPage: React.FC<BareMetalHostDetailsPageProps> = (props
9393
kind={referenceForModel(BareMetalHostModel)}
9494
resources={resources}
9595
menuActions={menuActionsCreator}
96-
customData={{ hasNodeMaintenanceCapability, maintenanceModel, bmoEnabled, t }}
96+
customData={{
97+
hasNodeMaintenanceCapability: !!maintenanceModel,
98+
maintenanceModel,
99+
bmoEnabled,
100+
t,
101+
}}
97102
/>
98103
);
99104
};

0 commit comments

Comments
 (0)