Skip to content

Commit

Permalink
fix(topology): adding status icon in the sidebar of vm details (#2131)
Browse files Browse the repository at this point in the history
* feat(topology) : showing status icon

* feat(topology) : utilizing Status component

* feat(topology) : fixing sonar issue

* feat(topology) : removing extra props from StatusIconAndText

* feat(topology-vm) : removing extra test case
  • Loading branch information
its-mitesh-kumar authored Sep 10, 2024
1 parent a4562cc commit b0f27e6
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ describe('StatusIconAndText', () => {
expect(getByTestId('status-text')).not.toBeNull();
});

it('should render with status text', () => {
const { getByTestId } = render(
<StatusIconAndText
icon={<div id="green-check-icon" />}
title={ComputedStatus.Succeeded}
/>,
);

expect(getByTestId('icon-with-title-Succeeded')).not.toBeNull();
expect(getByTestId('status-text')).toHaveTextContent('Succeeded');
});

it('should render DASH when there is not title', () => {
const { getByText } = render(
<StatusIconAndText
Expand Down
32 changes: 31 additions & 1 deletion plugins/topology/src/common/components/Status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import {
CheckCircleIcon,
ExclamationCircleIcon,
HourglassHalfIcon,
InProgressIcon,
NotStartedIcon,
OffIcon,
PausedIcon,
SyncAltIcon,
UnknownIcon,
} from '@patternfly/react-icons';
Expand All @@ -21,6 +24,7 @@ export type StatusProps = {
status: string | null;
height?: number;
width?: number;
displayStatusText?: string;
};

const DASH = '-';
Expand All @@ -36,13 +40,38 @@ const DASH = '-';
const Status = ({
status,
iconOnly,
displayStatusText,
}: React.PropsWithChildren<StatusProps>): React.ReactElement => {
const statusProps = {
title: status ?? '',
title: displayStatusText || status || '',
iconOnly,
};

switch (status) {
case 'Paused':
return (
<StatusIconAndText
{...statusProps}
icon={<PausedIcon className="bs-topology-status" />}
/>
);

case 'Stopped':
return (
<StatusIconAndText
{...statusProps}
icon={<OffIcon className="bs-topology-status" />}
/>
);

case 'Progress':
return (
<StatusIconAndText
{...statusProps}
icon={<InProgressIcon className="bs-topology-status" />}
/>
);

case 'Pending':
return (
<StatusIconAndText
Expand Down Expand Up @@ -75,6 +104,7 @@ const Status = ({
case 'ErrImagePull':
case 'Failed':
case 'ImagePullBackOff':
case 'Error':
return (
<StatusIconAndText
{...statusProps}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React from 'react';

import { ResourceIcon } from '../../../common/components/ResourceName';
import Status from '../../../common/components/Status';
import { LABEL_USED_TEMPLATE_NAME } from '../../../const';
import { K8sResourcesContext } from '../../../hooks/K8sResourcesContext';
import { VMKind } from '../../../types/vm';
import { VMIKind, VMKind } from '../../../types/vm';
import { getStatus } from '../../../utils/vm-status-utils';
import {
findPodFromVMI,
findVMI,
Expand Down Expand Up @@ -41,19 +43,22 @@ const TopologyVirtualMachineDetails = ({
const vmi = findVMI(vm, allVMIs);
const pods = findPodFromVMI(vmi, allPods);
const devices = getLabeledDevices(vm) || [];
const ipAddrs = getVmiIpAddresses(vmi); // Need data to test
const ipAddrs = getVmiIpAddresses(vmi);
const description = getDescription(vm);
const templateName = getLabel(vm, LABEL_USED_TEMPLATE_NAME);
const nodeName = getVMINodeName(vmi) || getNodeName(pods);
const os = getOperatingSystemName(vm) || getOperatingSystem(vm);
const workloadProfile = getWorkloadProfile(vm);

const vmStatus = getStatus(vm, vmi as VMIKind, pods?.[0]);
return (
<>
<div className="topology-workload-details">
<TopologyWorkloadDetails resource={vm}>
<TopologySideBarDetailsItem label="Status">
{vm?.status?.printableStatus}
<Status
status={vmStatus}
displayStatusText={vm?.status?.printableStatus}
/>
</TopologySideBarDetailsItem>
</TopologyWorkloadDetails>
</div>
Expand Down
24 changes: 23 additions & 1 deletion plugins/topology/src/utils/selector.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { V1Pod } from '@kubernetes/client-node';
import * as _ from 'lodash';

import { K8sResourceKind, VMKind } from '../types/vm';
import { K8sResourceKind, VMIKind, VMKind } from '../types/vm';

type StringHashMap = {
[key: string]: string;
Expand Down Expand Up @@ -40,3 +41,24 @@ export const getLabels = (

return labels || defaultValue || {};
};

export const getVMIConditionsByType = (
vmi: VMIKind,
condType: string,
): VMIKind['status']['conditions'] => {
const conditions = vmi?.status?.conditions;
return (conditions || []).filter(cond => cond.type === condType);
};

export const getDeletetionTimestamp = (vmi: VMIKind | VMKind) =>
_.get(vmi, 'metadata.deletionTimestamp');

export const getStatusConditions = (
statusResource: K8sResourceKind,
defaultValue = [],
) =>
_.get(statusResource, 'status.conditions') === undefined
? defaultValue
: statusResource?.status?.conditions;

export const getPodStatusPhase = (pod: V1Pod) => _.get(pod, 'status.phase');
162 changes: 162 additions & 0 deletions plugins/topology/src/utils/vm-status-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { V1Pod } from '@kubernetes/client-node';

import { K8sResourceKind, VMIKind, VMKind } from '../types/vm';
import {
POD_PHASE_PENDING,
RunStrategy,
StateChangeRequest,
VMIPhase,
VMStatusEnum,
VMStatusSimpleLabel,
} from '../vm-const';
import {
getDeletetionTimestamp,
getPodStatusPhase,
getStatusConditions,
getVMIConditionsByType,
} from './selector';

// Paused
const isVMIPaused = (vmi: VMIKind): boolean =>
getVMIConditionsByType(vmi, 'Paused').length > 0;

// Running
const getStatusPhase = <T = string>(entity: K8sResourceKind): T =>
entity?.status?.phase;
const isRunning = (vmi: VMIKind): boolean => {
if (getStatusPhase(vmi) === VMIPhase.Running) {
return true;
}
return false;
};

// Error
const getStatusConditionOfType = (
statusResource: K8sResourceKind,
type: string,
) =>
getStatusConditions(statusResource).find(
(condition: { type: string }) => condition?.type === type,
);

const isVMError = (vm: VMKind): boolean => {
const vmFailureCond = getStatusConditionOfType(vm, 'Failure');
if (vmFailureCond) {
return true;
}

return false;
};

// Stopped
const isVMCreated = (vm: VMKind) => !!vm?.status?.created;
const isStoppedFromConsole = (vm: VMKind, vmi: VMIKind) => {
return (
vm &&
isVMCreated(vm) &&
getStatusPhase(vmi) === VMIPhase.Succeeded &&
vm.status.printableStatus === VMStatusSimpleLabel.Stopped
);
};

// Deleting
const isDeleting = (vm: VMKind, vmi: VMIKind): boolean =>
!!(getDeletetionTimestamp(vm) || (vmi && getDeletetionTimestamp(vmi)));

// Stopping
const isVMExpectedRunning = (vm: VMKind, vmi: VMIKind) => {
if (!vm?.spec) {
return false;
}
const { running, runStrategy } = vm.spec;

if (running !== null) {
return running;
}

if (runStrategy !== null) {
let changeRequests;
switch (runStrategy as RunStrategy) {
case RunStrategy.Halted:
return false;
case RunStrategy.Always:
return true;
case RunStrategy.RerunOnFailure:
return getStatusPhase<VMIPhase>(vmi) !== VMIPhase.Succeeded;
case RunStrategy.Manual:
default:
changeRequests = new Set(
(vm.status?.stateChangeRequests || []).map(
chRequest => chRequest?.action,
),
);

if (changeRequests.has(StateChangeRequest.Stop)) {
return false;
}
if (changeRequests.has(StateChangeRequest.Start)) {
return true;
}

return isVMCreated(vm); // if there is no change request we can assume created is representing running (current and expected)
}
}
return false;
};
const isBeingStopped = (vm: VMKind, vmi: VMIKind): boolean => {
if (
vm &&
!isVMExpectedRunning(vm, vmi) &&
isVMCreated(vm) &&
getStatusPhase<VMIPhase>(vmi) !== VMIPhase.Succeeded
) {
return true;
}

return false;
};

// Starting
const isStarting = (vm: VMKind, vmi: VMIKind): boolean => {
if (vm && isVMCreated(vm) && isVMExpectedRunning(vm, vmi)) {
return true;
}
return false;
};

// isInProgress
const isInProgress = (vm: VMKind, vmi: VMIKind) =>
isStarting(vm, vmi) || isBeingStopped(vm, vmi) || isDeleting(vm, vmi);

// VMI_WAITING (PENDING)
const isVMIWaiting = (vmi: VMIKind) => getStatusPhase(vmi) === VMIPhase.Pending;

// CDI_IMPORT_PENDING(PENDING)
const isCDIImportPending = (pod: V1Pod): boolean =>
getPodStatusPhase(pod) === POD_PHASE_PENDING;

// V2V_CONVERSION_PENDING(PENDING)
const isV2VConversionPemding = (podOfVM: V1Pod): boolean => {
const podPhase = getPodStatusPhase(podOfVM);
return podPhase === POD_PHASE_PENDING;
};
// PENDING
const isPending = (vmi: VMIKind, pod: V1Pod) =>
isVMIWaiting(vmi) || isCDIImportPending(pod) || isV2VConversionPemding(pod);

export const getStatus = (vm: VMKind, vmi: VMIKind, pod: V1Pod) => {
if (isVMIPaused(vmi)) {
return VMStatusEnum.PAUSED;
} else if (isRunning(vmi)) {
return VMStatusEnum.RUNNING;
} else if (isVMError(vm)) {
return VMStatusEnum.ERROR;
} else if (isStoppedFromConsole(vm, vmi)) {
return VMStatusEnum.STOPPED;
} else if (isInProgress(vm, vmi)) {
return VMStatusEnum.IN_PROGRESS;
} else if (isPending(vmi, pod)) {
return VMStatusEnum.PENDING;
}
return VMStatusEnum.UNKNOWN;
};
38 changes: 38 additions & 0 deletions plugins/topology/src/vm-const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export enum VMIPhase {
Pending = 'Pending',
Scheduling = 'Scheduling',
Scheduled = 'Scheduled',
Running = 'Running',
Succeeded = 'Succeeded',
Failed = 'Failed',
Unknown = 'Unknown',
}
export enum VMStatusSimpleLabel {
Starting = 'Starting',
Paused = 'Paused',
Migrating = 'Migrating',
Stopping = 'Stopping',
Running = 'Running',
Stopped = 'Stopped',
Deleting = 'Deleting',
}
export enum VMStatusEnum {
PAUSED = 'Paused',
RUNNING = 'Running',
STOPPED = 'Stopped',
ERROR = 'Error',
PENDING = 'Pending', // VMI_WAITING,CDI_IMPORT_PENDING,V2V_CONVERSION_PENDING
IN_PROGRESS = 'Progress', // STARTING, STOPPING, DELETING
UNKNOWN = 'Unknown',
}
export enum RunStrategy {
Always = 'Always',
RerunOnFailure = 'RerunOnFailure',
Halted = 'Halted',
Manual = 'Manual',
}
export enum StateChangeRequest {
Start = 'Start',
Stop = 'Stop',
}
export const POD_PHASE_PENDING = 'Pending';

0 comments on commit b0f27e6

Please sign in to comment.