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

HPA In-Context (Delete | Edit | Topology Sidebar) #6150

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
78 changes: 73 additions & 5 deletions frontend/packages/console-app/src/actions/modify-hpa.ts
@@ -1,16 +1,84 @@
import { history, KebabAction } from '@console/internal/components/utils';
import { K8sKind, K8sResourceCommon } from '@console/internal/module/k8s';
import { KebabAction } from '@console/internal/components/utils';
import {
HorizontalPodAutoscalerKind,
K8sKind,
K8sResourceCommon,
referenceForModel,
} from '@console/internal/module/k8s';
import { HorizontalPodAutoscalerModel } from '@console/internal/models';
import deleteHPAModal from '@console/dev-console/src/components/hpa/DeleteHPAModal';

export const AddHorizontalPodAutoScaler: KebabAction = (kind: K8sKind, obj: K8sResourceCommon) => ({
type RelatedResources = {
hpas?: HorizontalPodAutoscalerKind[];
};

const hasHPAs = (mapOfResources: RelatedResources) =>
Array.isArray(mapOfResources?.hpas) && mapOfResources.hpas.length > 0;

const hpaRoute = ({ metadata: { name, namespace } }: K8sResourceCommon, kind: K8sKind) =>
`/workload-hpa/ns/${namespace}/${referenceForModel(kind)}/${name}`;

export const AddHorizontalPodAutoScaler: KebabAction = (
kind: K8sKind,
obj: K8sResourceCommon,
resources: RelatedResources,
) => ({
label: `Add ${HorizontalPodAutoscalerModel.label}`,
href: hpaRoute(obj, kind),
hidden: hasHPAs(resources),
accessReview: {
group: HorizontalPodAutoscalerModel.apiGroup,
resource: HorizontalPodAutoscalerModel.plural,
namespace: obj.metadata.namespace,
verb: 'create',
},
});

export const EditHorizontalPodAutoScaler: KebabAction = (
kind: K8sKind,
obj: K8sResourceCommon,
resources: RelatedResources,
) => ({
label: `Edit ${HorizontalPodAutoscalerModel.label}`,
href: hpaRoute(obj, kind),
hidden: !hasHPAs(resources),
accessReview: {
group: HorizontalPodAutoscalerModel.apiGroup,
resource: HorizontalPodAutoscalerModel.plural,
namespace: obj.metadata.namespace,
verb: 'update',
},
});

export const DeleteHorizontalPodAutoScaler: KebabAction = (
kind: K8sKind,
obj: K8sResourceCommon,
resources: RelatedResources,
) => ({
label: `Remove ${HorizontalPodAutoscalerModel.label}`,
callback: () => {
history.push(`/workload-hpa/ns/${obj.metadata.namespace}/${kind.kind}/${obj.metadata.name}`);
deleteHPAModal({
workload: obj,
hpa: resources?.hpas?.[0],
});
},
hidden: !hasHPAs(resources),
accessReview: {
group: HorizontalPodAutoscalerModel.apiGroup,
resource: HorizontalPodAutoscalerModel.plural,
namespace: obj.metadata.namespace,
verb: 'create',
verb: 'delete',
},
});

export const hideActionForHPAs = (action: KebabAction): KebabAction => (
kind: K8sKind,
obj: K8sResourceCommon,
resources: RelatedResources,
) => {
const actionOptions = action(kind, obj);
return {
...actionOptions,
hidden: hasHPAs(resources) || actionOptions.hidden,
};
};
20 changes: 17 additions & 3 deletions frontend/packages/console-shared/src/components/pod/PodRing.tsx
Expand Up @@ -3,7 +3,8 @@ import * as _ from 'lodash';
import { Button, Split, SplitItem, Bullseye } from '@patternfly/react-core';
import { K8sResourceKind, k8sPatch, K8sKind } from '@console/internal/module/k8s';
import { AngleUpIcon, AngleDownIcon } from '@patternfly/react-icons';
import { podRingLabel, usePodScalingAccessStatus } from '../../utils';
import { useRelatedHPA } from '@console/dev-console/src/components/hpa/hooks';
import { hpaPodRingLabel, podRingLabel, usePodScalingAccessStatus } from '../../utils';
import { ExtPodKind } from '../../types';
import PodStatus from './PodStatus';
import './PodRing.scss';
Expand All @@ -28,7 +29,7 @@ const PodRing: React.FC<PodRingProps> = ({
enableScaling = true,
}) => {
const [clickCount, setClickCount] = React.useState(obj.spec.replicas);
const isScalingAllowed = usePodScalingAccessStatus(
const isAccessScalingAllowed = usePodScalingAccessStatus(
obj,
resourceKind,
pods,
Expand Down Expand Up @@ -67,8 +68,21 @@ const PodRing: React.FC<PodRingProps> = ({
setClickCount(clickCount + operation);
handleScaling(clickCount + operation);
};

const {
apiVersion,
kind,
metadata: { name, namespace },
} = obj;
const [hpa] = useRelatedHPA(apiVersion, kind, name, namespace);
const hpaControlledScaling = !!hpa;

const isScalingAllowed = isAccessScalingAllowed && !hpaControlledScaling;

const resourceObj = rc || obj;
const { title, subTitle, titleComponent } = podRingLabel(resourceObj, obj.kind, pods);
const { title, subTitle, titleComponent } = hpaControlledScaling
? hpaPodRingLabel(resourceObj, hpa, pods)
: podRingLabel(resourceObj, kind, pods);

return (
<Split>
Expand Down
9 changes: 8 additions & 1 deletion frontend/packages/console-shared/src/types/resource.ts
@@ -1,4 +1,10 @@
import { JobKind, K8sResourceKind, PodKind, RouteKind } from '@console/internal/module/k8s';
import {
HorizontalPodAutoscalerKind,
JobKind,
K8sResourceKind,
PodKind,
RouteKind,
} from '@console/internal/module/k8s';
import { DEPLOYMENT_STRATEGY } from '../constants';
import { OverviewItemAlerts, PodControllerOverviewItem } from './pod';
import { ClusterServiceVersionKind } from '@console/operator-lifecycle-manager';
Expand All @@ -25,6 +31,7 @@ export type OverviewItem<T = K8sResourceKind> = {
current?: PodControllerOverviewItem;
isRollingOut?: boolean;
obj: T;
hpas?: HorizontalPodAutoscalerKind[];
pods?: PodKind[];
previous?: PodControllerOverviewItem;
routes: RouteKind[];
Expand Down
18 changes: 18 additions & 0 deletions frontend/packages/console-shared/src/utils/pod-ring-utils.ts
Expand Up @@ -16,6 +16,7 @@ import {
K8sResourceKind,
K8sKind,
SelfSubjectAccessReviewKind,
HorizontalPodAutoscalerKind,
} from '@console/internal/module/k8s';
import { useSafetyFirst } from '@console/internal/components/safety-first';
import { PodRCData, PodRingResources, PodRingData, ExtPodKind } from '../types';
Expand Down Expand Up @@ -206,6 +207,23 @@ export const podRingLabel = (
}
};

export const hpaPodRingLabel = (
obj: K8sResourceKind,
hpa: HorizontalPodAutoscalerKind,
pods: ExtPodKind[],
): PodRingLabelType => {
const desiredPodCount = obj.spec?.replicas;
const desiredPods = hpa.status?.desiredReplicas || desiredPodCount;
const currentPods = hpa.status?.currentReplicas;
const scaling =
(!currentPods && !!desiredPods) || !pods.every((p) => p.status?.phase === 'Running');
return {
title: scaling ? 'Autoscaling' : 'Autoscaled',
subTitle: `to ${desiredPods}`,
titleComponent: getTitleComponent(false, true),
};
};

export const usePodScalingAccessStatus = (
obj: K8sResourceKind,
resourceKind: K8sKind,
Expand Down
5 changes: 5 additions & 0 deletions frontend/packages/console-shared/src/utils/resource-utils.ts
Expand Up @@ -54,6 +54,7 @@ import {
} from '../constants';
import { resourceStatus, podStatus } from './ResourceStatus';
import { isKnativeServing, isIdled } from './pod-utils';
import { doesHpaMatch } from '@console/dev-console/src/components/hpa/hpa-utils';

export const getResourceList = (namespace: string, resList?: any): FirehoseResource[] => {
let resources: FirehoseResource[] = [
Expand Down Expand Up @@ -855,10 +856,12 @@ export const createDeploymentConfigItem = (
deploymentConfig,
resources?.monitoringAlerts,
);
const hpas = resources?.hpas?.data?.filter(doesHpaMatch(deploymentConfig));
const overviewItems = {
alerts,
buildConfigs,
current,
hpas,
isRollingOut,
obj: deploymentConfig,
previous,
Expand Down Expand Up @@ -907,11 +910,13 @@ export const createDeploymentItem = (
const status = resourceStatus(deployment, current, isRollingOut);
const pods = [..._.get(current, 'pods', []), ..._.get(previous, 'pods', [])];
const monitoringAlerts = getWorkloadMonitoringAlerts(deployment, resources?.monitoringAlerts);
const hpas = resources?.hpas?.data?.filter(doesHpaMatch(deployment));
const overviewItem = {
obj: deployment,
alerts,
buildConfigs,
current,
hpas,
isRollingOut,
previous,
pods,
Expand Down
@@ -0,0 +1,82 @@
import * as React from 'react';
import { Form } from '@patternfly/react-core';
import { ExclamationTriangleIcon } from '@patternfly/react-icons';
import { global_warning_color_100 as warningColor } from '@patternfly/react-tokens';
import {
createModalLauncher,
ModalBody,
ModalComponentProps,
ModalSubmitFooter,
ModalTitle,
} from '@console/internal/components/factory/modal';
import { HorizontalPodAutoscalerModel } from '@console/internal/models';
import { LoadingInline } from '@console/internal/components/utils';
import {
HorizontalPodAutoscalerKind,
k8sKill,
K8sResourceCommon,
} from '@console/internal/module/k8s';

type DeleteHPAModalProps = ModalComponentProps & {
hpa: HorizontalPodAutoscalerKind;
workload: K8sResourceCommon;
};

const DeleteHPAModal: React.FC<DeleteHPAModalProps> = ({ close, hpa, workload }) => {
const [submitError, setSubmitError] = React.useState<string>(null);
const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false);
const hpaName = hpa.metadata.name;
const workloadName = workload.metadata.name;

const handleSubmit = (e) => {
e.preventDefault();
setIsSubmitting(true);
k8sKill(HorizontalPodAutoscalerModel, hpa)
.then(() => {
close();
})
.catch((error) => {
setSubmitError(
error?.message ||
`Unknown error removing ${HorizontalPodAutoscalerModel.label} ${hpaName}.`,
);
});
};

return (
<Form onSubmit={handleSubmit}>
<div className="modal-content">
<ModalTitle>
<ExclamationTriangleIcon color={warningColor.value} /> Remove{' '}
{HorizontalPodAutoscalerModel.label}?
</ModalTitle>
<ModalBody>
{hpaName ? (
<>
<p>
Are you sure you want to remove the {HorizontalPodAutoscalerModel.label}{' '}
<b>{hpaName}</b> from <b>{workloadName}</b>?
</p>
<p>
The resources that are attached to the {HorizontalPodAutoscalerModel.label} will be
deleted.
</p>
</>
) : (
!submitError && <LoadingInline />
)}
</ModalBody>
<ModalSubmitFooter
errorMessage={submitError}
inProgress={isSubmitting}
submitText="Remove"
submitDanger
submitDisabled={!!submitError}
cancel={close}
/>
</div>
</Form>
);
};

export default createModalLauncher(DeleteHPAModal);
Expand Up @@ -13,7 +13,7 @@ const HPADetailsForm: React.FC = () => {
const {
setFieldValue,
values: {
disabledFields: { cpuUtilization, memoryUtilization },
disabledFields: { name: nameDisabled, cpuUtilization, memoryUtilization },
showCanUseYAMLMessage,
},
} = useFormikContext<HPAFormValues>();
Expand Down Expand Up @@ -63,7 +63,7 @@ const HPADetailsForm: React.FC = () => {
<div className="row">
<div className="col-lg-8">
<Flex direction={{ default: 'column' }}>
<InputField label="Name" name={`${name}.metadata.name`} />
<InputField isDisabled={nameDisabled} label="Name" name={`${name}.metadata.name`} />
<NumberSpinnerField label="Minimum Pods" name={`${name}.spec.minReplicas`} />
<NumberSpinnerField label="Maximum Pods" name={`${name}.spec.maxReplicas`} />
<HPAUtilizationField
Expand Down
11 changes: 9 additions & 2 deletions frontend/packages/dev-console/src/components/hpa/HPAForm.tsx
Expand Up @@ -7,6 +7,7 @@ import { HorizontalPodAutoscalerKind, K8sResourceCommon } from '@console/interna
import HPADetailsForm from './HPADetailsForm';
import { sanitizeHPAToForm } from './hpa-utils';
import { HPAFormValues } from './types';
import { HorizontalPodAutoscalerModel } from '@console/internal/models';

type HPAFormProps = {
targetResource: K8sResourceCommon;
Expand All @@ -25,7 +26,13 @@ const HPAForm: React.FC<FormikProps<HPAFormValues> & HPAFormProps> = ({
}) => {
const isForm = values.editorType === EditorType.Form;
const formEditor = <HPADetailsForm />;
const yamlEditor = <YAMLEditorField name="yamlData" onSave={handleSubmit} />;
const yamlEditor = (
<YAMLEditorField
name="yamlData"
onSave={handleSubmit}
schemaModel={HorizontalPodAutoscalerModel}
/>
);
const customMetrics = false;

React.useEffect(() => {
Expand Down Expand Up @@ -55,7 +62,7 @@ const HPAForm: React.FC<FormikProps<HPAFormValues> & HPAFormProps> = ({
errorMessage={status?.submitError}
isSubmitting={isSubmitting}
submitLabel="Save"
disableSubmit={isForm && (!isEmpty(errors) || status?.submitError)}
disableSubmit={isForm && !isEmpty(errors)}
resetLabel="Cancel"
sticky
/>
Expand Down