Skip to content

Commit

Permalink
feat(machines): Add 'Power cycle' as a feature-flagged action MAASENG…
Browse files Browse the repository at this point in the history
…-3945 (#5566)

- Added `VITE_APP_DPU_PROVISIONING` feature flag to env (default: `false`)
- Added "Power cycle" to power action menu
- Refactored `getFormComponent` in `MachineActionFormWrapper` to an object of key-value pairs to reduce complexity
- (drive-by) Replaced `for` loop test for valid ports with a handful of explicit test cases in `src/app/utils/isValidPortNumber.test.ts`

Resolves [MAASENG-3945](https://warthogs.atlassian.net/browse/MAASENG-3945)
  • Loading branch information
ndv99 authored Dec 13, 2024
1 parent 847d318 commit 8bd7e81
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 97 deletions.
6 changes: 5 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ VITE_BASENAME="/r"
VITE_APP_BASENAME=${BASENAME}
VITE_APP_VITE_BASENAME=${VITE_BASENAME}
VITE_APP_WEBSOCKET_DEBUG=false
VITE_APP_USABILLA_ID=fd6cf482fbbb
VITE_APP_USABILLA_ID=fd6cf482fbbb

# Feature flags

VITE_APP_DPU_PROVISIONING=false
10 changes: 10 additions & 0 deletions src/app/base/components/NodeActionMenu/NodeActionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ const getTakeActionLinks = (
if (excludeActions.includes(action)) {
return groupLinks;
}

// Only show "Power cycle" if the feature flag is enabled.
// TODO: Remove DPU provisioning feature flag https://warthogs.atlassian.net/browse/MAASENG-4186
if (
action === NodeActions.POWER_CYCLE &&
import.meta.env.VITE_APP_DPU_PROVISIONING !== "true"
) {
return groupLinks;
}

// When nodes are not provided then counts should not be visible.
const count =
nodes?.reduce(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const actionGroups: ActionGroup[] = [
actions: [
NodeActions.ON,
NodeActions.OFF,
NodeActions.POWER_CYCLE,
NodeActions.SOFT_OFF,
NodeActions.CHECK_POWER,
],
Expand Down Expand Up @@ -127,6 +128,15 @@ const generateActionMenus = (
return groupLinks;
}

// Only show "Power cycle" if the feature flag is enabled.
// TODO: Remove DPU provisioning feature flag https://warthogs.atlassian.net/browse/MAASENG-4186
if (
action === NodeActions.POWER_CYCLE &&
import.meta.env.VITE_APP_DPU_PROVISIONING !== "true"
) {
return groupLinks;
}

if (
singleNode &&
(action === NodeActions.LOCK || action === NodeActions.UNLOCK)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,97 +99,147 @@ export const MachineActionForm = ({
if (!filter) {
return null;
}
switch (action) {
case NodeActions.CLONE:
return (
<CloneForm
setSearchFilter={setSearchFilter}
{...commonMachineFormProps}
/>
);
case NodeActions.COMMISSION:
return <CommissionForm {...commonMachineFormProps} />;
case NodeActions.DELETE:
return (
<DeleteForm
onAfterSuccess={clearSelectedMachines}
onSubmit={() => {
dispatchForSelectedMachines(machineActions.delete);
}}
redirectURL={urls.machines.index}
{...commonNodeFormProps}
/>
);
case NodeActions.DEPLOY:
return <DeployForm {...commonMachineFormProps} />;
case NodeActions.MARK_BROKEN:
return <MarkBrokenForm {...commonMachineFormProps} />;
case NodeActions.OVERRIDE_FAILED_TESTING:
return <OverrideTestForm {...commonMachineFormProps} />;
case NodeActions.RELEASE:
return <ReleaseForm {...commonMachineFormProps} />;
case NodeActions.SET_POOL:
return <SetPoolForm {...commonMachineFormProps} />;
case NodeActions.SET_ZONE:
return (
<SetZoneForm<MachineEventErrors>
onSubmit={(zoneID) => {
dispatch(machineActions.cleanup());
dispatchForSelectedMachines(machineActions.setZone, {
zone_id: zoneID,
});
}}
{...commonNodeFormProps}
/>
);
case NodeActions.TAG:
case NodeActions.UNTAG:
return <TagForm {...commonMachineFormProps} />;
case NodeActions.TEST:
return (
<TestForm<MachineEventErrors>
applyConfiguredNetworking={applyConfiguredNetworking}
hardwareType={hardwareType}
onTest={(args) => {
dispatchForSelectedMachines(machineActions.test, {
enable_ssh: args.enableSSH,
script_input: args.scriptInputs,
testing_scripts: args.scripts.map((script) => script.name),
});
}}
{...commonNodeFormProps}
/>
);
case NodeActions.ABORT:
case NodeActions.ACQUIRE:
case NodeActions.EXIT_RESCUE_MODE:
case NodeActions.LOCK:
case NodeActions.MARK_FIXED:
case NodeActions.ON:
case NodeActions.RESCUE_MODE:
case NodeActions.UNLOCK:
return (
<FieldlessForm
action={action}
actions={machineActions}
{...commonNodeFormProps}
/>
);
case NodeActions.OFF:
case NodeActions.SOFT_OFF:
return (
<PowerOffForm
action={action}
actions={machineActions}
{...commonNodeFormProps}
/>
);
// No form should be opened for this, as it should only
// be available for machine details, and will be dispatched
// immediately on click.
case NodeActions.CHECK_POWER:
return null;
}

return formComponents[action]();
};

const formComponents = {
[NodeActions.CLONE]: () => (
<CloneForm
setSearchFilter={setSearchFilter}
{...commonMachineFormProps}
/>
),
[NodeActions.COMMISSION]: () => (
<CommissionForm {...commonMachineFormProps} />
),
[NodeActions.DELETE]: () => (
<DeleteForm
onAfterSuccess={clearSelectedMachines}
onSubmit={() => {
dispatchForSelectedMachines(machineActions.delete);
}}
redirectURL={urls.machines.index}
{...commonNodeFormProps}
/>
),
[NodeActions.DEPLOY]: () => <DeployForm {...commonMachineFormProps} />,
[NodeActions.MARK_BROKEN]: () => (
<MarkBrokenForm {...commonMachineFormProps} />
),
[NodeActions.OVERRIDE_FAILED_TESTING]: () => (
<OverrideTestForm {...commonMachineFormProps} />
),
[NodeActions.RELEASE]: () => <ReleaseForm {...commonMachineFormProps} />,
[NodeActions.SET_POOL]: () => <SetPoolForm {...commonMachineFormProps} />,
[NodeActions.SET_ZONE]: () => (
<SetZoneForm<MachineEventErrors>
onSubmit={(zoneID) => {
dispatch(machineActions.cleanup());
dispatchForSelectedMachines(machineActions.setZone, {
zone_id: zoneID,
});
}}
{...commonNodeFormProps}
/>
),
[NodeActions.TAG]: () => <TagForm {...commonMachineFormProps} />,
[NodeActions.UNTAG]: () => <TagForm {...commonMachineFormProps} />,
[NodeActions.TEST]: () => (
<TestForm<MachineEventErrors>
applyConfiguredNetworking={applyConfiguredNetworking}
hardwareType={hardwareType}
onTest={(args) => {
dispatchForSelectedMachines(machineActions.test, {
enable_ssh: args.enableSSH,
script_input: args.scriptInputs,
testing_scripts: args.scripts.map((script) => script.name),
});
}}
{...commonNodeFormProps}
/>
),
[NodeActions.ABORT]: () => (
<FieldlessForm
action={action}
actions={machineActions}
{...commonNodeFormProps}
/>
),
[NodeActions.ACQUIRE]: () => (
<FieldlessForm
action={action}
actions={machineActions}
{...commonNodeFormProps}
/>
),
[NodeActions.EXIT_RESCUE_MODE]: () => (
<FieldlessForm
action={action}
actions={machineActions}
{...commonNodeFormProps}
/>
),
[NodeActions.LOCK]: () => (
<FieldlessForm
action={action}
actions={machineActions}
{...commonNodeFormProps}
/>
),
[NodeActions.MARK_FIXED]: () => (
<FieldlessForm
action={action}
actions={machineActions}
{...commonNodeFormProps}
/>
),
[NodeActions.ON]: () => (
<FieldlessForm
action={action}
actions={machineActions}
{...commonNodeFormProps}
/>
),
[NodeActions.RESCUE_MODE]: () => (
<FieldlessForm
action={action}
actions={machineActions}
{...commonNodeFormProps}
/>
),
[NodeActions.UNLOCK]: () => (
<FieldlessForm
action={action}
actions={machineActions}
{...commonNodeFormProps}
/>
),
[NodeActions.POWER_CYCLE]: () => (
<FieldlessForm
action={action}
actions={machineActions}
{...commonNodeFormProps}
/>
),
[NodeActions.OFF]: () => (
<PowerOffForm
action={action}
actions={machineActions}
{...commonNodeFormProps}
/>
),
[NodeActions.SOFT_OFF]: () => (
<PowerOffForm
action={action}
actions={machineActions}
{...commonNodeFormProps}
/>
),
// No form should be opened for this, as it should only
// be available for machine details, and will be dispatched
// immediately on click.
[NodeActions.CHECK_POWER]: () => null,
};

if (selectedCountLoading) {
Expand Down
1 change: 1 addition & 0 deletions src/app/machines/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const MachineActionSidePanelViews = {
POWER_OFF_MACHINE: ["machineActionForm", NodeActions.OFF],
POWER_OFF_MACHINE_SOFT: ["machineActionForm", NodeActions.SOFT_OFF],
POWER_ON_MACHINE: ["machineActionForm", NodeActions.ON],
POWER_CYCLE_MACHINE: ["machineActionForm", NodeActions.POWER_CYCLE],
OVERRIDE_FAILED_TESTING_MACHINE: [
"machineActionForm",
NodeActions.OVERRIDE_FAILED_TESTING,
Expand Down
5 changes: 5 additions & 0 deletions src/app/store/machine/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,11 @@ const machineSlice = createSlice({
[`${NodeActions.RELEASE}Error`]: statusHandlers.release.error,
[`${NodeActions.RELEASE}Start`]: statusHandlers.release.start,
[`${NodeActions.RELEASE}Success`]: statusHandlers.release.success,
// TODO: Add actual functionality here once the backend is ready https://warthogs.atlassian.net/browse/MAASENG-4185
powerCycle: {
prepare: () => ({ payload: null }),
reducer: () => {},
},
removeRequest: {
prepare: (callId: string) =>
({
Expand Down
1 change: 1 addition & 0 deletions src/app/store/types/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export enum NodeActions {
OFF = "off",
ON = "on",
OVERRIDE_FAILED_TESTING = "override-failed-testing",
POWER_CYCLE = "power-cycle", // TODO: Verify string with backend https://warthogs.atlassian.net/browse/MAASENG-4185
RELEASE = "release",
RESCUE_MODE = "rescue-mode",
SET_POOL = "set-pool",
Expand Down
9 changes: 7 additions & 2 deletions src/app/store/utils/node/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,16 @@ export const getNodeActionTitle = (actionName: NodeActions): string => {
[NodeActions.MARK_FIXED]: "Mark fixed",
[NodeActions.OFF]: "Power off",
[NodeActions.ON]: "Power on",
[NodeActions.SOFT_OFF]: "Soft power off",
[NodeActions.OVERRIDE_FAILED_TESTING]: "Override failed testing",
[NodeActions.POWER_CYCLE]: "Power cycle",
[NodeActions.RELEASE]: "Release",
[NodeActions.RESCUE_MODE]: "Enter rescue mode",
[NodeActions.SET_POOL]: "Set pool",
[NodeActions.SET_ZONE]: "Set zone",
[NodeActions.SOFT_OFF]: "Soft power off",
[NodeActions.TAG]: "Tag",
[NodeActions.UNTAG]: "Untag",
[NodeActions.TEST]: "Test",
[NodeActions.UNTAG]: "Untag",
[NodeActions.UNLOCK]: "Unlock",
};

Expand Down Expand Up @@ -128,6 +129,10 @@ export const getNodeActionLabel = (
`Override failed tests for ${modelString}`,
`Overriding failed tests for ${modelString}`,
],
[NodeActions.POWER_CYCLE]: [
`Power cycle ${modelString}`,
`Power cycling ${modelString}`,
],
[NodeActions.RELEASE]: [
`Release ${modelString}`,
`Releasing ${modelString}`,
Expand Down
10 changes: 7 additions & 3 deletions src/app/utils/isValidPortNumber.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { isValidPortNumber } from ".";

it("returns true for any number between 0 and 65535", () => {
for (let i = 0; i <= 65535; i++) {
expect(isValidPortNumber(i)).toBe(true);
}
expect(isValidPortNumber(0)).toBe(true);
expect(isValidPortNumber(80)).toBe(true);
expect(isValidPortNumber(443)).toBe(true);
expect(isValidPortNumber(1234)).toBe(true);
expect(isValidPortNumber(5240)).toBe(true);
expect(isValidPortNumber(8400)).toBe(true);
expect(isValidPortNumber(65535)).toBe(true);
});

it("returns false for numbers larger than 65535", () => {
Expand Down

0 comments on commit 8bd7e81

Please sign in to comment.