Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(machines): Add 'Power cycle' as a feature-flagged action MAASENG-3945 #5566

Merged
Merged
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
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 = {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You just translated the zen of python to JS. Explicit is better than implicit. Looks cleaner albeit being a little more code.

Copy link
Collaborator

Choose a reason for hiding this comment

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

You just translated the zen of python to JS. Explicit is better than implicit. Looks cleaner albeit being a little more code.

Copy link
Collaborator

Choose a reason for hiding this comment

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

had some network glitches, sorry for the duplicates

Copy link
Collaborator

Choose a reason for hiding this comment

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

You just translated the zen of python to JS. Explicit is better than implicit. Looks cleaner albeit being a little more code.

[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", () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Though shall not smuggle in unrelated changes in a PR.
(It's a good change and I can see it won't break things so let's merge it with this one)

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
Loading