Skip to content

Commit

Permalink
generify 403 errors representation
Browse files Browse the repository at this point in the history
- add another option to cleanupAndGetResults
  • Loading branch information
suomiy committed Sep 1, 2020
1 parent 3b283e2 commit a650afe
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ export const createVMAction = (id: string) => (dispatch, getState) => {
const create = isCreateTemplate ? createVMTemplate : _createVM;
create(params)
.then(() => getResults(enhancedK8sMethods))
.catch((error) => cleanupAndGetResults(enhancedK8sMethods, error))
.catch((error) =>
cleanupAndGetResults(enhancedK8sMethods, error, { prettyPrintPermissionErrors: false }),
)
.then(({ isValid, ...tabState }: ResultsWrapper) =>
dispatch(
vmWizardInternalActions[InternalActionType.SetResults](id, tabState, isValid, false, false),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,26 @@ export const startVMImportOperatorWithCleanup = ({ getState, id, dispatch }: Upd
)
.catch((e) =>
// eslint-disable-next-line promise/no-nesting
cleanupAndGetResults(enhancedK8sMethods, e).then((results) => {
const errors = errorsFirstSort([...results.errors, ...results.requestResults]);
if (results.mainError) {
consoleWarn(results.mainError?.message, results.mainError?.detail);
}
errors.forEach((o) => consoleWarn(o.title, o.content.data));
return dispatch(
vmWizardInternalActions[InternalActionType.UpdateImportProviderField](
id,
VMImportProvider.OVIRT,
OvirtProviderField.CONTROLLER_LAST_ERROR,
{
isHidden: asHidden(false, OvirtProviderField.CONTROLLER_LAST_ERROR),
errors: results,
},
),
);
}),
cleanupAndGetResults(enhancedK8sMethods, e, { prettyPrintPermissionErrors: true }).then(
(results) => {
const errors = errorsFirstSort([...results.errors, ...results.requestResults]);
if (results.mainError) {
consoleWarn(results.mainError?.message, results.mainError?.detail);
}
errors.forEach((o) => consoleWarn(o.title, o.content.data));
return dispatch(
vmWizardInternalActions[InternalActionType.UpdateImportProviderField](
id,
VMImportProvider.OVIRT,
OvirtProviderField.CONTROLLER_LAST_ERROR,
{
isHidden: asHidden(false, OvirtProviderField.CONTROLLER_LAST_ERROR),
errors: results,
},
),
);
},
),
)
.catch((le) => consoleError(le));
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,26 @@ export const startV2VVMWareControllerWithCleanup = ({ getState, id, dispatch }:
)
.catch((e) =>
// eslint-disable-next-line promise/no-nesting
cleanupAndGetResults(enhancedK8sMethods, e).then((results) => {
const errors = errorsFirstSort([...results.errors, ...results.requestResults]);
if (results.mainError) {
consoleWarn(results.mainError?.message, results.mainError?.detail);
}
errors.forEach((o) => consoleWarn(o.title, o.content.data));
return dispatch(
vmWizardInternalActions[InternalActionType.UpdateImportProviderField](
id,
VMImportProvider.VMWARE,
VMWareProviderField.CONTROLLER_LAST_ERROR,
{
isHidden: asHidden(false, VMWareProviderField.CONTROLLER_LAST_ERROR),
errors: results,
},
),
);
}),
cleanupAndGetResults(enhancedK8sMethods, e, { prettyPrintPermissionErrors: true }).then(
(results) => {
const errors = errorsFirstSort([...results.errors, ...results.requestResults]);
if (results.mainError) {
consoleWarn(results.mainError?.message, results.mainError?.detail);
}
errors.forEach((o) => consoleWarn(o.title, o.content.data));
return dispatch(
vmWizardInternalActions[InternalActionType.UpdateImportProviderField](
id,
VMImportProvider.VMWARE,
VMWareProviderField.CONTROLLER_LAST_ERROR,
{
isHidden: asHidden(false, VMWareProviderField.CONTROLLER_LAST_ERROR),
errors: results,
},
),
);
},
),
)
.catch((le) => consoleError(le));
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export const INSUFFICIENT_PERMISSIONS_ERROR_TITLE = 'Insufficient permissions';
export const INSUFFICIENT_PERMISSIONS_ERROR_MESSAGE = 'Insufficient permissions';
export const INSUFFICIENT_PERMISSIONS_ERROR_DESC =
'You do not have the required permissions to perform this action. Please contact your administrator.';
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,13 @@ describe('enhancedK8sMethods', () => {
it('rollback does not fail on 404', async () => {
const methods = spyEnhancedK8sMethods();
(methods as any).k8sKill = async (kind: K8sKind, data: K8sResourceCommon) =>
Promise.reject(new K8sKillError('delete failed', { code: 404 }, data));
Promise.reject(
new K8sKillError(
'delete failed',
{ message: 'delete failed', json: { code: 404 } } as any,
data,
),
);

await methods.k8sCreate(VirtualMachineModel, testVM);
await methods.k8sPatch(VirtualMachineModel, testVM, []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class EnhancedK8sMethods {
this.appendHistory(new HistoryItem(HistoryType.GET, result), enhancedOpts);
return result;
} catch (error) {
throw new K8sGetError(error.message, { name, namespace });
throw new K8sGetError(error.message, error, { name, namespace });
}
};

Expand All @@ -75,7 +75,7 @@ export class EnhancedK8sMethods {
this.appendHistory(new HistoryItem(HistoryType.CREATE, result), enhancedOpts);
return result;
} catch (error) {
throw new K8sCreateError(error.message, data);
throw new K8sCreateError(error.message, error, data);
}
};

Expand All @@ -100,7 +100,7 @@ export class EnhancedK8sMethods {
this.appendHistory(new HistoryItem(HistoryType.PATCH, result), enhancedOpts);
return result;
} catch (error) {
throw new K8sPatchError(error.message, resource, patches);
throw new K8sPatchError(error.message, error, resource, patches);
}
};

Expand All @@ -117,7 +117,7 @@ export class EnhancedK8sMethods {
this.appendHistory(new HistoryItem(HistoryType.DELETE, resource), enhancedOpts);
return result;
} catch (error) {
throw new K8sKillError(error.message, error.json, resource);
throw new K8sKillError(error.message, error, resource);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,58 @@ import { K8sResourceKind, Patch } from '@console/internal/module/k8s';
type K8sError = K8sGetError | K8sCreateError | K8sPatchError | K8sKillError;
type K8sGetObject = { name: string; namespace: string };

export class K8sGetError extends Error {
readonly failedObject: K8sGetObject;
class ErrorWithCause extends Error {
readonly cause: Error;

readonly json: any;

constructor(message: string, failedObject: K8sGetObject) {
constructor(message: string, cause: Error) {
super(message);
this.cause = cause;
this.json = (cause as any)?.json;
}
}

export class K8sGetError extends ErrorWithCause {
readonly failedObject: K8sGetObject;

constructor(message: string, cause: Error, failedObject: K8sGetObject) {
super(message, cause);
this.failedObject = failedObject;
}
}

export class K8sCreateError extends Error {
export class K8sCreateError extends ErrorWithCause {
readonly failedObject: K8sResourceKind;

constructor(message: string, failedObject: K8sResourceKind) {
super(message);
constructor(message: string, cause: Error, failedObject: K8sResourceKind) {
super(message, cause);
this.failedObject = failedObject;
}
}

export class K8sPatchError extends Error {
export class K8sPatchError extends ErrorWithCause {
readonly failedObject: K8sResourceKind;

readonly failedPatches: Patch[];

constructor(message: string, failedObject: K8sResourceKind, failedPatches: Patch[]) {
super(message);
constructor(
message: string,
cause: Error,
failedObject: K8sResourceKind,
failedPatches: Patch[],
) {
super(message, cause);
this.failedObject = failedObject;
this.failedPatches = failedPatches;
}
}

export class K8sKillError extends Error {
export class K8sKillError extends ErrorWithCause {
readonly failedObject: K8sResourceKind;

readonly json: any;

constructor(message: string, json, failedObject: K8sResourceKind) {
super(message);
this.json = json;
constructor(message: string, cause: Error, failedObject: K8sResourceKind) {
super(message, cause);
this.failedObject = failedObject;
}
}
Expand All @@ -54,21 +68,24 @@ export class K8sMultipleErrors extends Error {
}
}

export class K8sDetailError extends Error {
export class K8sDetailError extends ErrorWithCause {
readonly title: string;

readonly detail: string;

constructor({
message,
title = '',
detail = '',
}: {
message: string;
title?: string;
detail?: string;
}) {
super(message);
constructor(
{
message,
title = '',
detail = '',
}: {
message: string;
title?: string;
detail?: string;
},
cause?: Error,
) {
super(message, cause);
this.title = title;
this.detail = detail;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import { getGeneratedName, getKind } from '../../selectors/selectors';
import { getFullResourceId } from '../../utils/utils';
import { EnhancedK8sMethods } from './enhancedK8sMethods';
import { Result, ResultContentType, ResultsWrapper } from './types';
import {
INSUFFICIENT_PERMISSIONS_ERROR_MESSAGE,
INSUFFICIENT_PERMISSIONS_ERROR_DESC,
} from '../../constants/errors/common';

const asResult = ({
obj,
Expand All @@ -35,7 +39,8 @@ const asResult = ({

export const cleanupAndGetResults = async (
enhancedK8sMethods: EnhancedK8sMethods,
{ message, title, detail, failedObject, failedPatches },
{ message, title, detail, json, failedObject, failedPatches },
{ prettyPrintPermissionErrors = false },
): Promise<ResultsWrapper> => {
const actualState = enhancedK8sMethods.getActualState(); // actual state will differ after cleanup

Expand Down Expand Up @@ -84,10 +89,16 @@ export const cleanupAndGetResults = async (
);
}

const isUnauthorized = json?.code === 403 && prettyPrintPermissionErrors;

const mainErrorTitle = isUnauthorized ? INSUFFICIENT_PERMISSIONS_ERROR_MESSAGE : title;
const mainErrorMessage = isUnauthorized ? INSUFFICIENT_PERMISSIONS_ERROR_DESC : message;
const mainErrorDetail = isUnauthorized ? message : detail;

return {
isFatal,
isValid: false,
mainError: { message, title, detail },
mainError: { title: mainErrorTitle, message: mainErrorMessage, detail: mainErrorDetail },
errors: [],
requestResults: [...failureResults, ...cleanupResults],
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
/* eslint-disable camelcase, @typescript-eslint/camelcase,no-await-in-loop */
import { getName, getNamespace } from '@console/shared';
import { EnhancedK8sMethods } from '../../enhancedK8sMethods/enhancedK8sMethods';
import { DataVolumeModel, UploadTokenRequestModel } from '@console/kubevirt-plugin/src/models';
import { V1alpha1DataVolume } from '@console/kubevirt-plugin/src/types/vm/disk/V1alpha1DataVolume';
import { apiVersionForModel, K8sKind, K8sResourceKind } from '@console/internal/module/k8s';
import {
apiVersionForModel,
K8sResourceKind,
k8sCreate,
k8sKill,
k8sGet,
} from '@console/internal/module/k8s';
import { delay } from '../../../utils/utils';
import { K8sCreateError } from '../../enhancedK8sMethods/errors';

const PVC_STATUS_DELAY = 2 * 1000;

Expand All @@ -14,11 +18,7 @@ const UPLOAD_STATES = {
READY: 'UploadReady',
};

const waitForUploadReady = async (
dataVolume: K8sResourceKind,
k8sGet: (kind: K8sKind, name: string, namespace: string) => Promise<K8sResourceKind>,
counter: number = 30,
) => {
const waitForUploadReady = async (dataVolume: K8sResourceKind, counter: number = 30) => {
const dvName = getName(dataVolume);
const namespace = getNamespace(dataVolume);
let dv = dataVolume;
Expand All @@ -30,11 +30,10 @@ const waitForUploadReady = async (
dv = await k8sGet(DataVolumeModel, dvName, namespace);
}

throw new K8sCreateError('Data Volume failed to initiate upload', dataVolume);
throw new Error('Data Volume failed to initiate upload');
};

export const createUploadToken = async (pvcName: string, namespace: string) => {
const { k8sCreate } = new EnhancedK8sMethods();
const createUploadToken = async (pvcName: string, namespace: string) => {
const tokenRequest = {
apiVersion: apiVersionForModel(UploadTokenRequestModel),
kind: UploadTokenRequestModel.kind,
Expand All @@ -51,27 +50,25 @@ export const createUploadToken = async (pvcName: string, namespace: string) => {
const resource = await k8sCreate(UploadTokenRequestModel, tokenRequest);
return resource?.status?.token;
} catch (error) {
throw new K8sCreateError(error.message, tokenRequest);
throw new Error(error.message);
}
};

export const createUploadPVC = async (dataVolume: V1alpha1DataVolume) => {
const { k8sCreate, k8sGet } = new EnhancedK8sMethods();
const dataVolumeName = getName(dataVolume);
const namespace = getNamespace(dataVolume);

try {
const dv = await k8sCreate(DataVolumeModel, dataVolume);
await waitForUploadReady(dv, k8sGet);
await waitForUploadReady(dv);
const token = await createUploadToken(dataVolumeName, namespace);

return { token };
} catch (error) {
throw new K8sCreateError(error.message, dataVolume);
throw new Error(error.message);
}
};

export const killUploadPVC = async (name: string, namespace: string) => {
const { k8sKill } = new EnhancedK8sMethods();
await k8sKill(DataVolumeModel, { metadata: { name, namespace } });
};

0 comments on commit a650afe

Please sign in to comment.